paradocs 1.0.22 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,12 +32,14 @@ module Paradocs
32
32
 
33
33
  def resolve(payload, schema, context)
34
34
  filtered_payload = {}
35
+ coercion_block = Paradocs.config.whitelist_coercion
36
+ coercion_block = coercion_block.is_a?(Proc) && coercion_block
35
37
  payload.dup.each do |key, value|
36
38
  key = key.to_sym
37
39
  schema = Schema.new if schema.nil?
38
40
  schema.send(:flush!)
39
41
  schema.send(:invoke_subschemes!, payload, context)
40
-
42
+ meta = get_meta_data(schema, key)
41
43
  if value.is_a?(Hash)
42
44
  field_schema = find_schema_by(schema, key)
43
45
  value = resolve(value, field_schema, context)
@@ -47,13 +49,14 @@ module Paradocs
47
49
  field_schema = find_schema_by(schema, key)
48
50
  resolve(v, field_schema, context)
49
51
  else
50
- v = FILTERED unless whitelisted?(schema, key)
52
+ v = FILTERED unless whitelisted?(meta, key)
51
53
  v
52
54
  end
53
55
  end
54
56
  else
55
- value = if whitelisted?(schema, key)
56
- value
57
+
58
+ value = if whitelisted?(meta, key)
59
+ coercion_block ? coercion_block.call(value, meta) : value
57
60
  elsif value.nil? || value.try(:blank?) || value.try(:empty?)
58
61
  !!value == value ? value : EMPTY
59
62
  else
@@ -61,7 +64,6 @@ module Paradocs
61
64
  end
62
65
  value
63
66
  end
64
-
65
67
  filtered_payload[key] = value
66
68
  end
67
69
 
@@ -71,13 +73,12 @@ module Paradocs
71
73
  private
72
74
 
73
75
  def find_schema_by(schema, key)
74
- meta_data = get_meta_data(schema, key)
75
- meta_data[:schema]
76
+ meta = get_meta_data(schema, key)
77
+ meta[:schema]
76
78
  end
77
79
 
78
- def whitelisted?(schema, key)
79
- meta_data = get_meta_data(schema, key)
80
- meta_data[:whitelisted] || Paradocs.config.whitelisted_keys.include?(key)
80
+ def whitelisted?(meta, key)
81
+ meta[:whitelisted] || Paradocs.config.whitelisted_keys.include?(key)
81
82
  end
82
83
 
83
84
  def get_meta_data(schema, key)
@@ -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
+
@@ -6,11 +6,11 @@ require 'paradocs/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "paradocs"
8
8
  spec.version = Paradocs::VERSION
