definition 1.0.0 → 1.1.2
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 +4 -4
- data/.github/workflows/actions.yml +1 -1
- data/.ruby-version +1 -0
- data/Changelog.md +12 -0
- data/Gemfile.lock +42 -46
- data/benchmark/model.rb +41 -4
- data/lib/definition/conform_result.rb +6 -8
- data/lib/definition/dsl/nil.rb +1 -3
- data/lib/definition/model.rb +5 -1
- data/lib/definition/types/and.rb +12 -35
- data/lib/definition/types/each.rb +33 -47
- data/lib/definition/types/keys.rb +75 -89
- data/lib/definition/types/lambda.rb +10 -12
- data/lib/definition/types/nil.rb +23 -0
- data/lib/definition/types/or.rb +14 -38
- data/lib/definition/types.rb +1 -0
- data/lib/definition/value_object.rb +2 -0
- data/lib/definition/version.rb +1 -1
- metadata +9 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d395d2c71aea47ece41fdb8904a8c2135b4b18ae35f602e262d38778a56bdfb
|
4
|
+
data.tar.gz: ae7f24b7b01f040f0df1acc375834ac880c4299368b2ac0c8721ab26fbff1866
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ec02230f3a42281334841075805b205ca5455d8034835e5da363b32a6905ad9c1cdf47f7b3f036c4696e0ac845cc049cd44dfaa65f0fb9a1c724e7cce991519d
|
7
|
+
data.tar.gz: 29b6f124f85ec5925bb4319053efa505fabb45fe130897faa43dd036f3542fe44ef791df543c2e5a2bce36c7706d516f9c8170437981fa8ffdc52dfa8d4fa07e
|
@@ -16,7 +16,7 @@ jobs:
|
|
16
16
|
strategy:
|
17
17
|
fail-fast: false
|
18
18
|
matrix:
|
19
|
-
ruby-version: ["2.7", "3.0", "3.1", "3.2", "jruby-9.3", "jruby-9.4"]
|
19
|
+
ruby-version: ["2.7", "3.0", "3.1", "3.2", "3.3", "jruby-9.3", "jruby-9.4"]
|
20
20
|
steps:
|
21
21
|
- uses: actions/checkout@v3
|
22
22
|
- uses: ruby/setup-ruby@v1
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.3.0
|
data/Changelog.md
CHANGED
@@ -6,6 +6,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
6
6
|
|
7
7
|
## Unreleased
|
8
8
|
|
9
|
+
## [1.1.2] - 2024-08-22
|
10
|
+
### Fixed
|
11
|
+
- Fixed "TypeError: no _dump_data is defined for class Proc" error that ocurred when a definition model inherits from another model that uses lambda based definitions
|
12
|
+
|
13
|
+
## [1.1.1] - 2024-05-21
|
14
|
+
### Fixed
|
15
|
+
- Fixed Definition::Model inheritance
|
16
|
+
|
17
|
+
## [1.1.0] - 2023-11-22
|
18
|
+
### Changes
|
19
|
+
- Improved performance
|
20
|
+
|
9
21
|
## [1.0.0] - 2023-02-14
|
10
22
|
### Removed
|
11
23
|
- Removed deprecated version of `GreaterThanEqual` definition that had a typo in it (GreaterThenEqual)
|
data/Gemfile.lock
CHANGED
@@ -1,36 +1,44 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
definition (1.
|
4
|
+
definition (1.1.2)
|
5
5
|
activesupport
|
6
6
|
i18n
|
7
7
|
|
8
8
|
GEM
|
9
9
|
remote: https://rubygems.org/
|
10
10
|
specs:
|
11
|
-
activesupport (
|
11
|
+
activesupport (7.1.3)
|
12
|
+
base64
|
13
|
+
bigdecimal
|
12
14
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
15
|
+
connection_pool (>= 2.2.5)
|
16
|
+
drb
|
13
17
|
i18n (>= 1.6, < 2)
|
14
18
|
minitest (>= 5.1)
|
19
|
+
mutex_m
|
15
20
|
tzinfo (~> 2.0)
|
16
|
-
zeitwerk (~> 2.3)
|
17
21
|
approvals (0.0.26)
|
18
22
|
json (~> 2.0)
|
19
23
|
nokogiri (~> 1.8)
|
20
24
|
thor (~> 1.0)
|
21
25
|
ast (2.4.2)
|
22
26
|
awesome_print (1.9.2)
|
23
|
-
|
27
|
+
base64 (0.2.0)
|
28
|
+
benchmark-ips (2.13.0)
|
29
|
+
bigdecimal (3.1.6)
|
24
30
|
coderay (1.1.3)
|
25
|
-
concurrent-ruby (1.2.
|
31
|
+
concurrent-ruby (1.2.3)
|
32
|
+
connection_pool (2.4.1)
|
26
33
|
diff-lcs (1.5.0)
|
27
|
-
|
28
|
-
|
34
|
+
drb (2.2.0)
|
35
|
+
ruby2_keywords
|
36
|
+
ffi (1.16.3)
|
29
37
|
formatador (1.1.0)
|
30
38
|
fuubar (2.5.1)
|
31
39
|
rspec-core (~> 3.0)
|
32
40
|
ruby-progressbar (~> 1.4)
|
33
|
-
guard (2.18.
|
41
|
+
guard (2.18.1)
|
34
42
|
formatador (>= 0.2.4)
|
35
43
|
listen (>= 2.7, < 4.0)
|
36
44
|
lumberjack (>= 1.0.12, < 2.0)
|
@@ -44,47 +52,37 @@ GEM
|
|
44
52
|
guard (~> 2.1)
|
45
53
|
guard-compat (~> 1.1)
|
46
54
|
rspec (>= 2.99.0, < 4.0)
|
47
|
-
i18n (1.
|
55
|
+
i18n (1.14.1)
|
48
56
|
concurrent-ruby (~> 1.0)
|
49
|
-
|
50
|
-
|
51
|
-
jaro_winkler (1.5.4-java)
|
52
|
-
json (2.6.3)
|
53
|
-
json (2.6.3-java)
|
57
|
+
jaro_winkler (1.5.6)
|
58
|
+
json (2.7.1)
|
54
59
|
listen (3.8.0)
|
55
60
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
56
61
|
rb-inotify (~> 0.9, >= 0.9.10)
|
57
|
-
lumberjack (1.2.
|
62
|
+
lumberjack (1.2.10)
|
58
63
|
method_source (1.0.0)
|
59
|
-
|
60
|
-
|
64
|
+
minitest (5.21.2)
|
65
|
+
mutex_m (0.2.0)
|
61
66
|
nenv (0.3.0)
|
62
|
-
nokogiri (1.
|
63
|
-
mini_portile2 (~> 2.8.0)
|
67
|
+
nokogiri (1.16.5-arm64-darwin)
|
64
68
|
racc (~> 1.4)
|
65
|
-
nokogiri (1.
|
69
|
+
nokogiri (1.16.5-x86_64-linux)
|
66
70
|
racc (~> 1.4)
|
67
71
|
notiffany (0.1.3)
|
68
72
|
nenv (~> 0.1)
|
69
73
|
shellany (~> 0.0)
|
70
|
-
parallel (1.
|
71
|
-
parser (3.
|
74
|
+
parallel (1.24.0)
|
75
|
+
parser (3.3.0.5)
|
72
76
|
ast (~> 2.4.1)
|
77
|
+
racc
|
73
78
|
pry (0.14.2)
|
74
79
|
coderay (~> 1.1)
|
75
80
|
method_source (~> 1.0)
|
76
|
-
|
77
|
-
coderay (~> 1.1)
|
78
|
-
method_source (~> 1.0)
|
79
|
-
spoon (~> 0.0)
|
80
|
-
psych (4.0.6)
|
81
|
+
psych (5.1.2)
|
81
82
|
stringio
|
82
|
-
|
83
|
-
jar-dependencies (>= 0.1.7)
|
84
|
-
racc (1.6.2)
|
85
|
-
racc (1.6.2-java)
|
83
|
+
racc (1.8.0)
|
86
84
|
rainbow (3.1.1)
|
87
|
-
rake (13.0
|
85
|
+
rake (13.1.0)
|
88
86
|
rb-fsevent (0.11.2)
|
89
87
|
rb-inotify (0.10.1)
|
90
88
|
ffi (~> 1.0)
|
@@ -92,18 +90,18 @@ GEM
|
|
92
90
|
rspec-core (~> 3.12.0)
|
93
91
|
rspec-expectations (~> 3.12.0)
|
94
92
|
rspec-mocks (~> 3.12.0)
|
95
|
-
rspec-core (3.12.
|
93
|
+
rspec-core (3.12.2)
|
96
94
|
rspec-support (~> 3.12.0)
|
97
|
-
rspec-expectations (3.12.
|
95
|
+
rspec-expectations (3.12.3)
|
98
96
|
diff-lcs (>= 1.2.0, < 2.0)
|
99
97
|
rspec-support (~> 3.12.0)
|
100
98
|
rspec-its (1.3.0)
|
101
99
|
rspec-core (>= 3.0.0)
|
102
100
|
rspec-expectations (>= 3.0.0)
|
103
|
-
rspec-mocks (3.12.
|
101
|
+
rspec-mocks (3.12.6)
|
104
102
|
diff-lcs (>= 1.2.0, < 2.0)
|
105
103
|
rspec-support (~> 3.12.0)
|
106
|
-
rspec-support (3.12.
|
104
|
+
rspec-support (3.12.1)
|
107
105
|
rspec_junit_formatter (0.6.0)
|
108
106
|
rspec-core (>= 2, < 4, != 2.12.0)
|
109
107
|
rubocop (0.66.0)
|
@@ -117,21 +115,19 @@ GEM
|
|
117
115
|
rubocop-rspec (1.32.0)
|
118
116
|
rubocop (>= 0.60.0)
|
119
117
|
rubocop_runner (2.2.1)
|
120
|
-
ruby-progressbar (1.
|
118
|
+
ruby-progressbar (1.13.0)
|
119
|
+
ruby2_keywords (0.0.5)
|
121
120
|
shellany (0.0.1)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
thor (1.2.1)
|
126
|
-
timecop (0.9.6)
|
121
|
+
stringio (3.1.0)
|
122
|
+
thor (1.3.0)
|
123
|
+
timecop (0.9.8)
|
127
124
|
tzinfo (2.0.6)
|
128
125
|
concurrent-ruby (~> 1.0)
|
129
126
|
unicode-display_width (1.5.0)
|
130
|
-
zeitwerk (2.6.7)
|
131
127
|
|
132
128
|
PLATFORMS
|
133
|
-
|
134
|
-
|
129
|
+
arm64-darwin-23
|
130
|
+
x86_64-linux
|
135
131
|
|
136
132
|
DEPENDENCIES
|
137
133
|
approvals (~> 0.0)
|
@@ -152,4 +148,4 @@ DEPENDENCIES
|
|
152
148
|
timecop
|
153
149
|
|
154
150
|
BUNDLED WITH
|
155
|
-
2.
|
151
|
+
2.4.21
|
data/benchmark/model.rb
CHANGED
@@ -4,7 +4,7 @@ require "bundler/inline"
|
|
4
4
|
|
5
5
|
gemfile do
|
6
6
|
source "https://rubygems.org"
|
7
|
-
gem "dry-struct", "~> 1.
|
7
|
+
gem "dry-struct", "~> 1.6"
|
8
8
|
gem "awesome_print"
|
9
9
|
gem "benchmark-ips"
|
10
10
|
gem "pry"
|
@@ -21,6 +21,16 @@ class DryStructModel < Dry::Struct
|
|
21
21
|
attribute :app_name, Dry::Types["strict.string"].optional.default(nil)
|
22
22
|
attribute :app_branch, Dry::Types["strict.string"].optional.default(nil)
|
23
23
|
attribute :platform, Dry::Types["strict.string"].optional.default(nil)
|
24
|
+
attribute :user, Dry::Types["strict.hash"].schema(
|
25
|
+
name: Dry::Types["strict.string"],
|
26
|
+
age: Dry::Types["coercible.integer"]
|
27
|
+
)
|
28
|
+
attribute :array, Dry::Types["strict.array"].of(
|
29
|
+
Dry::Types["strict.string"].enum("a", "b", "c", "d")
|
30
|
+
).optional.default(nil)
|
31
|
+
attribute :and_attribute, Dry::Types["strict.integer"].constrained(gt: 1)
|
32
|
+
attribute :or_attribute, Dry::Types["strict.integer"] | Dry::Types["strict.float"]
|
33
|
+
attribute :bool_attribute, Dry::Types["strict.bool"]
|
24
34
|
end
|
25
35
|
|
26
36
|
class DefinitionModel < Definition::Model
|
@@ -30,13 +40,40 @@ class DefinitionModel < Definition::Model
|
|
30
40
|
optional :app_name, Definition.Type(String)
|
31
41
|
optional :app_branch, Definition.Type(String)
|
32
42
|
optional :platform, Definition.Type(String)
|
43
|
+
required(:user, Definition.Keys do
|
44
|
+
required :name, Definition.Type(String)
|
45
|
+
required :age, Definition.CoercibleType(Integer)
|
46
|
+
end)
|
47
|
+
optional :array, Definition.Nilable(Definition.Each(Definition.Enum("a", "b", "c", "d")))
|
48
|
+
optional :and_attribute, Definition.Nilable(Definition.And(
|
49
|
+
Definition.Type(Integer),
|
50
|
+
Definition.GreaterThan(1)
|
51
|
+
))
|
52
|
+
required :or_attribute, Definition.Nilable(Definition.Or(
|
53
|
+
Definition.Type(Integer),
|
54
|
+
Definition.Type(Float)
|
55
|
+
))
|
56
|
+
required :bool_attribute, Definition.Boolean
|
33
57
|
end
|
34
58
|
|
35
59
|
puts "Benchmark with valid input data:"
|
36
|
-
valid_data = {
|
60
|
+
valid_data = {
|
61
|
+
id: 1,
|
62
|
+
app_key: "com.test",
|
63
|
+
app_version: "1.0.0",
|
64
|
+
app_name: "testapp",
|
65
|
+
user: {
|
66
|
+
name: "John Doe",
|
67
|
+
age: "65"
|
68
|
+
},
|
69
|
+
array: %w[a b c d a],
|
70
|
+
and_attribute: 2,
|
71
|
+
or_attribute: 3.4,
|
72
|
+
bool_attribute: true
|
73
|
+
}
|
37
74
|
|
38
75
|
Benchmark.ips do |x|
|
39
|
-
x.config(time:
|
76
|
+
x.config(time: 20, warmup: 5)
|
40
77
|
|
41
78
|
x.report("definition") do
|
42
79
|
DefinitionModel.new(**valid_data)
|
@@ -52,7 +89,7 @@ end
|
|
52
89
|
puts "Benchmark with invalid input data:"
|
53
90
|
invalid_data = { id: "abc", app_key: "com.test", app_name: "testapp" }
|
54
91
|
Benchmark.ips do |x|
|
55
|
-
x.config(time:
|
92
|
+
x.config(time: 20, warmup: 5)
|
56
93
|
|
57
94
|
x.report("definition") do
|
58
95
|
DefinitionModel.new(**invalid_data)
|
@@ -5,14 +5,14 @@ require "active_support"
|
|
5
5
|
module Definition
|
6
6
|
class ConformResult
|
7
7
|
def initialize(value, errors: [])
|
8
|
-
|
9
|
-
|
8
|
+
@value = value
|
9
|
+
@conform_errors = errors
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
attr_reader :value
|
13
13
|
|
14
14
|
def passed?
|
15
|
-
conform_errors.empty?
|
15
|
+
@conform_errors.empty?
|
16
16
|
end
|
17
17
|
alias conformed? passed?
|
18
18
|
|
@@ -21,7 +21,7 @@ module Definition
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def leaf_errors
|
24
|
-
conform_errors.map(&:leaf_errors).flatten
|
24
|
+
@conform_errors.map(&:leaf_errors).flatten
|
25
25
|
end
|
26
26
|
|
27
27
|
def errors
|
@@ -46,7 +46,7 @@ module Definition
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def error_tree
|
49
|
-
conform_errors
|
49
|
+
@conform_errors
|
50
50
|
end
|
51
51
|
|
52
52
|
private
|
@@ -71,7 +71,5 @@ module Definition
|
|
71
71
|
end
|
72
72
|
nil
|
73
73
|
end
|
74
|
-
|
75
|
-
attr_accessor :conform_errors
|
76
74
|
end
|
77
75
|
end
|
data/lib/definition/dsl/nil.rb
CHANGED
data/lib/definition/model.rb
CHANGED
data/lib/definition/types/and.rb
CHANGED
@@ -21,46 +21,23 @@ module Definition
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def conform(value)
|
24
|
-
|
24
|
+
last_result = nil
|
25
|
+
definitions.each do |definition|
|
26
|
+
last_result = definition.conform(last_result.nil? ? value : last_result.value)
|
27
|
+
next if last_result.passed?
|
28
|
+
|
29
|
+
return ConformResult.new(last_result.value, errors: [
|
30
|
+
ConformError.new(self, "Not all definitions are valid for '#{name}'",
|
31
|
+
sub_errors: last_result.error_tree)
|
32
|
+
])
|
33
|
+
end
|
34
|
+
|
35
|
+
ConformResult.new(last_result.value)
|
25
36
|
end
|
26
37
|
|
27
38
|
def error_renderer
|
28
39
|
ErrorRenderers::Leaf
|
29
40
|
end
|
30
|
-
|
31
|
-
class Conformer
|
32
|
-
def initialize(definition)
|
33
|
-
self.definition = definition
|
34
|
-
end
|
35
|
-
|
36
|
-
def conform(value)
|
37
|
-
results = conform_all(value)
|
38
|
-
|
39
|
-
if results.all?(&:conformed?)
|
40
|
-
ConformResult.new(results.last.value)
|
41
|
-
else
|
42
|
-
ConformResult.new(value, errors: [
|
43
|
-
ConformError.new(definition, "Not all definitions are valid for '#{definition.name}'",
|
44
|
-
sub_errors: results.map(&:error_tree).flatten)
|
45
|
-
])
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
attr_accessor :definition
|
52
|
-
|
53
|
-
def conform_all(value)
|
54
|
-
results = []
|
55
|
-
definition.definitions.each do |definition|
|
56
|
-
result = definition.conform(value)
|
57
|
-
value = result.value
|
58
|
-
results << result
|
59
|
-
break unless result.passed?
|
60
|
-
end
|
61
|
-
results
|
62
|
-
end
|
63
|
-
end
|
64
41
|
end
|
65
42
|
end
|
66
43
|
end
|
@@ -13,64 +13,50 @@ module Definition
|
|
13
13
|
super(name)
|
14
14
|
end
|
15
15
|
|
16
|
-
def conform(
|
17
|
-
|
18
|
-
end
|
16
|
+
def conform(values)
|
17
|
+
return non_array_error(values) unless values.is_a?(Array)
|
19
18
|
|
20
|
-
|
21
|
-
ErrorRenderers::Leaf
|
22
|
-
end
|
19
|
+
errors = false
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
results = values.map do |value|
|
22
|
+
result = item_definition.conform(value)
|
23
|
+
errors = true unless result.passed?
|
24
|
+
result
|
27
25
|
end
|
28
26
|
|
29
|
-
|
30
|
-
return non_array_error(value) unless value.is_a?(Array)
|
31
|
-
|
32
|
-
results = conform_all(value)
|
33
|
-
|
34
|
-
if results.all?(&:conformed?)
|
35
|
-
ConformResult.new(results.map(&:value))
|
36
|
-
else
|
37
|
-
ConformResult.new(value, errors: [ConformError.new(definition,
|
38
|
-
"Not all items conform with '#{definition.name}'",
|
39
|
-
sub_errors: errors(results))])
|
40
|
-
end
|
41
|
-
end
|
27
|
+
return ConformResult.new(results.map(&:value)) unless errors
|
42
28
|
|
43
|
-
|
29
|
+
ConformResult.new(values, errors: [ConformError.new(self,
|
30
|
+
"Not all items conform with '#{name}'",
|
31
|
+
sub_errors: convert_errors(results))])
|
32
|
+
end
|
44
33
|
|
45
|
-
|
34
|
+
def error_renderer
|
35
|
+
ErrorRenderers::Leaf
|
36
|
+
end
|
46
37
|
|
47
|
-
|
48
|
-
errors = []
|
49
|
-
results.each_with_index do |result, index|
|
50
|
-
next if result.passed?
|
38
|
+
private
|
51
39
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
sub_errors: result.error_tree
|
57
|
-
)
|
58
|
-
end
|
59
|
-
errors
|
60
|
-
end
|
40
|
+
def convert_errors(results)
|
41
|
+
errors = []
|
42
|
+
results.each_with_index do |result, index|
|
43
|
+
next if result.passed?
|
61
44
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
45
|
+
errors << KeyConformError.new(
|
46
|
+
self,
|
47
|
+
"Item #{result.value.inspect} did not conform to #{name}",
|
48
|
+
key: index,
|
49
|
+
sub_errors: result.error_tree
|
50
|
+
)
|
66
51
|
end
|
52
|
+
errors
|
53
|
+
end
|
67
54
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
55
|
+
def non_array_error(value)
|
56
|
+
ConformResult.new(value, errors: [
|
57
|
+
ConformError.new(self,
|
58
|
+
"Non-Array value does not conform with #{name}")
|
59
|
+
])
|
74
60
|
end
|
75
61
|
end
|
76
62
|
end
|
@@ -9,11 +9,11 @@ module Definition
|
|
9
9
|
class Keys < Base
|
10
10
|
module Dsl
|
11
11
|
def required(key, definition)
|
12
|
-
required_definitions
|
12
|
+
required_definitions[key] = definition
|
13
13
|
end
|
14
14
|
|
15
15
|
def optional(key, definition, **opts)
|
16
|
-
optional_definitions
|
16
|
+
optional_definitions[key] = definition
|
17
17
|
default(key, opts[:default]) if opts.key?(:default)
|
18
18
|
end
|
19
19
|
|
@@ -31,8 +31,8 @@ module Definition
|
|
31
31
|
|
32
32
|
ensure_keys_do_not_interfere(other)
|
33
33
|
|
34
|
-
|
35
|
-
|
34
|
+
required_definitions.merge!(other.required_definitions)
|
35
|
+
optional_definitions.merge!(other.optional_definitions)
|
36
36
|
defaults.merge!(other.defaults)
|
37
37
|
end
|
38
38
|
|
@@ -57,114 +57,100 @@ module Definition
|
|
57
57
|
|
58
58
|
def initialize(name, req: {}, opt: {}, defaults: {}, options: {})
|
59
59
|
super(name)
|
60
|
-
self.required_definitions = req
|
61
|
-
self.optional_definitions = opt
|
60
|
+
self.required_definitions = req
|
61
|
+
self.optional_definitions = opt
|
62
62
|
self.defaults = defaults
|
63
63
|
self.ignore_extra_keys = options.fetch(:ignore_extra_keys, false)
|
64
64
|
end
|
65
65
|
|
66
|
-
def
|
67
|
-
|
66
|
+
def initialize_dup(_other)
|
67
|
+
super
|
68
|
+
self.required_definitions = required_definitions.dup
|
69
|
+
self.optional_definitions = optional_definitions.dup
|
70
|
+
self.defaults = defaults.dup
|
68
71
|
end
|
69
72
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
self.errors = []
|
79
|
-
@conform_result_value = {} # This will be the output value after conforming
|
80
|
-
@not_conformed_value_keys = value.dup # Used to track which keys are left over in the end (unexpected keys)
|
81
|
-
end
|
73
|
+
def conform(input_value) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
74
|
+
# input_value is duplicated because we don't want to modify the user object that is passed into this function.
|
75
|
+
# The following logic will iterate over each definition and delete the key associated with the definition from
|
76
|
+
# the input value.
|
77
|
+
# In the end if there are still keys on the object and 'ignore_extra_keys' is false, an error will be raised.
|
78
|
+
value = input_value.dup
|
79
|
+
result_value = {}
|
80
|
+
errors = []
|
82
81
|
|
83
|
-
|
84
|
-
return invalid_input_result unless valid_input_type?
|
82
|
+
return wrong_type_result(value) unless value.is_a?(Hash)
|
85
83
|
|
86
|
-
|
87
|
-
|
84
|
+
required_definitions.each do |key, definition|
|
85
|
+
if value.key?(key)
|
86
|
+
result = definition.conform(value.delete(key))
|
87
|
+
result_value[key] = result.value
|
88
|
+
next if result.passed?
|
88
89
|
|
89
|
-
|
90
|
+
errors.push(key_error(definition, key, result))
|
91
|
+
else
|
92
|
+
errors << missing_key_error(key)
|
93
|
+
end
|
90
94
|
end
|
91
95
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
errors = [ConformError.new(definition,
|
98
|
-
"#{definition.name} is not a Hash",
|
99
|
-
i18n_key: "keys.not_a_hash")]
|
100
|
-
ConformResult.new(value, errors: errors)
|
101
|
-
end
|
96
|
+
optional_definitions.each do |key, definition|
|
97
|
+
if value.key?(key)
|
98
|
+
result = definition.conform(value.delete(key))
|
99
|
+
result_value[key] = result.value
|
100
|
+
next if result.passed?
|
102
101
|
|
103
|
-
|
104
|
-
|
102
|
+
errors.push(key_error(definition, key, result))
|
103
|
+
elsif defaults.key?(key)
|
104
|
+
result_value[key] = defaults.fetch(key)
|
105
|
+
end
|
105
106
|
end
|
106
107
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
extra_keys.each do |key|
|
112
|
-
errors.push(KeyConformError.new(
|
113
|
-
definition,
|
114
|
-
"#{definition.name} has extra key: #{key.inspect}",
|
115
|
-
key: key,
|
116
|
-
i18n_key: "keys.has_extra_key"
|
117
|
-
))
|
108
|
+
if !ignore_extra_keys && !value.keys.empty?
|
109
|
+
value.keys.each do |key|
|
110
|
+
errors << extra_key_error(key)
|
118
111
|
end
|
119
112
|
end
|
120
113
|
|
121
|
-
|
122
|
-
|
123
|
-
conform_definitions(definition.optional_definitions, required: false)
|
124
|
-
|
125
|
-
@conform_result_value
|
126
|
-
end
|
114
|
+
ConformResult.new(result_value, errors: errors)
|
115
|
+
end
|
127
116
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
key_definition = hash[:definition]
|
132
|
-
conform_definition(key, key_definition, required: required)
|
133
|
-
end
|
134
|
-
end
|
117
|
+
def keys
|
118
|
+
required_definitions.keys + optional_definitions.keys
|
119
|
+
end
|
135
120
|
|
136
|
-
|
137
|
-
def conform_definition(key, key_definition, required:) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
138
|
-
@not_conformed_value_keys.delete(key) # Keys left over in that hash at the end are considered unexpected
|
139
|
-
|
140
|
-
# If the input value is missing a key:
|
141
|
-
# a) add a missing key error if it is a required key
|
142
|
-
# b) otherwise initialize the missing key in the output value if a default value is configured
|
143
|
-
unless value.key?(key)
|
144
|
-
errors.push(missing_key_error(key)) if required
|
145
|
-
@conform_result_value[key] = definition.defaults[key] if definition.defaults.key?(key)
|
146
|
-
return
|
147
|
-
end
|
121
|
+
private
|
148
122
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
123
|
+
def wrong_type_result(value)
|
124
|
+
ConformResult.new(value, errors: [
|
125
|
+
ConformError.new(
|
126
|
+
self,
|
127
|
+
"#{name} is not a Hash",
|
128
|
+
i18n_key: "keys.not_a_hash"
|
129
|
+
)
|
130
|
+
])
|
131
|
+
end
|
153
132
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
133
|
+
def extra_key_error(key)
|
134
|
+
KeyConformError.new(
|
135
|
+
self,
|
136
|
+
"#{name} has extra key: #{key.inspect}",
|
137
|
+
key: key,
|
138
|
+
i18n_key: "keys.has_extra_key"
|
139
|
+
)
|
140
|
+
end
|
159
141
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
142
|
+
def missing_key_error(key)
|
143
|
+
KeyConformError.new(self,
|
144
|
+
"#{name} is missing key #{key.inspect}",
|
145
|
+
key: key,
|
146
|
+
i18n_key: "keys.has_missing_key")
|
147
|
+
end
|
166
148
|
|
167
|
-
|
149
|
+
def key_error(definition, key, result)
|
150
|
+
KeyConformError.new(definition,
|
151
|
+
"#{name} fails validation for key #{key}",
|
152
|
+
key: key,
|
153
|
+
sub_errors: result.error_tree)
|
168
154
|
end
|
169
155
|
end
|
170
156
|
end
|
@@ -28,41 +28,39 @@ module Definition
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def fail_with(error_message)
|
31
|
-
|
31
|
+
@error_message = error_message
|
32
32
|
end
|
33
33
|
end
|
34
34
|
include Dsl
|
35
35
|
|
36
36
|
def initialize(definition)
|
37
|
-
|
37
|
+
@definition = definition
|
38
38
|
end
|
39
39
|
|
40
40
|
def conform(value)
|
41
|
-
lambda_result = instance_exec(value,
|
41
|
+
lambda_result = instance_exec(value, &@definition.conformity_test_lambda)
|
42
42
|
return lambda_result if lambda_result.is_a?(ConformResult)
|
43
43
|
|
44
|
-
failure_result_with(value
|
44
|
+
failure_result_with(value)
|
45
45
|
end
|
46
46
|
|
47
47
|
private
|
48
48
|
|
49
|
-
attr_accessor :definition, :error_message
|
50
|
-
|
51
49
|
def standard_error_message
|
52
|
-
"Did not pass test for #{definition.name}"
|
50
|
+
"Did not pass test for #{@definition.name}"
|
53
51
|
end
|
54
52
|
|
55
53
|
def contextual_error_message
|
56
|
-
return standard_error_message if definition.context.empty?
|
54
|
+
return standard_error_message if @definition.context.empty?
|
57
55
|
|
58
|
-
"#{standard_error_message} (#{definition.context.values.join(',')})"
|
56
|
+
"#{standard_error_message} (#{@definition.context.values.join(',')})"
|
59
57
|
end
|
60
58
|
|
61
|
-
def failure_result_with(value
|
59
|
+
def failure_result_with(value)
|
62
60
|
ConformResult.new(value, errors: [
|
63
|
-
ConformError.new(definition,
|
61
|
+
ConformError.new(@definition,
|
64
62
|
contextual_error_message,
|
65
|
-
translated_message: error_message)
|
63
|
+
translated_message: @error_message)
|
66
64
|
])
|
67
65
|
end
|
68
66
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "definition/types/base"
|
4
|
+
|
5
|
+
module Definition
|
6
|
+
module Types
|
7
|
+
class Nil < Base
|
8
|
+
def initialize
|
9
|
+
super(:nil)
|
10
|
+
end
|
11
|
+
|
12
|
+
def conform(value)
|
13
|
+
if value.nil?
|
14
|
+
ConformResult.new(value)
|
15
|
+
else
|
16
|
+
ConformResult.new(value, errors: [
|
17
|
+
ConformError.new(self, "Did not pass test for nil")
|
18
|
+
])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/definition/types/or.rb
CHANGED
@@ -13,55 +13,31 @@ module Definition
|
|
13
13
|
end
|
14
14
|
|
15
15
|
include Dsl
|
16
|
-
|
16
|
+
attr_reader :definitions
|
17
17
|
|
18
18
|
def initialize(name, *args)
|
19
|
-
|
19
|
+
@definitions = *args
|
20
20
|
super(name)
|
21
21
|
end
|
22
22
|
|
23
23
|
def conform(value)
|
24
|
-
|
24
|
+
last_result = nil
|
25
|
+
@definitions.each do |definition|
|
26
|
+
last_result = definition.conform(value)
|
27
|
+
return last_result if last_result.passed?
|
28
|
+
end
|
29
|
+
|
30
|
+
ConformResult.new(value, errors: [
|
31
|
+
ConformError.new(self,
|
32
|
+
"None of the definitions are valid for '#{name}'."\
|
33
|
+
" Errors for last tested definition:",
|
34
|
+
sub_errors: last_result.error_tree)
|
35
|
+
])
|
25
36
|
end
|
26
37
|
|
27
38
|
def error_renderer
|
28
39
|
ErrorRenderers::Leaf
|
29
40
|
end
|
30
|
-
|
31
|
-
class Conformer
|
32
|
-
def initialize(definition)
|
33
|
-
self.definition = definition
|
34
|
-
end
|
35
|
-
|
36
|
-
def conform(value)
|
37
|
-
result = first_successful_conform_or_errors(value)
|
38
|
-
if result.is_a?(ConformResult)
|
39
|
-
result
|
40
|
-
else
|
41
|
-
error = ConformError.new(definition,
|
42
|
-
"None of the definitions are valid for '#{definition.name}'."\
|
43
|
-
" Errors for last tested definition:",
|
44
|
-
sub_errors: result)
|
45
|
-
ConformResult.new(value, errors: [error])
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def first_successful_conform_or_errors(value)
|
52
|
-
errors = []
|
53
|
-
definition.definitions.each do |definition|
|
54
|
-
result = definition.conform(value)
|
55
|
-
return result if result.passed?
|
56
|
-
|
57
|
-
errors = result.error_tree
|
58
|
-
end
|
59
|
-
|
60
|
-
errors.flatten
|
61
|
-
end
|
62
|
-
|
63
|
-
attr_accessor :definition
|
64
|
-
end
|
65
41
|
end
|
66
42
|
end
|
67
43
|
end
|
data/lib/definition/types.rb
CHANGED
data/lib/definition/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: definition
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dominik Goltermann
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -248,7 +248,7 @@ dependencies:
|
|
248
248
|
- - ">="
|
249
249
|
- !ruby/object:Gem::Version
|
250
250
|
version: '0'
|
251
|
-
description:
|
251
|
+
description:
|
252
252
|
email:
|
253
253
|
- dominik@goltermann.cc
|
254
254
|
executables: []
|
@@ -259,6 +259,7 @@ files:
|
|
259
259
|
- ".gitignore"
|
260
260
|
- ".rspec"
|
261
261
|
- ".rubocop.yml"
|
262
|
+
- ".ruby-version"
|
262
263
|
- Changelog.md
|
263
264
|
- Gemfile
|
264
265
|
- Gemfile.lock
|
@@ -296,6 +297,7 @@ files:
|
|
296
297
|
- lib/definition/types/include.rb
|
297
298
|
- lib/definition/types/keys.rb
|
298
299
|
- lib/definition/types/lambda.rb
|
300
|
+
- lib/definition/types/nil.rb
|
299
301
|
- lib/definition/types/or.rb
|
300
302
|
- lib/definition/types/type.rb
|
301
303
|
- lib/definition/value_object.rb
|
@@ -304,7 +306,7 @@ homepage: https://github.com/Goltergaul/definition
|
|
304
306
|
licenses:
|
305
307
|
- MIT
|
306
308
|
metadata: {}
|
307
|
-
post_install_message:
|
309
|
+
post_install_message:
|
308
310
|
rdoc_options: []
|
309
311
|
require_paths:
|
310
312
|
- lib
|
@@ -319,8 +321,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
319
321
|
- !ruby/object:Gem::Version
|
320
322
|
version: '0'
|
321
323
|
requirements: []
|
322
|
-
rubygems_version: 3.3
|
323
|
-
signing_key:
|
324
|
+
rubygems_version: 3.5.3
|
325
|
+
signing_key:
|
324
326
|
specification_version: 4
|
325
327
|
summary: Simple and composable validation and coercion of data structures inspired
|
326
328
|
by clojure specs
|