paradocs 1.0.22
Sign up to get free protection for your applications and to get access to all the features.
- 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/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "paradocs"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
@@ -0,0 +1,123 @@
|
|
1
|
+
module Paradocs
|
2
|
+
class BasePolicy
|
3
|
+
def self.build(name, meth, &block)
|
4
|
+
klass = Class.new(self)
|
5
|
+
klass.public_send(meth, &block)
|
6
|
+
klass.policy_name = name
|
7
|
+
klass
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.message(&block)
|
11
|
+
@message_block = block if block_given?
|
12
|
+
@message_block
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.validate(&validate_block)
|
16
|
+
@validate_block = validate_block if block_given?
|
17
|
+
@validate_block
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.coerce(&coerce_block)
|
21
|
+
@coerce_block = coerce_block if block_given?
|
22
|
+
@coerce_block
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.eligible(&block)
|
26
|
+
@eligible_block = block if block_given?
|
27
|
+
@eligible_block
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.meta_data(&block)
|
31
|
+
@meta_data_block = block if block_given?
|
32
|
+
@meta_data_block
|
33
|
+
end
|
34
|
+
|
35
|
+
%w(error silent_error).each do |name|
|
36
|
+
getter = "#{name}s"
|
37
|
+
define_singleton_method(getter) do
|
38
|
+
parent_errors = superclass.respond_to?(getter) ? superclass.send(getter) : []
|
39
|
+
parent_errors | (instance_variable_get("@#{getter}") || instance_variable_set("@#{getter}", []))
|
40
|
+
end
|
41
|
+
|
42
|
+
define_singleton_method("register_#{name}") do |*exceptions| # TODO: spec
|
43
|
+
# [Exception, as: :helper_method] or [Exception1, Exception2....]
|
44
|
+
only_errors = []
|
45
|
+
exceptions.each_with_index do |ex, index|
|
46
|
+
if ex.is_a? Hash
|
47
|
+
exception = exceptions[index - 1]
|
48
|
+
define_method(ex[:as]) { exception }
|
49
|
+
next
|
50
|
+
end
|
51
|
+
next unless ex.is_a?(Class) || ex < StandardError
|
52
|
+
only_errors << ex
|
53
|
+
end
|
54
|
+
instance_variable_set("@#{getter}", ((self.public_send(getter) || []) + only_errors).uniq)
|
55
|
+
end
|
56
|
+
|
57
|
+
define_method(getter) do
|
58
|
+
instance_variable_set("@#{getter}", self.class.send("register_#{name}") || [])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.policy_name=(name)
|
63
|
+
@policy_name = name
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.policy_name
|
67
|
+
@policy_name || self.name.split("::").last.downcase.to_sym
|
68
|
+
end
|
69
|
+
|
70
|
+
attr_accessor :environment
|
71
|
+
def initialize(*args)
|
72
|
+
@init_params = args
|
73
|
+
end
|
74
|
+
|
75
|
+
def eligible?(value, key, payload)
|
76
|
+
args = (init_params + [value, key, payload])
|
77
|
+
(self.class.eligible || ->(*) { true }).call(*args)
|
78
|
+
end
|
79
|
+
|
80
|
+
def coerce(value, key, context)
|
81
|
+
(self.class.coerce || ->(v, *_) { v }).call(value, key, context)
|
82
|
+
end
|
83
|
+
|
84
|
+
def valid?(value, key, payload)
|
85
|
+
args = (init_params + [value, key, payload])
|
86
|
+
@message = self.class.message.call(*args) if self.class.message
|
87
|
+
validate(*args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def meta_data
|
91
|
+
return self.class.meta_data.call(*init_params) if self.class.meta_data
|
92
|
+
meta
|
93
|
+
end
|
94
|
+
|
95
|
+
def validate(*args)
|
96
|
+
(self.class.validate || ->(*) { true }).call(*args)
|
97
|
+
end
|
98
|
+
|
99
|
+
def policy_name
|
100
|
+
(self.class.policy_name || self.to_s.demodulize.underscore).to_sym
|
101
|
+
end
|
102
|
+
|
103
|
+
def message
|
104
|
+
@message ||= 'is invalid'
|
105
|
+
end
|
106
|
+
|
107
|
+
protected
|
108
|
+
|
109
|
+
def validate(*args)
|
110
|
+
(self.class.validate || ->(*args) { true }).call(*args)
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def meta
|
116
|
+
@meta = {self.class.policy_name => {errors: self.class.errors}}
|
117
|
+
end
|
118
|
+
|
119
|
+
def init_params
|
120
|
+
@init_params ||= [] # safe default if #initialize was overwritten
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Paradocs
|
2
|
+
class Top
|
3
|
+
attr_reader :errors
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@errors = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def add_error(key, msg)
|
10
|
+
errors[key] ||= []
|
11
|
+
errors[key] << msg
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Context
|
16
|
+
attr_reader :environment
|
17
|
+
def initialize(path=nil, top=Top.new, environment={}, subschemes={})
|
18
|
+
@top = top
|
19
|
+
@path = Array(path).compact
|
20
|
+
@environment = environment
|
21
|
+
@subschemes = subschemes
|
22
|
+
end
|
23
|
+
|
24
|
+
def subschema(subschema_name)
|
25
|
+
subschema = @subschemes[subschema_name]
|
26
|
+
return unless subschema
|
27
|
+
@subschemes.merge!(subschema.subschemes)
|
28
|
+
subschema
|
29
|
+
end
|
30
|
+
|
31
|
+
def errors
|
32
|
+
top.errors
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_error(msg)
|
36
|
+
top.add_error(string_path, msg)
|
37
|
+
end
|
38
|
+
|
39
|
+
def sub(key)
|
40
|
+
self.class.new(path + [key], top, environment, @subschemes)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
attr_reader :path, :top
|
45
|
+
|
46
|
+
def string_path
|
47
|
+
path.reduce(['$']) do |m, segment|
|
48
|
+
m << (segment.is_a?(Integer) ? "[#{segment}]" : ".#{segment}")
|
49
|
+
m
|
50
|
+
end.join
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "date"
|
2
|
+
|
3
|
+
module Paradocs
|
4
|
+
# type coercions
|
5
|
+
Paradocs.policy :integer do
|
6
|
+
coerce do |v, k, c|
|
7
|
+
v.to_i
|
8
|
+
end
|
9
|
+
|
10
|
+
meta_data do
|
11
|
+
{type: :integer}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
Paradocs.policy :number do
|
16
|
+
coerce do |v, k, c|
|
17
|
+
v.to_f
|
18
|
+
end
|
19
|
+
|
20
|
+
meta_data do
|
21
|
+
{type: :number}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Paradocs.policy :string do
|
26
|
+
coerce do |v, k, c|
|
27
|
+
v.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
meta_data do
|
31
|
+
{type: :string}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
Paradocs.policy :boolean do
|
36
|
+
coerce do |v, k, c|
|
37
|
+
!!v
|
38
|
+
end
|
39
|
+
|
40
|
+
meta_data do
|
41
|
+
{type: :boolean}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# type validations
|
46
|
+
Paradocs.policy :array do
|
47
|
+
message do |actual|
|
48
|
+
"expects an array, but got #{actual.inspect}"
|
49
|
+
end
|
50
|
+
|
51
|
+
validate do |value, key, payload|
|
52
|
+
!payload.key?(key) || value.is_a?(Array)
|
53
|
+
end
|
54
|
+
|
55
|
+
meta_data do
|
56
|
+
{type: :array}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
Paradocs.policy :object do
|
61
|
+
message do |actual|
|
62
|
+
"expects a hash, but got #{actual.inspect}"
|
63
|
+
end
|
64
|
+
|
65
|
+
validate do |value, key, payload|
|
66
|
+
!payload.key?(key) ||
|
67
|
+
value.respond_to?(:[]) &&
|
68
|
+
value.respond_to?(:key?)
|
69
|
+
end
|
70
|
+
|
71
|
+
meta_data do
|
72
|
+
{type: :object}
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
Paradocs.policy :split do
|
77
|
+
coerce do |v, k, c|
|
78
|
+
v.kind_of?(Array) ? v : v.to_s.split(/\s*,\s*/)
|
79
|
+
end
|
80
|
+
|
81
|
+
meta_data do
|
82
|
+
{type: :array}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Paradocs.policy :datetime do
|
87
|
+
coerce do |v, k, c|
|
88
|
+
DateTime.parse(v.to_s)
|
89
|
+
end
|
90
|
+
|
91
|
+
meta_data do
|
92
|
+
{type: :datetime}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/paradocs/dsl.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require "paradocs"
|
2
|
+
|
3
|
+
module Paradocs
|
4
|
+
module DSL
|
5
|
+
# Example
|
6
|
+
# class Foo
|
7
|
+
# include Paradocs::DSL
|
8
|
+
#
|
9
|
+
# schema do
|
10
|
+
# field(:title).type(:string).present
|
11
|
+
# field(:age).type(:integer).default(20)
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# attr_reader :params
|
15
|
+
#
|
16
|
+
# def initialize(input)
|
17
|
+
# @params = self.class.schema.resolve(input)
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# foo = Foo.new(title: "A title", nope: "hello")
|
22
|
+
#
|
23
|
+
# foo.params # => {title: "A title", age: 20}
|
24
|
+
#
|
25
|
+
|
26
|
+
def self.included(base)
|
27
|
+
base.extend(ClassMethods)
|
28
|
+
base.schemas = {Paradocs.config.default_schema_name => Paradocs::Schema.new}
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def schema=(sc)
|
33
|
+
@schemas[Paradocs.config.default_schema_name] = sc
|
34
|
+
end
|
35
|
+
|
36
|
+
def schemas=(sc)
|
37
|
+
@schemas = sc
|
38
|
+
end
|
39
|
+
|
40
|
+
def inherited(subclass)
|
41
|
+
subclass.schemas = @schemas.each_with_object({}) do |(key, sc), hash|
|
42
|
+
hash[key] = sc.merge(Paradocs::Schema.new)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def schema(*args, &block)
|
47
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
48
|
+
key = args.first.is_a?(Symbol) ? args.shift : Paradocs.config.default_schema_name
|
49
|
+
current_schema = @schemas.fetch(key) { Paradocs::Schema.new }
|
50
|
+
new_schema = if block_given? || options.any?
|
51
|
+
Paradocs::Schema.new(options, &block)
|
52
|
+
elsif args.first.is_a?(Paradocs::Schema)
|
53
|
+
args.first
|
54
|
+
end
|
55
|
+
|
56
|
+
return current_schema unless new_schema
|
57
|
+
|
58
|
+
@schemas[key] = current_schema ? current_schema.merge(new_schema) : new_schema
|
59
|
+
paradocs_after_define_schema(@schemas[key])
|
60
|
+
@schemas[key]
|
61
|
+
end
|
62
|
+
|
63
|
+
def paradocs_after_define_schema(sc)
|
64
|
+
# noop hook
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Paradocs
|
2
|
+
module Extensions
|
3
|
+
module Insides
|
4
|
+
def structure(ignore_transparent: true, root: "", &block)
|
5
|
+
flush!
|
6
|
+
fields.each_with_object({meta_keys[:errors] => [], meta_keys[:subschemes] => {}}) do |(_, field), obj|
|
7
|
+
meta, sc = collect_meta(field, root)
|
8
|
+
if sc
|
9
|
+
meta[:structure] = sc.structure(ignore_transparent: ignore_transparent, root: meta[:json_path], &block)
|
10
|
+
obj[meta_keys[:errors]] += meta[:structure].delete(meta_keys[:errors])
|
11
|
+
else
|
12
|
+
obj[meta_keys[:errors]] += field.possible_errors
|
13
|
+
end
|
14
|
+
obj[field.key] = meta unless ignore_transparent && field.transparent?
|
15
|
+
yield(field.key, meta) if block_given?
|
16
|
+
|
17
|
+
next unless field.mutates_schema?
|
18
|
+
subschemes.each do |name, subschema|
|
19
|
+
obj[meta_keys[:subschemes]][name] = subschema.structure(ignore_transparent: ignore_transparent, root: root, &block)
|
20
|
+
obj[meta_keys[:errors]] += obj[meta_keys[:subschemes]][name][meta_keys[:errors]]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def flatten_structure(ignore_transparent: true, root: "", &block)
|
26
|
+
flush!
|
27
|
+
fields.each_with_object({meta_keys[:errors] => [], meta_keys[:subschemes] => {}}) do |(_, field), obj|
|
28
|
+
meta, sc = collect_meta(field, root)
|
29
|
+
humanized_name = meta.delete(:nested_name)
|
30
|
+
obj[humanized_name] = meta unless ignore_transparent && field.transparent?
|
31
|
+
|
32
|
+
if sc
|
33
|
+
deep_result = sc.flatten_structure(ignore_transparent: ignore_transparent, root: meta[:json_path], &block)
|
34
|
+
obj[meta_keys[:errors]] += deep_result.delete(meta_keys[:errors])
|
35
|
+
obj[meta_keys[:subschemes]].merge!(deep_result.delete(meta_keys[:subschemes]))
|
36
|
+
obj.merge!(deep_result)
|
37
|
+
else
|
38
|
+
obj[meta_keys[:errors]] += field.possible_errors
|
39
|
+
end
|
40
|
+
yield(humanized_name, meta) if block_given?
|
41
|
+
next unless field.mutates_schema?
|
42
|
+
subschemes.each do |name, subschema|
|
43
|
+
obj[meta_keys[:subschemes]][name] ||= subschema.flatten_structure(ignore_transparent: ignore_transparent, root: root, &block)
|
44
|
+
obj[meta_keys[:errors]] += obj[meta_keys[:subschemes]][name][meta_keys[:errors]]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def walk(meta_key = nil, &visitor)
|
50
|
+
r = visit(meta_key, &visitor)
|
51
|
+
Results.new(r, {}, {})
|
52
|
+
end
|
53
|
+
|
54
|
+
def visit(meta_key = nil, &visitor)
|
55
|
+
fields.each_with_object({}) do |(_, field), m|
|
56
|
+
m[field.key] = field.visit(meta_key, &visitor)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def collect_meta(field, root)
|
63
|
+
json_path = root.empty? ? "$.#{field.key}" : "#{root}.#{field.key}"
|
64
|
+
meta = field.meta_data.merge(json_path: json_path)
|
65
|
+
sc = meta.delete(:schema)
|
66
|
+
meta[:mutates_schema] = true if meta.delete(:mutates_schema)
|
67
|
+
json_path << "[]" if meta[:type] == :array
|
68
|
+
meta[:nested_name] = json_path.gsub("[]", "")[2..-1]
|
69
|
+
[meta, sc]
|
70
|
+
end
|
71
|
+
|
72
|
+
def meta_keys
|
73
|
+
%i(errors subschemes).map! { |key| [key, "#{Paradocs.config.meta_prefix}#{key}".to_sym] }.to_h
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require "paradocs/field_dsl"
|
2
|
+
|
3
|
+
module Paradocs
|
4
|
+
class Field
|
5
|
+
include FieldDSL
|
6
|
+
|
7
|
+
attr_reader :key, :meta_data
|
8
|
+
Result = Struct.new(:eligible?, :value)
|
9
|
+
|
10
|
+
def initialize(key)
|
11
|
+
@key = key
|
12
|
+
@policies = []
|
13
|
+
@default_block = nil
|
14
|
+
@meta_data = {}
|
15
|
+
@policies = []
|
16
|
+
@mutation_block = nil
|
17
|
+
@expects_mutation = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def meta(hash = nil)
|
21
|
+
@meta_data = @meta_data.merge(hash) if hash.is_a?(Hash)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def possible_errors
|
26
|
+
meta_data.map { |_, v| v[:errors] if v.is_a?(Hash) }.flatten.compact
|
27
|
+
end
|
28
|
+
|
29
|
+
def default(value)
|
30
|
+
meta default: value
|
31
|
+
@default_block = (value.respond_to?(:call) ? value : ->(key, payload, context) { value })
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def mutates_schema!(&block)
|
36
|
+
@mutation_block ||= block if block_given?
|
37
|
+
@expects_mutation = @expects_mutation.nil? && true
|
38
|
+
meta mutates_schema: @mutation_block
|
39
|
+
@mutation_block
|
40
|
+
end
|
41
|
+
|
42
|
+
def mutates_schema?
|
43
|
+
!!@mutation_block
|
44
|
+
end
|
45
|
+
|
46
|
+
def expects_mutation?
|
47
|
+
mutates_schema? && @expects_mutation
|
48
|
+
end
|
49
|
+
|
50
|
+
def policy(key, *args)
|
51
|
+
pol = lookup(key, args)
|
52
|
+
|
53
|
+
meta pol.meta_data
|
54
|
+
policies << pol
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
alias_method :type, :policy
|
59
|
+
alias_method :rule, :policy
|
60
|
+
|
61
|
+
def schema(sc = nil, &block)
|
62
|
+
sc = (sc ? sc : Schema.new(&block))
|
63
|
+
meta schema: sc
|
64
|
+
policy sc.schema
|
65
|
+
end
|
66
|
+
|
67
|
+
def transparent?
|
68
|
+
!!meta_data[:transparent]
|
69
|
+
end
|
70
|
+
|
71
|
+
def visit(meta_key = nil, &visitor)
|
72
|
+
if sc = meta_data[:schema]
|
73
|
+
r = sc.visit(meta_key, &visitor)
|
74
|
+
(meta_data[:type] == :array) ? [r] : r
|
75
|
+
else
|
76
|
+
meta_key ? meta_data[meta_key] : yield(self)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def subschema_for_mutation(payload, env)
|
81
|
+
subschema_name = @mutation_block.call(payload[key], key, payload, env) if @mutation_block
|
82
|
+
@expects_mutation = false
|
83
|
+
subschema_name
|
84
|
+
end
|
85
|
+
|
86
|
+
def resolve(payload, context)
|
87
|
+
eligible = payload.key?(key)
|
88
|
+
value = payload[key] # might be nil
|
89
|
+
|
90
|
+
if !eligible && has_default?
|
91
|
+
eligible = true
|
92
|
+
value = default_block.call(key, payload, context)
|
93
|
+
payload[key] = value
|
94
|
+
end
|
95
|
+
policies.each do |policy|
|
96
|
+
# pass schema additional data to the each policy
|
97
|
+
policy.environment = context.environment if policy.respond_to?(:environment=)
|
98
|
+
if !policy.eligible?(value, key, payload)
|
99
|
+
eligible = false
|
100
|
+
if has_default?
|
101
|
+
eligible = true
|
102
|
+
value = default_block.call(key, payload, context)
|
103
|
+
end
|
104
|
+
break
|
105
|
+
else
|
106
|
+
value, valid = resolve_one(policy, value, payload, context)
|
107
|
+
|
108
|
+
unless valid
|
109
|
+
eligible = true # eligible, but has errors
|
110
|
+
break # only one error at a time
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
Result.new(eligible, value)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
attr_reader :policies, :default_block
|
120
|
+
|
121
|
+
def resolve_one(policy, value, payload, context)
|
122
|
+
begin
|
123
|
+
value = policy.coerce(value, key, context)
|
124
|
+
valid = policy.valid?(value, key, payload)
|
125
|
+
|
126
|
+
context.add_error(policy.message) unless valid
|
127
|
+
[value, valid]
|
128
|
+
rescue *(policy.try(:errors) || []) => e
|
129
|
+
# context.add_error e.message # NOTE: do we need it?
|
130
|
+
raise e
|
131
|
+
rescue *(policy.try(:silent_errors) || []) => e
|
132
|
+
context.add_error e.message
|
133
|
+
rescue StandardError => e
|
134
|
+
raise e if policy.is_a? Paradocs::Schema # from the inner level, just reraise
|
135
|
+
raise ConfigurationError.new("#{e.class} should be registered in the policy") if Paradocs.config.explicit_errors
|
136
|
+
context.add_error policy.message unless Paradocs.config.explicit_errors
|
137
|
+
[value, false]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def has_default?
|
142
|
+
!!default_block
|
143
|
+
end
|
144
|
+
|
145
|
+
def lookup(key, args)
|
146
|
+
obj = key.is_a?(Symbol) ? Paradocs.registry.policies[key] : key
|
147
|
+
|
148
|
+
raise ConfigurationError, "No policies defined for #{key.inspect}" unless obj
|
149
|
+
obj.respond_to?(:new) ? obj.new(*args) : obj
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Paradocs
|
2
|
+
# Field DSL
|
3
|
+
# host instance must implement:
|
4
|
+
# #meta(options Hash)
|
5
|
+
# #policy(key Symbol) self
|
6
|
+
#
|
7
|
+
module FieldDSL
|
8
|
+
def required
|
9
|
+
policy :required
|
10
|
+
end
|
11
|
+
|
12
|
+
def present
|
13
|
+
required.policy :present
|
14
|
+
end
|
15
|
+
|
16
|
+
def declared
|
17
|
+
policy :declared
|
18
|
+
end
|
19
|
+
|
20
|
+
def options(opts)
|
21
|
+
policy :options, opts
|
22
|
+
end
|
23
|
+
|
24
|
+
def whitelisted
|
25
|
+
policy :whitelisted
|
26
|
+
end
|
27
|
+
|
28
|
+
def transparent
|
29
|
+
meta transparent: true
|
30
|
+
end
|
31
|
+
|
32
|
+
def length(opts)
|
33
|
+
policy :length, opts
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|