9
- spec.authors = ["Ismael Celis", "Maxim Tkachenko"]
10
- spec.email = ["ismaelct@gmail.com", "tkachenko.maxim.w@gmail.com"]
9
+ spec.authors = ["Maxim Tkachenko", "Ismael Celis"]
10
+ spec.email = ["tkachenko.maxim.w@gmail.com", "ismaelct@gmail.com"]
11
11
  spec.description = %q{Flexible DSL for declaring allowed parameters focused on DRY validation that gives you opportunity to generate API documentation on-the-fly.}
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://github.com/mtkachenk0/paradocs"
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", "~> 0"
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
@@ -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)
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", "barfield" => 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, "barfield" => 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,244 @@
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)
22
+ field(:name).type(:string).meta(label: "very important staff")
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[:id]).to eq({
68
+ type: :integer,
69
+ required: true,
70
+ present: true,
71
+ policy_with_error: {errors: [ArgumentError]},
72
+ json_path: "$.data.id",
73
+ nested_name: "data.id"
74
+ })
75
+ expect(data_structure[:name]).to eq({
76
+ type: :string,
77
+ label: "very important staff",
78
+ mutates_schema: true,
79
+ block_works: true,
80
+ json_path: "$.data.name",
81
+ nested_name: "data.name"
82
+ })
83
+ expect(data_structure[:role]).to eq({
84
+ type: :string,
85
+ options: ["admin", "user"],
86
+ default: "user",
87
+ mutates_schema: true,
88
+ block_works: true,
89
+ json_path: "$.data.role",
90
+ nested_name: "data.role"
91
+ })
92
+ expect(data_structure[:extra]).to eq({
93
+ type: :array,
94
+ required: true,
95
+ block_works: true,
96
+ json_path: "$.data.extra[]",
97
+ nested_name: "data.extra",
98
+ structure: {
99
+ extra: {
100
+ default: false,
101
+ block_works: true,
102
+ json_path: "$.data.extra[].extra",
103
+ nested_name: "data.extra.extra",
104
+ policy_with_silent_error: {errors: []}
105
+ },
106
+ _subschemes: {}
107
+ }
108
+ })
109
+ end
110
+ end
111
+
112
+ describe "#flatten" do
113
+ it "generates flatten data for documentation generation" do
114
+ expect(schema.structure.flatten { |key, meta| meta[:block_works] = true if key.split(".").size == 1 }).to eq({
115
+ "data" => {
116
+ type: :object,
117
+ required: true,
118
+ present: true,
119
+ block_works: true,
120
+ json_path: "$.data"
121
+ },
122
+ "data.extra" => {
123
+ type: :array,
124
+ required: true,
125
+ json_path: "$.data.extra[]"
126
+ },
127
+ "data.extra.extra" => {
128
+ default: false,
129
+ json_path: "$.data.extra[].extra",
130
+ policy_with_silent_error: {errors: []}
131
+ },
132
+ "data.id" => {
133
+ type: :integer,
134
+ required: true,
135
+ present: true,
136
+ json_path: "$.data.id",
137
+ policy_with_error: {errors: [ArgumentError]}
138
+ },
139
+ "data.name" => {
140
+ type: :string,
141
+ json_path: "$.data.name",
142
+ label: "very important staff",
143
+ mutates_schema: true
144
+ },
145
+ "data.role" => {
146
+ type: :string,
147
+ options: ["admin", "user"],
148
+ default: "user",
149
+ json_path: "$.data.role",
150
+ mutates_schema: true
151
+ },
152
+ _errors: [ArgumentError],
153
+ _subschemes: {
154
+ test_subschema: {
155
+ _errors: [],
156
+ _subschemes: {},
157
+ "data.test1"=>{
158
+ required: true,
159
+ present: true,
160
+ json_path: "$.data.test1"
161
+ }
162
+ },
163
+ subschema: {
164
+ _errors: [],
165
+ _subschemes: {},
166
+ "data.test_field" => {required: true, present: true, json_path: "$.data.test_field"}
167
+ }
168
+ }
169
+ })
170
+ end
171
+ end
172
+
173
+ describe "#all_flatten" do
174
+ it "generates N structures, where N = number of unique combinations of applied subschemas" do
175
+ expect(schema.structure.all_flatten).to eq({
176
+ subschema: {
177
+ _errors: [ArgumentError],
178
+ "data" => {type: :object, required: true, present: true, json_path: "$.data"},
179
+ "data.id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, json_path: "$.data.id"},
180
+ "data.name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true},
181
+ "data.role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", mutates_schema: true},
182
+ "data.extra" => {type: :array, required: true, json_path: "$.data.extra[]"},
183
+ "data.extra.extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra"},
184
+ "data.test_field" => {required: true, present: true, json_path: "$.data.test_field"}
185
+ },
186
+ test_subschema: {
187
+ _errors: [ArgumentError],
188
+ "data" => {type: :object, required: true, present: true, json_path: "$.data"},
189
+ "data.id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, json_path: "$.data.id"},
190
+ "data.name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true},
191
+ "data.role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", mutates_schema: true},
192
+ "data.extra" => {type: :array, required: true, json_path: "$.data.extra[]"},
193
+ "data.extra.extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra"},
194
+ "data.test1" => {required: true, present: true, json_path: "$.data.test1"}
195
+ }
196
+ })
197
+ end
198
+ end
199
+
200
+ describe "#all_nested" do
201
+ it "generates N structures, where N = number of unique combinations of applied subschemas" do
202
+ result = schema.structure.all_nested
203
+ expect(result[:subschema]).to eq({
204
+ _errors: [ArgumentError],
205
+ "data" => {
206
+ type: :object,
207
+ required: true,
208
+ present: true,
209
+ json_path: "$.data",
210
+ nested_name: "data",
211
+ structure: {
212
+ "role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", nested_name: "data.role", mutates_schema: true},
213
+ "test_field" => {required: true, present: true, json_path: "$.data.test_field", nested_name: "data.test_field"},
214
+ "id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, json_path: "$.data.id", nested_name: "data.id"},
215
+ "name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true, nested_name: "data.name"},
216
+ "extra" => {
217
+ type: :array, required: true, json_path: "$.data.extra[]", nested_name: "data.extra",
218
+ structure: {"extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra", nested_name: "data.extra.extra"}}
219
+ }
220
+ }
221
+ }
222
+ })
223
+ expect(result[:test_subschema]).to eq({
224
+ _errors: [ArgumentError],
225
+ "data" => {
226
+ type: :object,
227
+ required: true,
228
+ present: true,
229
+ json_path: "$.data",
230
+ nested_name: "data",
231
+ structure: {
232
+ "role" => {type: :string, options: ["admin", "user"], default: "user", json_path: "$.data.role", nested_name: "data.role", mutates_schema: true},
233
+ "test1" => {required: true, present: true, json_path: "$.data.test1", nested_name: "data.test1"},
234
+ "id" => {type: :integer, required: true, present: true, policy_with_error: {errors: [ArgumentError]}, json_path: "$.data.id", nested_name: "data.id"},
235
+ "name" => {type: :string, label: "very important staff", json_path: "$.data.name", mutates_schema: true, nested_name: "data.name"},
236
+ "extra" => {
237
+ type: :array, required: true, json_path: "$.data.extra[]", nested_name: "data.extra",
238
+ structure: {"extra" => {default: false, policy_with_silent_error: {errors: []}, json_path: "$.data.extra[].extra", nested_name: "data.extra.extra"}}}
239
+ }
240
+ }
241
+ })
242
+ end
243
+ end
244
+ end
@@ -32,7 +32,7 @@ describe Paradocs::Schema do
32
32
 
