paradocs 1.0.22

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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