paradocs 1.0.24 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.readthedocs.yml +5 -0
- data/README.md +1 -11
- data/docs/changelog.md +17 -0
- data/docs/custom_configuration.md +10 -0
- data/docs/documentation_generation.md +304 -0
- data/docs/faq.md +21 -0
- data/docs/form_objects_dsl.md +90 -0
- data/docs/index.md +106 -0
- data/docs/payload_builder.md +105 -0
- data/docs/policies.md +309 -0
- data/docs/schema.md +294 -0
- data/docs/struct.md +135 -0
- data/docs/subschema.md +29 -0
- data/lib/paradocs/extensions/payload_builder.rb +45 -0
- data/lib/paradocs/extensions/structure.rb +119 -0
- data/lib/paradocs/field_dsl.rb +12 -0
- data/lib/paradocs/schema.rb +32 -10
- data/lib/paradocs/struct.rb +1 -0
- data/lib/paradocs/version.rb +1 -1
- data/mkdocs.yml +17 -0
- data/paradocs.gemspec +3 -3
- data/requirements.txt +1 -0
- data/spec/extensions/payload_builder_spec.rb +70 -0
- data/spec/extensions/structures_spec.rb +250 -0
- data/spec/field_spec.rb +1 -1
- data/spec/schema_spec.rb +7 -7
- data/spec/struct_spec.rb +8 -8
- data/spec/subschema_spec.rb +4 -4
- metadata +29 -12
- data/lib/paradocs/extensions/insides.rb +0 -77
- data/spec/schema_structures_spec.rb +0 -169
@@ -0,0 +1,45 @@
|
|
1
|
+
module Paradocs
|
2
|
+
module Extensions
|
3
|
+
class PayloadBuilder
|
4
|
+
attr_reader :structure, :result
|
5
|
+
attr_accessor :skip_word
|
6
|
+
def initialize(schema, skip_word: :skip)
|
7
|
+
@structure = schema.structure
|
8
|
+
@skip_word = skip_word
|
9
|
+
end
|
10
|
+
|
11
|
+
def build!(sort_by_schema: false, &block)
|
12
|
+
result = structure.all_nested.map { |name, struct| [name, build_simple_structure(struct, &block)] }.to_h
|
13
|
+
sort_by_schema ? schema.resolve(result).output : result
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def build_simple_structure(struct, &block)
|
19
|
+
struct.map do |key, value|
|
20
|
+
key = key.to_s
|
21
|
+
next if key.start_with?(Paradocs.config.meta_prefix) # skip all the meta fields
|
22
|
+
ex_value = restore_one(key, value, &block)
|
23
|
+
next if ex_value == @skip_word
|
24
|
+
key = value[:alias] || key
|
25
|
+
[key.to_s, ex_value]
|
26
|
+
end.compact.to_h
|
27
|
+
end
|
28
|
+
|
29
|
+
def restore_one(key, value, &block)
|
30
|
+
default = value[:default]
|
31
|
+
ex_value = if value[:structure]
|
32
|
+
data = build_simple_structure(value[:structure], &block)
|
33
|
+
value[:type] == :array ? [data] : data
|
34
|
+
elsif default
|
35
|
+
default.is_a?(Proc) ? default.call : default
|
36
|
+
elsif value[:options] && !value[:options].empty?
|
37
|
+
options = value[:options]
|
38
|
+
value[:type] == :array ? options : options.sample
|
39
|
+
end
|
40
|
+
return ex_value unless block_given?
|
41
|
+
yield(key, value, ex_value, @skip_word)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Paradocs
|
2
|
+
module Extensions
|
3
|
+
class Structure
|
4
|
+
DEFAULT = :generic
|
5
|
+
%w(errors subschemes).each do |key|
|
6
|
+
define_method(key) { "#{Paradocs.config.meta_prefix}#{key}".to_sym }
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :schema, :ignore_transparent, :root
|
10
|
+
attr_accessor :ignore_transparent
|
11
|
+
def initialize(schema, ignore_transparent=true, root="")
|
12
|
+
@schema = schema
|
13
|
+
@ignore_transparent = ignore_transparent
|
14
|
+
@root = root
|
15
|
+
end
|
16
|
+
|
17
|
+
def nested(&block)
|
18
|
+
schema.fields.each_with_object({errors => [], subschemes => {}}) do |(_, field), result|
|
19
|
+
meta, sc = collect_meta(field, root)
|
20
|
+
if sc
|
21
|
+
meta[:structure] = self.class.new(sc, ignore_transparent, meta[:json_path]).nested(&block)
|
22
|
+
result[errors] += meta[:structure].delete(errors)
|
23
|
+
else
|
24
|
+
result[errors] += field.possible_errors
|
25
|
+
end
|
26
|
+
|
27
|
+
field_key = field.meta_data[:alias] || field.key
|
28
|
+
result[field_key] = meta unless ignore_transparent && field.transparent?
|
29
|
+
yield(field_key, meta) if block_given?
|
30
|
+
|
31
|
+
next unless field.mutates_schema?
|
32
|
+
schema.subschemes.each do |name, subschema|
|
33
|
+
result[subschemes][name] = self.class.new(subschema, ignore_transparent, root).nested(&block)
|
34
|
+
result[errors] += result[subschemes][name][errors]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def all_nested(&block)
|
40
|
+
all_flatten(&block).each_with_object({}) do |(name, struct), obj|
|
41
|
+
obj[name] = {}
|
42
|
+
# sort the flatten struct to have iterated 1lvl keys before 2lvl and so on...
|
43
|
+
struct.sort_by { |k, v| k.to_s.count(".") }.each do |key, value|
|
44
|
+
target = obj[name]
|
45
|
+
key, value = key.to_s, value.clone # clone the values, because we do mutation below
|
46
|
+
value.merge!(nested_name: key) if value.respond_to?(:merge) # it can be array (_errors)
|
47
|
+
next target[key.to_sym] = value if key.start_with?(Paradocs.config.meta_prefix) # copy meta fields
|
48
|
+
|
49
|
+
parts = key.split(".")
|
50
|
+
next target[key] ||= value if parts.size == 1 # copy 1lvl key
|
51
|
+
parts.each.with_index do |subkey, index|
|
52
|
+
target[subkey] ||= value
|
53
|
+
next if parts.size == index + 1
|
54
|
+
target[subkey][:structure] ||= {}
|
55
|
+
target = target[subkey][:structure] # target goes deeper for each part
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def all_flatten(schema_structure=nil, &block)
|
62
|
+
schema_structure ||= flatten(&block)
|
63
|
+
if schema_structure[subschemes].empty?
|
64
|
+
schema_structure.delete(subschemes) # don't include redundant key
|
65
|
+
return {DEFAULT => schema_structure}
|
66
|
+
end
|
67
|
+
schema_structure[subschemes].each_with_object({}) do |(name, subschema), result|
|
68
|
+
if subschema[subschemes].empty?
|
69
|
+
result[name] = schema_structure.merge(subschema)
|
70
|
+
result[name][errors] += schema_structure[errors]
|
71
|
+
result[name][errors].uniq!
|
72
|
+
result[name].delete(subschemes)
|
73
|
+
next result[name]
|
74
|
+
end
|
75
|
+
|
76
|
+
all_flatten(subschema).each do |sub_name, schema|
|
77
|
+
result["#{name}_#{sub_name}".to_sym] = schema_structure.merge(schema)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def flatten(&block)
|
83
|
+
schema.fields.each_with_object({errors => [], subschemes => {}}) do |(_, field), obj|
|
84
|
+
meta, sc = collect_meta(field, root)
|
85
|
+
humanized_name = meta.delete(:nested_name)
|
86
|
+
obj[humanized_name] = meta unless ignore_transparent && field.transparent?
|
87
|
+
|
88
|
+
if sc
|
89
|
+
deep_result = self.class.new(sc, ignore_transparent, meta[:json_path]).flatten(&block)
|
90
|
+
obj[errors] += deep_result.delete(errors)
|
91
|
+
obj[subschemes].merge!(deep_result.delete(subschemes))
|
92
|
+
obj.merge!(deep_result)
|
93
|
+
else
|
94
|
+
obj[errors] += field.possible_errors
|
95
|
+
end
|
96
|
+
yield(humanized_name, meta) if block_given?
|
97
|
+
next unless field.mutates_schema?
|
98
|
+
schema.subschemes.each do |name, subschema|
|
99
|
+
obj[subschemes][name] ||= self.class.new(subschema, ignore_transparent, root).flatten(&block)
|
100
|
+
obj[errors] += obj[subschemes][name][errors]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def collect_meta(field, root)
|
108
|
+
field_key = field.meta_data[:alias] || field.key
|
109
|
+
json_path = root.empty? ? "$.#{field_key}" : "#{root}.#{field_key}"
|
110
|
+
meta = field.meta_data.merge(json_path: json_path)
|
111
|
+
sc = meta.delete(:schema)
|
112
|
+
meta[:mutates_schema] = true if meta.delete(:mutates_schema)
|
113
|
+
json_path << "[]" if meta[:type] == :array
|
114
|
+
meta[:nested_name] = json_path.gsub("[]", "")[2..-1]
|
115
|
+
[meta, sc]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/paradocs/field_dsl.rb
CHANGED
@@ -32,5 +32,17 @@ module Paradocs
|
|
32
32
|
def length(opts)
|
33
33
|
policy :length, opts
|
34
34
|
end
|
35
|
+
|
36
|
+
def description(text)
|
37
|
+
meta description: text
|
38
|
+
end
|
39
|
+
|
40
|
+
def as(identifier)
|
41
|
+
meta alias: identifier
|
42
|
+
end
|
43
|
+
|
44
|
+
def example(value)
|
45
|
+
meta example: value
|
46
|
+
end
|
35
47
|
end
|
36
48
|
end
|
data/lib/paradocs/schema.rb
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
require "paradocs/context"
|
2
2
|
require "paradocs/results"
|
3
3
|
require "paradocs/field"
|
4
|
-
require "paradocs/extensions/
|
4
|
+
require "paradocs/extensions/structure"
|
5
|
+
require "paradocs/extensions/payload_builder"
|
5
6
|
|
6
7
|
module Paradocs
|
7
8
|
class Schema
|
8
|
-
include Extensions::Insides
|
9
|
-
|
10
9
|
attr_accessor :environment
|
11
|
-
attr_reader :subschemes
|
10
|
+
attr_reader :subschemes, :structure_builder
|
12
11
|
def initialize(options={}, &block)
|
13
12
|
@options = options
|
14
13
|
@fields = {}
|
@@ -18,6 +17,7 @@ module Paradocs
|
|
18
17
|
@default_field_policies = []
|
19
18
|
@ignored_field_keys = []
|
20
19
|
@expansions = {}
|
20
|
+
@structure_builder = Paradocs::Extensions::Structure.new(self)
|
21
21
|
end
|
22
22
|
|
23
23
|
def schema
|
@@ -29,6 +29,27 @@ module Paradocs
|
|
29
29
|
f.mutates_schema!(&block)
|
30
30
|
end
|
31
31
|
|
32
|
+
def structure(ignore_transparent: true)
|
33
|
+
flush!
|
34
|
+
structure_builder.ignore_transparent = ignore_transparent
|
35
|
+
structure_builder
|
36
|
+
end
|
37
|
+
|
38
|
+
def example_payloads(&block)
|
39
|
+
@example_payloads ||= Paradocs::Extensions::PayloadBuilder.new(self).build!(&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
def walk(meta_key = nil, &visitor)
|
43
|
+
r = visit(meta_key, &visitor)
|
44
|
+
Results.new(r, {}, {})
|
45
|
+
end
|
46
|
+
|
47
|
+
def visit(meta_key = nil, &visitor)
|
48
|
+
fields.each_with_object({}) do |(_, field), m|
|
49
|
+
m[field.key] = field.visit(meta_key, &visitor)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
32
53
|
def subschema(*args, &block)
|
33
54
|
options = args.last.is_a?(Hash) ? args.last : {}
|
34
55
|
name = args.first.is_a?(Symbol) ? args.shift : Paradocs.config.default_schema_name
|
@@ -143,6 +164,11 @@ module Paradocs
|
|
143
164
|
end
|
144
165
|
end
|
145
166
|
|
167
|
+
def flush!
|
168
|
+
@fields = {}
|
169
|
+
@applied = false
|
170
|
+
end
|
171
|
+
|
146
172
|
protected
|
147
173
|
|
148
174
|
attr_reader :definitions, :options
|
@@ -155,7 +181,8 @@ module Paradocs
|
|
155
181
|
invoke_subschemes!(val, context, flds: flds)
|
156
182
|
flds.each_with_object({}) do |(_, field), m|
|
157
183
|
r = field.resolve(val, context.sub(field.key))
|
158
|
-
|
184
|
+
key = field.meta_data[:alias] || field.key
|
185
|
+
m[key] = r.value if r.eligible?
|
159
186
|
end
|
160
187
|
end
|
161
188
|
|
@@ -205,10 +232,5 @@ module Paradocs
|
|
205
232
|
end
|
206
233
|
@applied = true
|
207
234
|
end
|
208
|
-
|
209
|
-
def flush!
|
210
|
-
@fields = {}
|
211
|
-
@applied = false
|
212
|
-
end
|
213
235
|
end
|
214
236
|
end
|
data/lib/paradocs/struct.rb
CHANGED
data/lib/paradocs/version.rb
CHANGED
data/mkdocs.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
site_name: Paradocs
|
2
|
+
theme:
|
3
|
+
name: readthedocs
|
4
|
+
nav_style: dark
|
5
|
+
nav:
|
6
|
+
- 'index.md'
|
7
|
+
- 'policies.md'
|
8
|
+
- 'schema.md'
|
9
|
+
- 'struct.md'
|
10
|
+
- 'form_objects_dsl.md'
|
11
|
+
- 'subschema.md'
|
12
|
+
- 'documentation_generation.md'
|
13
|
+
- 'payload_builder.md'
|
14
|
+
- 'custom_configuration.md'
|
15
|
+
- 'faq.md'
|
16
|
+
- 'changelog.md'
|
17
|
+
|
data/paradocs.gemspec
CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Paradocs::VERSION
|
9
9
|
spec.authors = ["Maxim Tkachenko", "Ismael Celis"]
|
10
10
|
spec.email = ["tkachenko.maxim.w@gmail.com", "ismaelct@gmail.com"]
|
11
|
-
spec.description = %q{Flexible
|
11
|
+
spec.description = %q{Flexible DRY validations with API docs generation done right TLDR; parametrics on steroids.}
|
12
12
|
spec.summary = %q{A huge add-on for original gem mostly focused on retrieving the more metadata from declared schemas as possible.}
|
13
|
-
spec.homepage = "https://
|
13
|
+
spec.homepage = "https://paradocs.readthedocs.io/en/latest"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.require_paths = ["lib"]
|
20
20
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 2.1"
|
22
|
-
spec.add_development_dependency "rake",
|
22
|
+
spec.add_development_dependency "rake", '~> 12.3'
|
23
23
|
spec.add_development_dependency "rspec", '3.4.0'
|
24
24
|
spec.add_development_dependency "pry", "~> 0"
|
25
25
|
end
|
data/requirements.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
mkdocs==1.1.2
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Paradocs::Extensions::PayloadBuilder do
|
4
|
+
let(:schema) do
|
5
|
+
Paradocs::Schema.new do
|
6
|
+
field(:test).present.type(:string).mutates_schema! { :subschema1 }
|
7
|
+
subschema(:subschema1) do
|
8
|
+
field(:subtest1).declared.type(:number)
|
9
|
+
end
|
10
|
+
subschema(:subschema2) do
|
11
|
+
field(:subtest2).required.type(:array).schema do
|
12
|
+
field(:hello).type(:string).mutates_schema! { |*| :deep_schema }
|
13
|
+
subschema(:deep_schema) { field(:deep_field).type(:boolean) }
|
14
|
+
subschema(:empty) { }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
# 2 mutation fields and more than 1 subschema pack work good in validation but docs
|
18
|
+
# will contain only 1 subschema at once: foo subschemes will never be mixed with test subschemes
|
19
|
+
field(:foo).required.type(:object).schema do
|
20
|
+
field(:bar).present.type(:string).options(["foo", "bar"]).mutates_schema! do |value, *|
|
21
|
+
value == "foo" ? :fooschema : :barschema
|
22
|
+
end
|
23
|
+
subschema(:fooschema) { }
|
24
|
+
subschema(:barschema) do
|
25
|
+
field(:barfield).present.type(:boolean).as(:bar_field)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "gives an example payload and takes into account the subschemes" do
|
32
|
+
allow_any_instance_of(Array).to receive(:sample) { "bar" }
|
33
|
+
payloads = described_class.new(schema).build!
|
34
|
+
expect(payloads.keys.sort).to eq([:barschema, :fooschema, :subschema1, :subschema2_deep_schema, :subschema2_empty])
|
35
|
+
expect(payloads[:barschema]).to eq({"test" => nil, "foo" => {"bar" => "bar", "bar_field" => nil}})
|
36
|
+
expect(payloads[:fooschema]).to eq({"test" => nil, "foo" => {"bar" => "bar"}})
|
37
|
+
expect(payloads[:subschema1]).to eq({"test" => nil, "foo" => {"bar" => "bar"}, "subtest1" => nil})
|
38
|
+
expect(payloads[:subschema2_deep_schema]).to eq({
|
39
|
+
"subtest2" => [{"deep_field" => nil, "hello" => nil}], "test" => nil, "foo" => {"bar" => "bar"}
|
40
|
+
})
|
41
|
+
expect(payloads[:subschema2_empty]).to eq({
|
42
|
+
"test" => nil, "foo" => {"bar" => "bar"}, "subtest2" => [{"hello" => nil}]
|
43
|
+
})
|
44
|
+
end
|
45
|
+
|
46
|
+
it "yields a usefull block that changes the result" do
|
47
|
+
payloads = described_class.new(schema).build! do |key, meta, example, skip_word|
|
48
|
+
if key == "bar"
|
49
|
+
nil
|
50
|
+
elsif meta[:type] == :boolean
|
51
|
+
true
|
52
|
+
elsif key == "subtest1"
|
53
|
+
skip_word # this key value pair will be ommited
|
54
|
+
else
|
55
|
+
example # return suggested value
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
expect(payloads.keys.sort).to eq([:barschema, :fooschema, :subschema1, :subschema2_deep_schema, :subschema2_empty])
|
60
|
+
expect(payloads[:barschema]).to eq({"test" => nil, "foo" => {"bar" => nil, "bar_field" => true}}) # barfield is change to true and bar is nil
|
61
|
+
expect(payloads[:fooschema]).to eq({"test" => nil, "foo" => {"bar" => nil}}) # bar is nil
|
62
|
+
expect(payloads[:subschema1]).to eq({"test" => nil, "foo" => {"bar" => nil}}) # subtest is missing, bar is nil
|
63
|
+
expect(payloads[:subschema2_deep_schema]).to eq({
|
64
|
+
"subtest2" => [{"deep_field" => true, "hello" => nil}], "test" => nil, "foo" => {"bar" => nil}
|
65
|
+
})
|
66
|
+
expect(payloads[:subschema2_empty]).to eq({
|
67
|
+
"test" => nil, "foo" => {"bar" => nil}, "subtest2" => [{"hello" => nil}]
|
68
|
+
})
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Paradocs::Extensions::Structure do
|
4
|
+
Paradocs.policy :policy_with_error do
|
5
|
+
register_error ArgumentError
|
6
|
+
|
7
|
+
validate do |*|
|
8
|
+
raise ArgumentError
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
Paradocs.policy :policy_with_silent_error do
|
13
|
+
register_silent_error RuntimeError
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:schema) do
|
17
|
+
Paradocs::Schema.new do
|
18
|
+
subschema(:highest_level) { field(:test).present } # no mutations on this level -> subschema ignored
|
19
|
+
|
20
|
+
field(:data).type(:object).present.schema do
|
21
|
+
field(:id).type(:integer).present.policy(:policy_with_error).as(:user_id)
|
22
|
+
field(:name).type(:string).meta(label: "very important staff").description("Example description").example("John")
|
23
|
+
field(:role).type(:string).declared.options(["admin", "user"]).default("user").mutates_schema! do |*|
|
24
|
+
:test_subschema
|
25
|
+
end
|
26
|
+
field(:extra).type(:array).required.schema do
|
27
|
+
field(:extra).declared.default(false).policy(:policy_with_silent_error)
|
28
|
+
end
|
29
|
+
|
30
|
+
mutation_by!(:name) { :subschema }
|
31
|
+
|
32
|
+
subschema(:subschema) do
|
33
|
+
field(:test_field).present
|
34
|
+
end
|
35
|
+
subschema(:test_subschema) do
|
36
|
+
field(:test1).present
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#nested" do
|
43
|
+
it "generates nested data for documentation generation" do
|
44
|
+
result = schema.structure.nested { |k, meta| meta[:block_works] = true unless meta[:present] }
|
45
|
+
expect(result[:_subschemes]).to eq({})
|
46
|
+
expect(result[:_errors]).to eq([ArgumentError])
|
47
|
+
data_structure = result[:data].delete(:structure)
|
48
|
+
expect(result[:data]).to eq({
|
49
|
+
type: :object,
|
50
|
+
required: true,
|
51
|
+
present: true,
|
52
|
+
json_path: "$.data",
|
53
|
+
nested_name: "data",
|
54
|
+
})
|
55
|
+
expect(data_structure[:_subschemes]).to eq({
|
56
|
+
test_subschema: {
|
57
|
+
_errors: [],
|
58
|
+
_subschemes: {},
|
59
|
+
test1: {required: true, present: true, json_path: "$.data.test1", nested_name: "data.test1"}
|
60
|
+
},
|
61
|
+
subschema: {
|
62
|
+
_errors: [],
|
63
|
+
_subschemes: {},
|
64
|
+
test_field: {required: true, present: true, json_path: "$.data.test_field", nested_name: "data.test_field"}
|
65
|
+
}
|
66
|
+
})
|
67
|
+
expect(data_structure[:user_id]).to eq({
|
68
|
+
type: :integer,
|
69
|
+
required: true,
|
70
|
+
present: true,
|
71
|
+
policy_with_error: {errors: [ArgumentError]},
|
72
|
+
alias: :user_id,
|
73
|
+
json_path: "$.data.user_id",
|
74
|
+
nested_name: "data.user_id"
|
75
|
+
})
|
76
|
+
expect(data_structure[:name]).to eq({
|
77
|
+
type: :string,
|
78
|
+
label: "very important staff",
|
79
|
+
description: "Example description",
|
80
|
+
example: "John",
|
81
|
+
mutates_schema: true,
|
82
|
+
block_works: true,
|
83
|
+
json_path: "$.data.name",
|
84
|
+
nested_name: "data.name"
|
85
|
+
})
|
86
|
+
expect(data_structure[:role]).to eq({
|
87
|
+
type: :string,
|
88
|
+
options: ["admin", "user"],
|
89
|
+
default: "user",
|
90
|
+
mutates_schema: true,
|
91
|
+
block_works: true,
|
92
|
+
json_path: "$.data.role",
|
93
|
+
nested_name: "data.role"
|
94
|
+
})
|
95
|
+
expect(data_structure[:extra]).to eq({
|
96
|
+
type: :array,
|
97
|
+
required: true,
|
98
|
+
block_works: true,
|
99
|
+
json_path: "$.data.extra[]",
|
100
|
+
nested_name: "data.extra",
|
101
|
+
structure: {
|
102
|
+
extra: {
|
103
|
+
default: false,
|
104
|
+
block_works: true,
|
105
|
+
json_path: "$.data.extra[].extra",
|
106
|
+
nested_name: "data.extra.extra",
|
107
|
+
policy_with_silent_error: {errors: []}
|
108
|
+
},
|
109
|
+
_subschemes: {}
|
110
|
+
}
|
111
|
+
})
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "#flatten" do
|
116
|
+
it "generates flatten data for documentation generation" do
|
117
|
+
expect(schema.structure.flatten { |key, meta| meta[:block_works] = true if key.split(".").size == 1 }).to eq({
|
118
|
+
"data" => {
|
119
|
+
type: :object,
|
120
|
+
required: true,
|
121
|
+
present: true,
|
122
|
+
block_works: true,
|
123
|
+
json_path: "$.data"
|
124
|
+
},
|
125
|
+
"data.extra" => {
|
126
|
+
type: :array,
|
127
|
+
required: true,
|
128
|
+
json_path: "$.data.extra[]"
|
129
|
+
},
|
130
|
+
"data.extra.extra" => {
|
131
|
+
default: false,
|
132
|
+
json_path: "$.data.extra[].extra",
|
133
|
+
policy_with_silent_error: {errors: []}
|
134
|
+
},
|
135
|
+
"data.user_id" => {
|
136
|
+
type: :integer,
|
137
|
+
required: true,
|
138
|
+
present: true,
|
139
|
+
alias: :user_id,
|
140
|
+
json_path: "$.data.user_id",
|
141
|
+
policy_with_error: {errors: [ArgumentError]}
|
142
|
+
},
|
143
|
+
"data.name" => {
|
144
|
+
type: :string,
|
145
|
+
json_path: "$.data.name",
|
146
|
+
label: "very important staff",
|
147
|
+
description: "Example description",
|
148
|
+
example: "John",
|
149
|
+
mutates_schema: true
|
150
|
+
},
|
151
|
+
"data.role" => {
|
152
|
+
type: :string,
|
153
|
+
options: ["admin", "user"],
|
154
|
+
default: "user",
|
155
|
+
json_path: "$.data.role",
|
156
|
+
mutates_schema: true
|
157
|
+
},
|
158
|
+
_errors: [ArgumentError],
|
159
|
+
_subschemes: {
|
160
|
+
test_subschema: {
|
161
|
+
_errors: [],
|
162
|
+
_subschemes: {},
|
163
|
+
"data.test1"=>{
|
164
|
+
required: true,
|
165
|
+
present: true,
|
166
|
+
json_path: "$.data.test1"
|
167
|
+
}
|
168
|
+
},
|
169
|
+
subschema: {
|
170
|
+
_errors: [],
|
171
|
+
_subschemes: {},
|
172
|
+
"data.test_field" => {required: true, present: true, json_path: "$.data.test_field"}
|
173
|
+
}
|
174
|
+
}
|
175
|
+
})
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#all_flatten" do
|
180
|
+
it "generates N structures, where N = number of unique combinations of applied subschemas" do
|
181
|
+
expect(schema.structure.all_flatten).to eq({
|
182
|
+
subschema: {
|
183
|
+
_errors: [ArgumentError],
|
184
|
+
"data" => {type: :object, required: true, present: true, json_path: "$.data"},
|
185
|
+
"data.user_id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, alias: :user_id, json_path: "$.data.user_id"},
|
186
|
+
"data.name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true, description: "Example description", example: "John"},
|
187
|
+
"data.role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", mutates_schema: true},
|
188
|
+
"data.extra" => {type: :array, required: true, json_path: "$.data.extra[]"},
|
189
|
+
"data.extra.extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra"},
|
190
|
+
"data.test_field" => {required: true, present: true, json_path: "$.data.test_field"}
|
191
|
+
},
|
192
|
+
test_subschema: {
|
193
|
+
_errors: [ArgumentError],
|
194
|
+
"data" => {type: :object, required: true, present: true, json_path: "$.data"},
|
195
|
+
"data.user_id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, alias: :user_id, json_path: "$.data.user_id"},
|
196
|
+
"data.name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true, description: "Example description", example: "John"},
|
197
|
+
"data.role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", mutates_schema: true},
|
198
|
+
"data.extra" => {type: :array, required: true, json_path: "$.data.extra[]"},
|
199
|
+
"data.extra.extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra"},
|
200
|
+
"data.test1" => {required: true, present: true, json_path: "$.data.test1"}
|
201
|
+
}
|
202
|
+
})
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
describe "#all_nested" do
|
207
|
+
it "generates N structures, where N = number of unique combinations of applied subschemas" do
|
208
|
+
result = schema.structure.all_nested
|
209
|
+
expect(result[:subschema]).to eq({
|
210
|
+
_errors: [ArgumentError],
|
211
|
+
"data" => {
|
212
|
+
type: :object,
|
213
|
+
required: true,
|
214
|
+
present: true,
|
215
|
+
json_path: "$.data",
|
216
|
+
nested_name: "data",
|
217
|
+
structure: {
|
218
|
+
"role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", nested_name: "data.role", mutates_schema: true},
|
219
|
+
"test_field" => {required: true, present: true, json_path: "$.data.test_field", nested_name: "data.test_field"},
|
220
|
+
"user_id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, alias: :user_id, json_path: "$.data.user_id", nested_name: "data.user_id"},
|
221
|
+
"name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true, nested_name: "data.name", description: "Example description", example: "John"},
|
222
|
+
"extra" => {
|
223
|
+
type: :array, required: true, json_path: "$.data.extra[]", nested_name: "data.extra",
|
224
|
+
structure: {"extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra", nested_name: "data.extra.extra"}}
|
225
|
+
}
|
226
|
+
}
|
227
|
+
}
|
228
|
+
})
|
229
|
+
expect(result[:test_subschema]).to eq({
|
230
|
+
_errors: [ArgumentError],
|
231
|
+
"data" => {
|
232
|
+
type: :object,
|
233
|
+
required: true,
|
234
|
+
present: true,
|
235
|
+
json_path: "$.data",
|
236
|
+
nested_name: "data",
|
237
|
+
structure: {
|
238
|
+
"role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", nested_name: "data.role", mutates_schema: true},
|
239
|
+
"test1" => {required: true, present: true, json_path: "$.data.test1", nested_name: "data.test1"},
|
240
|
+
"user_id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, alias: :user_id, json_path: "$.data.user_id", nested_name: "data.user_id"},
|
241
|
+
"name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true, nested_name: "data.name", description: "Example description", example: "John"},
|
242
|
+
"extra" => {
|
243
|
+
type: :array, required: true, json_path: "$.data.extra[]", nested_name: "data.extra",
|
244
|
+
structure: {"extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra", nested_name: "data.extra.extra"}}}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
})
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|