33
33
  describe "#structure" do
34
34
  it "represents data structure and meta data" do
35
- sc = subject.structure
35
+ sc = subject.structure.nested
36
36
  expect(sc[:title][:present]).to be true
37
37
  expect(sc[:title][:type]).to eq :string
38
38
  expect(sc[:price][:type]).to eq :integer
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  require 'paradocs/struct'
3
3
 
4
4
  describe Paradocs::Struct do
5
- it "works" do
5
+ it 'works' do
6
6
  friend_class = Class.new do
7
7
  include Paradocs::Struct
8
8
 
@@ -53,7 +53,7 @@ describe Paradocs::Struct do
53
53
  expect(invalid_instance.friends[1].errors['$.name']).not_to be_nil
54
54
  end
55
55
 
56
- it "is inmutable by default" do
56
+ it 'is inmutable by default' do
57
57
  klass = Class.new do
58
58
  include Paradocs::Struct
59
59
 
@@ -66,7 +66,7 @@ describe Paradocs::Struct do
66
66
 
67
67
  instance = klass.new
68
68
  expect {
69
- instance.title = "foo"
69
+ instance.title = 'foo'
70
70
  }.to raise_error NoMethodError
71
71
 
72
72
  expect {
@@ -74,7 +74,7 @@ describe Paradocs::Struct do
74
74
  }.to raise_error RuntimeError
75
75
  end
76
76
 
77
- it "works with anonymous nested schemas" do
77
+ it 'works with anonymous nested schemas' do
78
78
  klass = Class.new do
79
79
  include Paradocs::Struct
80
80
 
@@ -125,7 +125,7 @@ describe Paradocs::Struct do
125
125
  expect(user.friends.first.salutation).to eq 'my age is 43'
126
126
  end
127
127
 
128
- it "wraps regular schemas in structs" do
128
+ it 'wraps regular schemas in structs' do
129
129
  friend_schema = Paradocs::Schema.new do
130
130
  field(:name)
131
131
  end
@@ -147,7 +147,7 @@ describe Paradocs::Struct do
147
147
  expect(instance.friends.first.name).to eq 'Ismael'
148
148
  end
149
149
 
150
- it "#to_h" do
150
+ it '#to_h' do
151
151
  klass = Class.new do
152
152
  include Paradocs::Struct
153
153
 
@@ -185,7 +185,7 @@ describe Paradocs::Struct do
185
185
  expect(new_instance.to_h[:title]).to eq 'foo'
186
186
  end
187
187
 
188
- it "works with inheritance" do
188
+ it 'works with inheritance' do
189
189
  klass = Class.new do
190
190
  include Paradocs::Struct
191
191
 
@@ -218,7 +218,7 @@ describe Paradocs::Struct do
218
218
  expect(instance.friends.size).to eq 2
219
219
  end
220
220
 
221
- it "implements deep struct equality" do
221
+ it 'implements deep struct equality' do
222
222
  klass = Class.new do
223
223
  include Paradocs::Struct
224
224
 
@@ -268,7 +268,7 @@ describe Paradocs::Struct do
268
268
  expect(s1 == s4).to be false
269
269
  end
270
270
 
271
- it "#merge returns a new instance" do
271
+ it '#merge returns a new instance' do
272
272
  klass = Class.new do
273
273
  include Paradocs::Struct
274
274
 
@@ -301,6 +301,20 @@ describe Paradocs::Struct do
301
301
  expect(copy.friends.first.name).to eq 'jane'
302
302
  end
303
303
 
304
+ it 'passes the environment to the schema' do
305
+ klass = Class.new do
306
+ include Paradocs::Struct
307
+
308
+ schema do
309
+ field(:age).type(:integer)
310
+ end
311
+ end
312
+
313
+ new_instance = klass.new({}, { key: :value })
314
+
315
+ expect(new_instance.send(:_results).environment).to eq({ key: :value })
316
+ end
317
+
304
318
  describe '.new!' do
305
319
  it 'raises a useful exception if invalid data' do
306
320
  klass = Class.new do
@@ -320,5 +334,19 @@ describe Paradocs::Struct do
320
334
  valid = klass.new!(title: 'foo')
321
335
  expect(valid.title).to eq 'foo'
322
336
  end
337
+
338
+ it 'passes the environment to the schema' do
339
+ klass = Class.new do
340
+ include Paradocs::Struct
341
+
342
+ schema do
343
+ field(:title).type(:string).present
344
+ end
345
+ end
346
+
347
+ new_instance = klass.new!({ title: 'test' }, { key: :value })
348
+
349
+ expect(new_instance.send(:_results).environment).to eq({ key: :value })
350
+ end
323
351
  end
324
352
  end