parametric 0.0.1 → 0.2.12
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 +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +2 -1
- data/Gemfile +4 -0
- data/README.md +1017 -96
- data/bench/struct_bench.rb +53 -0
- data/bin/console +14 -0
- data/lib/parametric/block_validator.rb +66 -0
- data/lib/parametric/context.rb +49 -0
- data/lib/parametric/default_types.rb +97 -0
- data/lib/parametric/dsl.rb +70 -0
- data/lib/parametric/field.rb +113 -0
- data/lib/parametric/field_dsl.rb +26 -0
- data/lib/parametric/policies.rb +111 -38
- data/lib/parametric/registry.rb +23 -0
- data/lib/parametric/results.rb +15 -0
- data/lib/parametric/schema.rb +228 -0
- data/lib/parametric/struct.rb +108 -0
- data/lib/parametric/version.rb +3 -1
- data/lib/parametric.rb +18 -5
- data/parametric.gemspec +2 -3
- data/spec/custom_block_validator_spec.rb +21 -0
- data/spec/dsl_spec.rb +176 -0
- data/spec/expand_spec.rb +29 -0
- data/spec/field_spec.rb +430 -0
- data/spec/policies_spec.rb +72 -0
- data/spec/schema_lifecycle_hooks_spec.rb +133 -0
- data/spec/schema_spec.rb +289 -0
- data/spec/schema_walk_spec.rb +42 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/struct_spec.rb +298 -0
- data/spec/validators_spec.rb +106 -0
- metadata +49 -23
- data/lib/parametric/hash.rb +0 -36
- data/lib/parametric/params.rb +0 -60
- data/lib/parametric/utils.rb +0 -24
- data/spec/parametric_spec.rb +0 -182
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'benchmark/ips'
|
2
|
+
require 'parametric/struct'
|
3
|
+
|
4
|
+
StructAccount = Struct.new(:id, :email, keyword_init: true)
|
5
|
+
StructFriend = Struct.new(:name, keyword_init: true)
|
6
|
+
StructUser = Struct.new(:name, :age, :friends, :account, keyword_init: true)
|
7
|
+
|
8
|
+
class ParametricAccount
|
9
|
+
include Parametric::Struct
|
10
|
+
schema do
|
11
|
+
field(:id).type(:integer).present
|
12
|
+
field(:email).type(:string)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class ParametricUser
|
17
|
+
include Parametric::Struct
|
18
|
+
schema do
|
19
|
+
field(:name).type(:string).present
|
20
|
+
field(:age).type(:integer).default(42)
|
21
|
+
field(:friends).type(:array).schema do
|
22
|
+
field(:name).type(:string).present
|
23
|
+
end
|
24
|
+
field(:account).type(:object).schema ParametricAccount
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
Benchmark.ips do |x|
|
29
|
+
x.report("Struct") {
|
30
|
+
StructUser.new(
|
31
|
+
name: 'Ismael',
|
32
|
+
age: 42,
|
33
|
+
friends: [
|
34
|
+
StructFriend.new(name: 'Joe'),
|
35
|
+
StructFriend.new(name: 'Joan'),
|
36
|
+
],
|
37
|
+
account: StructAccount.new(id: 123, email: 'my@account.com')
|
38
|
+
)
|
39
|
+
}
|
40
|
+
x.report("Parametric::Struct") {
|
41
|
+
ParametricUser.new!(
|
42
|
+
name: 'Ismael',
|
43
|
+
age: 42,
|
44
|
+
friends: [
|
45
|
+
{ name: 'Joe' },
|
46
|
+
{ name: 'Joan' }
|
47
|
+
],
|
48
|
+
account: { id: 123, email: 'my@account.com' }
|
49
|
+
)
|
50
|
+
}
|
51
|
+
x.compare!
|
52
|
+
end
|
53
|
+
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "parametric"
|
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,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Parametric
|
4
|
+
class BlockValidator
|
5
|
+
def self.build(meth, &block)
|
6
|
+
klass = Class.new(self)
|
7
|
+
klass.public_send(meth, &block)
|
8
|
+
klass
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.message(&block)
|
12
|
+
@message_block = block if block_given?
|
13
|
+
@message_block if instance_variable_defined?('@message_block')
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.validate(&validate_block)
|
17
|
+
@validate_block = validate_block if block_given?
|
18
|
+
@validate_block if instance_variable_defined?('@validate_block')
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.coerce(&coerce_block)
|
22
|
+
@coerce_block = coerce_block if block_given?
|
23
|
+
@coerce_block
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.eligible(&block)
|
27
|
+
@eligible_block = block if block_given?
|
28
|
+
@eligible_block if instance_variable_defined?('@eligible_block')
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.meta_data(&block)
|
32
|
+
@meta_data_block = block if block_given?
|
33
|
+
@meta_data_block if instance_variable_defined?('@meta_data_block')
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :message
|
37
|
+
|
38
|
+
def initialize(*args)
|
39
|
+
@args = args
|
40
|
+
@message = 'is invalid'
|
41
|
+
@validate_block = self.class.validate || ->(*args) { true }
|
42
|
+
@coerce_block = self.class.coerce || ->(v, *_) { v }
|
43
|
+
@eligible_block = self.class.eligible || ->(*args) { true }
|
44
|
+
@meta_data_block = self.class.meta_data || ->(*args) { {} }
|
45
|
+
end
|
46
|
+
|
47
|
+
def eligible?(value, key, payload)
|
48
|
+
args = (@args + [value, key, payload])
|
49
|
+
@eligible_block.call(*args)
|
50
|
+
end
|
51
|
+
|
52
|
+
def coerce(value, key, context)
|
53
|
+
@coerce_block.call(value, key, context)
|
54
|
+
end
|
55
|
+
|
56
|
+
def valid?(value, key, payload)
|
57
|
+
args = (@args + [value, key, payload])
|
58
|
+
@message = self.class.message.call(*args) if self.class.message
|
59
|
+
@validate_block.call(*args)
|
60
|
+
end
|
61
|
+
|
62
|
+
def meta_data
|
63
|
+
@meta_data_block.call *@args
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Parametric
|
4
|
+
class Top
|
5
|
+
attr_reader :errors
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@errors = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_error(key, msg)
|
12
|
+
errors[key] ||= []
|
13
|
+
errors[key] << msg
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Context
|
18
|
+
def initialize(path = nil, top = Top.new)
|
19
|
+
@top = top
|
20
|
+
@path = Array(path).compact
|
21
|
+
end
|
22
|
+
|
23
|
+
def errors
|
24
|
+
top.errors
|
25
|
+
end
|
26
|
+
|
27
|
+
def add_error(msg)
|
28
|
+
top.add_error(string_path, msg)
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_base_error(key, msg)
|
32
|
+
top.add_error(key, msg)
|
33
|
+
end
|
34
|
+
|
35
|
+
def sub(key)
|
36
|
+
self.class.new(path + [key], top)
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
attr_reader :path, :top
|
41
|
+
|
42
|
+
def string_path
|
43
|
+
path.reduce(['$']) do |m, segment|
|
44
|
+
m << (segment.is_a?(Integer) ? "[#{segment}]" : ".#{segment}")
|
45
|
+
m
|
46
|
+
end.join
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
|
5
|
+
module Parametric
|
6
|
+
# type coercions
|
7
|
+
Parametric.policy :integer do
|
8
|
+
coerce do |v, k, c|
|
9
|
+
v.to_i
|
10
|
+
end
|
11
|
+
|
12
|
+
meta_data do
|
13
|
+
{type: :integer}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Parametric.policy :number do
|
18
|
+
coerce do |v, k, c|
|
19
|
+
v.to_f
|
20
|
+
end
|
21
|
+
|
22
|
+
meta_data do
|
23
|
+
{type: :number}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Parametric.policy :string do
|
28
|
+
coerce do |v, k, c|
|
29
|
+
v.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
meta_data do
|
33
|
+
{type: :string}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Parametric.policy :boolean do
|
38
|
+
coerce do |v, k, c|
|
39
|
+
!!v
|
40
|
+
end
|
41
|
+
|
42
|
+
meta_data do
|
43
|
+
{type: :boolean}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# type validations
|
48
|
+
Parametric.policy :array do
|
49
|
+
message do |actual|
|
50
|
+
"expects an array, but got #{actual.inspect}"
|
51
|
+
end
|
52
|
+
|
53
|
+
validate do |value, key, payload|
|
54
|
+
!payload.key?(key) || value.is_a?(Array)
|
55
|
+
end
|
56
|
+
|
57
|
+
meta_data do
|
58
|
+
{type: :array}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
Parametric.policy :object do
|
63
|
+
message do |actual|
|
64
|
+
"expects a hash, but got #{actual.inspect}"
|
65
|
+
end
|
66
|
+
|
67
|
+
validate do |value, key, payload|
|
68
|
+
!payload.key?(key) ||
|
69
|
+
value.respond_to?(:[]) &&
|
70
|
+
value.respond_to?(:key?)
|
71
|
+
end
|
72
|
+
|
73
|
+
meta_data do
|
74
|
+
{type: :object}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
Parametric.policy :split do
|
79
|
+
coerce do |v, k, c|
|
80
|
+
v.kind_of?(Array) ? v : v.to_s.split(/\s*,\s*/)
|
81
|
+
end
|
82
|
+
|
83
|
+
meta_data do
|
84
|
+
{type: :array}
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
Parametric.policy :datetime do
|
89
|
+
coerce do |v, k, c|
|
90
|
+
DateTime.parse(v.to_s)
|
91
|
+
end
|
92
|
+
|
93
|
+
meta_data do
|
94
|
+
{type: :datetime}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "parametric"
|
4
|
+
|
5
|
+
module Parametric
|
6
|
+
module DSL
|
7
|
+
# Example
|
8
|
+
# class Foo
|
9
|
+
# include Parametric::DSL
|
10
|
+
#
|
11
|
+
# schema do
|
12
|
+
# field(:title).type(:string).present
|
13
|
+
# field(:age).type(:integer).default(20)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# attr_reader :params
|
17
|
+
#
|
18
|
+
# def initialize(input)
|
19
|
+
# @params = self.class.schema.resolve(input)
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# foo = Foo.new(title: "A title", nope: "hello")
|
24
|
+
#
|
25
|
+
# foo.params # => {title: "A title", age: 20}
|
26
|
+
#
|
27
|
+
DEFAULT_SCHEMA_NAME = :schema
|
28
|
+
|
29
|
+
def self.included(base)
|
30
|
+
base.extend(ClassMethods)
|
31
|
+
base.schemas = {DEFAULT_SCHEMA_NAME => Parametric::Schema.new}
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
def schema=(sc)
|
36
|
+
@schemas[DEFAULT_SCHEMA_NAME] = sc
|
37
|
+
end
|
38
|
+
|
39
|
+
def schemas=(sc)
|
40
|
+
@schemas = sc
|
41
|
+
end
|
42
|
+
|
43
|
+
def inherited(subclass)
|
44
|
+
subclass.schemas = @schemas.each_with_object({}) do |(key, sc), hash|
|
45
|
+
hash[key] = sc.merge(Parametric::Schema.new)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def schema(*args, &block)
|
50
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
51
|
+
key = args.first.is_a?(Symbol) ? args.first : DEFAULT_SCHEMA_NAME
|
52
|
+
current_schema = @schemas.fetch(key) { Parametric::Schema.new }
|
53
|
+
new_schema = if block_given? || options.any?
|
54
|
+
Parametric::Schema.new(options, &block)
|
55
|
+
elsif args.first.respond_to?(:schema)
|
56
|
+
args.first
|
57
|
+
end
|
58
|
+
|
59
|
+
return current_schema unless new_schema
|
60
|
+
|
61
|
+
@schemas[key] = current_schema ? current_schema.merge(new_schema) : new_schema
|
62
|
+
parametric_after_define_schema(@schemas[key])
|
63
|
+
end
|
64
|
+
|
65
|
+
def parametric_after_define_schema(sc)
|
66
|
+
# noop hook
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "parametric/field_dsl"
|
4
|
+
|
5
|
+
module Parametric
|
6
|
+
class ConfigurationError < StandardError; end
|
7
|
+
|
8
|
+
class Field
|
9
|
+
include FieldDSL
|
10
|
+
|
11
|
+
attr_reader :key, :meta_data
|
12
|
+
Result = Struct.new(:eligible?, :value)
|
13
|
+
|
14
|
+
def initialize(key, registry = Parametric.registry)
|
15
|
+
@key = key
|
16
|
+
@policies = []
|
17
|
+
@registry = registry
|
18
|
+
@default_block = nil
|
19
|
+
@meta_data = {}
|
20
|
+
@policies = []
|
21
|
+
end
|
22
|
+
|
23
|
+
def meta(hash = nil)
|
24
|
+
@meta_data = @meta_data.merge(hash) if hash.is_a?(Hash)
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def default(value)
|
29
|
+
meta default: value
|
30
|
+
@default_block = (value.respond_to?(:call) ? value : ->(key, payload, context) { value })
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def policy(key, *args)
|
35
|
+
pol = lookup(key, args)
|
36
|
+
meta pol.meta_data
|
37
|
+
policies << pol
|
38
|
+
self
|
39
|
+
end
|
40
|
+
alias_method :type, :policy
|
41
|
+
|
42
|
+
def schema(sc = nil, &block)
|
43
|
+
sc = (sc ? sc : Schema.new(&block))
|
44
|
+
meta schema: sc
|
45
|
+
policy sc.schema
|
46
|
+
end
|
47
|
+
|
48
|
+
def visit(meta_key = nil, &visitor)
|
49
|
+
if sc = meta_data[:schema]
|
50
|
+
r = sc.visit(meta_key, &visitor)
|
51
|
+
(meta_data[:type] == :array) ? [r] : r
|
52
|
+
else
|
53
|
+
meta_key ? meta_data[meta_key] : yield(self)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def resolve(payload, context)
|
58
|
+
eligible = payload.key?(key)
|
59
|
+
value = payload[key] # might be nil
|
60
|
+
|
61
|
+
if !eligible && has_default?
|
62
|
+
eligible = true
|
63
|
+
value = default_block.call(key, payload, context)
|
64
|
+
return Result.new(eligible, value)
|
65
|
+
end
|
66
|
+
|
67
|
+
policies.each do |policy|
|
68
|
+
if !policy.eligible?(value, key, payload)
|
69
|
+
eligible = false
|
70
|
+
if has_default?
|
71
|
+
eligible = true
|
72
|
+
value = default_block.call(key, payload, context)
|
73
|
+
end
|
74
|
+
break
|
75
|
+
else
|
76
|
+
value = resolve_one(policy, value, context)
|
77
|
+
if !policy.valid?(value, key, payload)
|
78
|
+
eligible = true # eligible, but has errors
|
79
|
+
context.add_error policy.message
|
80
|
+
break # only one error at a time
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Result.new(eligible, value)
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
attr_reader :policies, :registry, :default_block
|
90
|
+
|
91
|
+
def resolve_one(policy, value, context)
|
92
|
+
begin
|
93
|
+
policy.coerce(value, key, context)
|
94
|
+
rescue StandardError => e
|
95
|
+
context.add_error e.message
|
96
|
+
value
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def has_default?
|
101
|
+
!!default_block && !meta_data[:skip_default]
|
102
|
+
end
|
103
|
+
|
104
|
+
def lookup(key, args)
|
105
|
+
obj = key.is_a?(Symbol) ? registry.policies[key] : key
|
106
|
+
|
107
|
+
raise ConfigurationError, "No policies defined for #{key.inspect}" unless obj
|
108
|
+
|
109
|
+
obj.respond_to?(:new) ? obj.new(*args) : obj
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Parametric
|
4
|
+
# Field DSL
|
5
|
+
# host instance must implement:
|
6
|
+
# #meta(options Hash)
|
7
|
+
# #policy(key Symbol) self
|
8
|
+
#
|
9
|
+
module FieldDSL
|
10
|
+
def required
|
11
|
+
policy :required
|
12
|
+
end
|
13
|
+
|
14
|
+
def present
|
15
|
+
required.policy :present
|
16
|
+
end
|
17
|
+
|
18
|
+
def declared
|
19
|
+
policy :declared
|
20
|
+
end
|
21
|
+
|
22
|
+
def options(opts)
|
23
|
+
policy :options, opts
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/parametric/policies.rb
CHANGED
@@ -1,62 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Parametric
|
2
4
|
module Policies
|
5
|
+
class Format
|
6
|
+
attr_reader :message
|
3
7
|
|
4
|
-
|
5
|
-
|
6
|
-
@
|
7
|
-
@decorated = decorated
|
8
|
+
def initialize(fmt, msg = "invalid format")
|
9
|
+
@message = msg
|
10
|
+
@fmt = fmt
|
8
11
|
end
|
9
12
|
|
10
|
-
def
|
11
|
-
|
13
|
+
def eligible?(value, key, payload)
|
14
|
+
payload.key?(key)
|
12
15
|
end
|
13
16
|
|
14
|
-
def value
|
15
|
-
|
17
|
+
def coerce(value, key, context)
|
18
|
+
value
|
16
19
|
end
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
def valid?(value, key, payload)
|
22
|
+
!payload.key?(key) || !!(value.to_s =~ @fmt)
|
23
|
+
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
v = decorated.value
|
25
|
-
v.any? ? v : Array(options[:default])
|
25
|
+
def meta_data
|
26
|
+
{}
|
26
27
|
end
|
27
28
|
end
|
29
|
+
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
+
# Default validators
|
32
|
+
EMAIL_REGEXP = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i.freeze
|
31
33
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
34
|
+
Parametric.policy :format, Policies::Format
|
35
|
+
Parametric.policy :email, Policies::Format.new(EMAIL_REGEXP, 'invalid email')
|
36
|
+
|
37
|
+
Parametric.policy :noop do
|
38
|
+
eligible do |value, key, payload|
|
39
|
+
true
|
37
40
|
end
|
41
|
+
end
|
38
42
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
end
|
43
|
+
Parametric.policy :declared do
|
44
|
+
eligible do |value, key, payload|
|
45
|
+
payload.key? key
|
43
46
|
end
|
47
|
+
end
|
44
48
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
Parametric.policy :declared_no_default do
|
50
|
+
eligible do |value, key, payload|
|
51
|
+
payload.key? key
|
52
|
+
end
|
53
|
+
|
54
|
+
meta_data do
|
55
|
+
{skip_default: true}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
Parametric.policy :required do
|
60
|
+
message do |*|
|
61
|
+
"is required"
|
62
|
+
end
|
63
|
+
|
64
|
+
validate do |value, key, payload|
|
65
|
+
payload.key? key
|
66
|
+
end
|
67
|
+
|
68
|
+
meta_data do
|
69
|
+
{required: true}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
Parametric.policy :present do
|
74
|
+
message do |*|
|
75
|
+
"is required and value must be present"
|
51
76
|
end
|
52
77
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
78
|
+
validate do |value, key, payload|
|
79
|
+
case value
|
80
|
+
when String
|
81
|
+
value.strip != ''
|
82
|
+
when Array, Hash
|
83
|
+
value.any?
|
84
|
+
else
|
85
|
+
!value.nil?
|
58
86
|
end
|
59
87
|
end
|
60
88
|
|
89
|
+
meta_data do
|
90
|
+
{present: true}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
Parametric.policy :gt do
|
95
|
+
message do |num, actual|
|
96
|
+
"must be greater than #{num}, but got #{actual}"
|
97
|
+
end
|
98
|
+
|
99
|
+
validate do |num, actual, key, payload|
|
100
|
+
!payload[key] || actual.to_i > num.to_i
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
Parametric.policy :lt do
|
105
|
+
message do |num, actual|
|
106
|
+
"must be less than #{num}, but got #{actual}"
|
107
|
+
end
|
108
|
+
|
109
|
+
validate do |num, actual, key, payload|
|
110
|
+
!payload[key] || actual.to_i < num.to_i
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
Parametric.policy :options do
|
115
|
+
message do |options, actual|
|
116
|
+
"must be one of #{options.join(', ')}, but got #{actual}"
|
117
|
+
end
|
118
|
+
|
119
|
+
eligible do |options, actual, key, payload|
|
120
|
+
payload.key?(key)
|
121
|
+
end
|
122
|
+
|
123
|
+
validate do |options, actual, key, payload|
|
124
|
+
!payload.key?(key) || ok?(options, actual)
|
125
|
+
end
|
126
|
+
|
127
|
+
meta_data do |opts|
|
128
|
+
{options: opts}
|
129
|
+
end
|
130
|
+
|
131
|
+
def ok?(options, actual)
|
132
|
+
[actual].flatten.all?{|v| options.include?(v)}
|
133
|
+
end
|
61
134
|
end
|
62
|
-
end
|
135
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'parametric/block_validator'
|
4
|
+
|
5
|
+
module Parametric
|
6
|
+
class Registry
|
7
|
+
attr_reader :policies
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@policies = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def coercions
|
14
|
+
policies
|
15
|
+
end
|
16
|
+
|
17
|
+
def policy(name, plcy = nil, &block)
|
18
|
+
policies[name] = (plcy || BlockValidator.build(:instance_eval, &block))
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|