parametric 0.0.1 → 0.2.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|