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