paradocs 1.0.22 → 1.1.2

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.
@@ -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