paradocs 1.0.22
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 +7 -0
- data/.github/workflows/spec.yml +25 -0
- data/.gitignore +19 -0
- data/.gitlab-ci.yml +20 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/README.md +1078 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/lib/paradocs/base_policy.rb +123 -0
- data/lib/paradocs/context.rb +54 -0
- data/lib/paradocs/default_types.rb +95 -0
- data/lib/paradocs/dsl.rb +68 -0
- data/lib/paradocs/extensions/insides.rb +77 -0
- data/lib/paradocs/field.rb +152 -0
- data/lib/paradocs/field_dsl.rb +36 -0
- data/lib/paradocs/policies.rb +170 -0
- data/lib/paradocs/registry.rb +42 -0
- data/lib/paradocs/results.rb +13 -0
- data/lib/paradocs/schema.rb +214 -0
- data/lib/paradocs/struct.rb +102 -0
- data/lib/paradocs/support.rb +47 -0
- data/lib/paradocs/version.rb +3 -0
- data/lib/paradocs/whitelist.rb +91 -0
- data/lib/paradocs.rb +36 -0
- data/paradocs.gemspec +25 -0
- data/spec/custom_block_validator_spec.rb +88 -0
- data/spec/custom_validator.rb +61 -0
- data/spec/dsl_spec.rb +175 -0
- data/spec/expand_spec.rb +29 -0
- data/spec/field_spec.rb +416 -0
- data/spec/helpers.rb +18 -0
- data/spec/policies_spec.rb +159 -0
- data/spec/schema_spec.rb +299 -0
- data/spec/schema_structures_spec.rb +169 -0
- data/spec/schema_walk_spec.rb +42 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/struct_spec.rb +324 -0
- data/spec/subschema_spec.rb +178 -0
- data/spec/validators_spec.rb +86 -0
- data/spec/whitelist_spec.rb +97 -0
- metadata +162 -0
data/lib/paradocs.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require "paradocs/version"
|
2
|
+
require "paradocs/support"
|
3
|
+
require "paradocs/registry"
|
4
|
+
require "paradocs/field"
|
5
|
+
require "paradocs/results"
|
6
|
+
require "paradocs/schema"
|
7
|
+
require "paradocs/context"
|
8
|
+
require "paradocs/base_policy"
|
9
|
+
require 'ostruct'
|
10
|
+
|
11
|
+
module Paradocs
|
12
|
+
def self.registry
|
13
|
+
@registry ||= Registry.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.policy(name, plcy = nil, &block)
|
17
|
+
registry.policy name, plcy, &block
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.config
|
21
|
+
@config ||= OpenStruct.new(
|
22
|
+
explicit_errors: false,
|
23
|
+
whitelisted_keys: [],
|
24
|
+
default_schema_name: :schema,
|
25
|
+
meta_prefix: "_"
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.configure
|
30
|
+
yield self.config if block_given?
|
31
|
+
self.config
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'paradocs/default_types'
|
36
|
+
require 'paradocs/policies'
|
data/paradocs.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'paradocs/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "paradocs"
|
8
|
+
spec.version = Paradocs::VERSION
|
9
|
+
spec.authors = ["Ismael Celis", "Maxim Tkachenko"]
|
10
|
+
spec.email = ["ismaelct@gmail.com", "tkachenko.maxim.w@gmail.com"]
|
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
|
+
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"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 2.1"
|
22
|
+
spec.add_development_dependency "rake", "~> 0"
|
23
|
+
spec.add_development_dependency "rspec", '3.4.0'
|
24
|
+
spec.add_development_dependency "pry", "~> 0"
|
25
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'custom block validator' do
|
4
|
+
Paradocs.policy :validate_if do
|
5
|
+
eligible do |options, value, key, payload|
|
6
|
+
options.all? do |key, value|
|
7
|
+
payload[key] == value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'works if I just define an :eligible block' do
|
13
|
+
schema = Paradocs::Schema.new do
|
14
|
+
field(:name).policy(:validate_if, age: 40).present
|
15
|
+
field(:age).type(:integer)
|
16
|
+
end
|
17
|
+
|
18
|
+
expect(schema.resolve(age: 30).errors.any?).to be false
|
19
|
+
expect(schema.resolve(age: 40).errors.any?).to be true # name is missing
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
expect(Paradocs.registry.policies.values.map(&:policy_name)).to all(be)
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "error handling" do
|
27
|
+
Paradocs.policy :strict_validation do
|
28
|
+
register_error ArgumentError
|
29
|
+
register_silent_error RuntimeError
|
30
|
+
|
31
|
+
validate do |value, key, payload|
|
32
|
+
raise ArgumentError.new("test") if value > 50
|
33
|
+
raise RuntimeError.new("value should not exceed 30") if value > 30
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
coerce do |value, key, context|
|
38
|
+
value / 0 if value > 100
|
39
|
+
value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:schema) do
|
44
|
+
Paradocs::Schema.new do
|
45
|
+
field(:age).type(:integer).policy(:strict_validation)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "with disabled explicit errors" do
|
50
|
+
it "works fine if value is valid" do
|
51
|
+
expect(schema.resolve(age: 20).errors.any?).to be false
|
52
|
+
end
|
53
|
+
|
54
|
+
it "catches silent errors and uses the error.message as validation failure message" do
|
55
|
+
expect(schema.resolve(age: 31).errors).to eq({"$.age" => ["value should not exceed 30"]})
|
56
|
+
end
|
57
|
+
|
58
|
+
it "raises the very registered error to the highest level" do
|
59
|
+
expect { schema.resolve(age: 51) }.to raise_error(ArgumentError).with_message("test")
|
60
|
+
end
|
61
|
+
|
62
|
+
it "catches unregistered error and uses the policy.message as validation failure message" do
|
63
|
+
expect(schema.resolve(age: 101).errors).to eq({"$.age" => ["is invalid"]})
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "with enabled explicit errors" do
|
68
|
+
before { allow(Paradocs.config).to receive(:explicit_errors) { true } }
|
69
|
+
|
70
|
+
it "works fine if value is valid" do
|
71
|
+
expect(schema.resolve(age: 20).errors.any?).to be false
|
72
|
+
end
|
73
|
+
|
74
|
+
it "catches silent errors and uses the error.message as validation failure message" do
|
75
|
+
expect(schema.resolve(age: 31).errors).to eq({"$.age" => ["value should not exceed 30"]})
|
76
|
+
end
|
77
|
+
|
78
|
+
it "raises the very registered error to the highest level" do
|
79
|
+
expect { schema.resolve(age: 51) }.to raise_error(ArgumentError).with_message("test")
|
80
|
+
end
|
81
|
+
|
82
|
+
it "catches unregistered error and raises Configuration error" do
|
83
|
+
expect { schema.resolve(age: 101).errors }.to raise_error(Paradocs::ConfigurationError)
|
84
|
+
.with_message("ZeroDivisionError should be registered in the policy")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "custom validator" do
|
4
|
+
let(:validator) do
|
5
|
+
class PresentIf < Paradocs::BasePolicy
|
6
|
+
def initialize(condition)
|
7
|
+
@condition = @condition
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate(value, key, payload)
|
11
|
+
value.present? && key.present?
|
12
|
+
end
|
13
|
+
|
14
|
+
def eligible?(value, key, payload)
|
15
|
+
condition.call(value, key, payload)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
PresentIf
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "registration" do
|
22
|
+
it "doesn't raise error if validator is built properly" do
|
23
|
+
expect { Paradocs.policy :present_if, validator }.to_not raise_error
|
24
|
+
expect(Paradocs.registry.policies[:present_if]).to eq(validator)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "raises ConfigurationError if validator class doesn't respond to required methods" do
|
28
|
+
expect { Paradocs.policy :bad_validator, Class.new }.to raise_error(Paradocs::ConfigurationError)
|
29
|
+
.with_message(/Policy .* should respond to \[:valid\?, :coerce, :eligible\?, :meta_data, :policy_name\]/)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "raises ConfigrationError if child validator class overrides #valid? method" do
|
33
|
+
expect do
|
34
|
+
Paradocs.policy(
|
35
|
+
:bad_validator,
|
36
|
+
Class.new(Paradocs::BasePolicy) do
|
37
|
+
define_method(:valid?) { true }
|
38
|
+
end
|
39
|
+
)
|
40
|
+
end.to raise_error(Paradocs::ConfigurationError)
|
41
|
+
.with_message(/Overriding #valid\? in .* is forbidden\. Override #validate instead/)
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with enabled explicit_errors" do
|
45
|
+
before { allow(Paradocs.config).to receive(:explicit_errors) { true } }
|
46
|
+
|
47
|
+
it "raises ConfigurationError if custom validator doesn't implement .errors method" do
|
48
|
+
validator = Class.new do
|
49
|
+
def valid?; end
|
50
|
+
def coerce; end
|
51
|
+
def eligible?; end
|
52
|
+
def meta_data; end
|
53
|
+
def policy_name; end
|
54
|
+
end
|
55
|
+
|
56
|
+
expect { Paradocs.policy :malformed_validator, validator }.to raise_error(Paradocs::ConfigurationError)
|
57
|
+
.with_message(/Policy .* should respond to \.errors method/)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/dsl_spec.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require "paradocs/dsl"
|
3
|
+
|
4
|
+
describe "classes including DSL module" do
|
5
|
+
class Parent
|
6
|
+
include Paradocs::DSL
|
7
|
+
|
8
|
+
schema :extras, search_type: :string do |opts|
|
9
|
+
field(:search).policy(opts[:search_type])
|
10
|
+
end
|
11
|
+
|
12
|
+
schema(age_type: :integer) do |opts|
|
13
|
+
field(:title).policy(:string)
|
14
|
+
field(:age).policy(opts[:age_type])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Child < Parent
|
19
|
+
schema :extras do
|
20
|
+
field(:query).type(:string)
|
21
|
+
end
|
22
|
+
|
23
|
+
schema(age_type: :string) do
|
24
|
+
field(:description).policy(:string)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class GrandChild < Child
|
29
|
+
schema :extras, search_type: :integer
|
30
|
+
|
31
|
+
schema(age_type: :integer)
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#schema" do
|
35
|
+
let(:input) {
|
36
|
+
{
|
37
|
+
title: "A title",
|
38
|
+
age: 38,
|
39
|
+
description: "A description"
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
it "merges parent's schema into child's" do
|
44
|
+
parent_output = Parent.schema.resolve(input).output
|
45
|
+
child_output = Child.schema.resolve(input).output
|
46
|
+
|
47
|
+
expect(parent_output.keys).to match_array([:title, :age])
|
48
|
+
expect(parent_output[:title]).to eq "A title"
|
49
|
+
expect(parent_output[:age]).to eq 38
|
50
|
+
|
51
|
+
expect(child_output.keys).to match_array([:title, :age, :description])
|
52
|
+
expect(child_output[:title]).to eq "A title"
|
53
|
+
expect(child_output[:age]).to eq "38"
|
54
|
+
expect(child_output[:description]).to eq "A description"
|
55
|
+
|
56
|
+
# named schema
|
57
|
+
parent_output = Parent.schema(:extras).resolve(search: 10, query: 'foo').output
|
58
|
+
child_output = Child.schema(:extras).resolve(search: 10, query: 'foo').output
|
59
|
+
|
60
|
+
expect(parent_output.keys).to match_array([:search])
|
61
|
+
expect(parent_output[:search]).to eq "10"
|
62
|
+
expect(child_output.keys).to match_array([:search, :query])
|
63
|
+
expect(child_output[:search]).to eq "10"
|
64
|
+
expect(child_output[:query]).to eq "foo"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "inherits options" do
|
68
|
+
grand_child_output = GrandChild.schema.resolve(input).output
|
69
|
+
|
70
|
+
expect(grand_child_output.keys).to match_array([:title, :age, :description])
|
71
|
+
expect(grand_child_output[:title]).to eq "A title"
|
72
|
+
expect(grand_child_output[:age]).to eq 38
|
73
|
+
expect(grand_child_output[:description]).to eq "A description"
|
74
|
+
|
75
|
+
# named schema
|
76
|
+
grand_child_output = GrandChild.schema(:extras).resolve(search: "100", query: "bar").output
|
77
|
+
expect(grand_child_output.keys).to match_array([:search, :query])
|
78
|
+
expect(grand_child_output[:search]).to eq 100
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "inheriting schema policy" do
|
83
|
+
let!(:a) {
|
84
|
+
Class.new do
|
85
|
+
include Paradocs::DSL
|
86
|
+
|
87
|
+
schema.policy(:present) do
|
88
|
+
field(:title).policy(:string)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
}
|
92
|
+
|
93
|
+
let!(:b) {
|
94
|
+
Class.new(a)
|
95
|
+
}
|
96
|
+
|
97
|
+
it "inherits policy" do
|
98
|
+
results = a.schema.resolve({})
|
99
|
+
expect(results.errors["$.title"]).not_to be_empty
|
100
|
+
|
101
|
+
results = b.schema.resolve({})
|
102
|
+
expect(results.errors["$.title"]).not_to be_empty
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "overriding schema policy" do
|
107
|
+
let!(:a) {
|
108
|
+
Class.new do
|
109
|
+
include Paradocs::DSL
|
110
|
+
|
111
|
+
schema.policy(:present) do
|
112
|
+
field(:title).policy(:string)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
}
|
116
|
+
|
117
|
+
let!(:b) {
|
118
|
+
Class.new(a) do
|
119
|
+
schema.policy(:declared)
|
120
|
+
end
|
121
|
+
}
|
122
|
+
|
123
|
+
it "does not mutate parent schema" do
|
124
|
+
results = a.schema.resolve({})
|
125
|
+
expect(results.errors).not_to be_empty
|
126
|
+
|
127
|
+
results = b.schema.resolve({})
|
128
|
+
expect(results.errors).to be_empty
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "removes fields defined in the parent class" do
|
133
|
+
let!(:a) {
|
134
|
+
Class.new do
|
135
|
+
include Paradocs::DSL
|
136
|
+
|
137
|
+
schema do
|
138
|
+
field(:title).policy(:string)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
}
|
142
|
+
|
143
|
+
let!(:b) {
|
144
|
+
Class.new(a) do
|
145
|
+
schema.ignore(:title) do
|
146
|
+
field(:age)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
}
|
150
|
+
|
151
|
+
it "removes inherited field from child class" do
|
152
|
+
results = a.schema.resolve({title: "Mr.", age: 20})
|
153
|
+
expect(results.output).to eq({title: "Mr."})
|
154
|
+
|
155
|
+
results = b.schema.resolve({title: "Mr.", age: 20})
|
156
|
+
expect(results.output).to eq({age: 20})
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
describe "passing other schema or form in definition" do
|
161
|
+
it 'applies schema' do
|
162
|
+
a = Paradocs::Schema.new do
|
163
|
+
field(:name).policy(:string)
|
164
|
+
field(:age).policy(:integer).default(40)
|
165
|
+
end
|
166
|
+
b = Class.new do
|
167
|
+
include Paradocs::DSL
|
168
|
+
schema a
|
169
|
+
end
|
170
|
+
|
171
|
+
results = b.schema.resolve(name: 'Neil')
|
172
|
+
expect(results.output).to eq({name: 'Neil', age: 40})
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
data/spec/expand_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Paradocs::Schema do
|
4
|
+
it "expands fields dynamically" do
|
5
|
+
schema = described_class.new do
|
6
|
+
field(:title).type(:string).present
|
7
|
+
expand(/^attr_(.+)/) do |match|
|
8
|
+
field(match[1]).type(:string)
|
9
|
+
end
|
10
|
+
expand(/^validate_(.+)/) do |match|
|
11
|
+
field(match[1]).type(:string).present
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
out = schema.resolve({
|
16
|
+
title: "foo",
|
17
|
+
:"attr_Attribute 1" => "attr 1",
|
18
|
+
:"attr_Attribute 2" => "attr 2",
|
19
|
+
:"validate_valid_attr" => "valid",
|
20
|
+
:"validate_invalid_attr" => "",
|
21
|
+
})
|
22
|
+
|
23
|
+
expect(out.output[:title]).to eq 'foo'
|
24
|
+
expect(out.output[:"Attribute 1"]).to eq 'attr 1'
|
25
|
+
expect(out.output[:"Attribute 2"]).to eq 'attr 2'
|
26
|
+
|
27
|
+
expect(out.errors['$.invalid_attr']).to eq ['is required and value must be present']
|
28
|
+
end
|
29
|
+
end
|