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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 15e2253bb98a99bde3103531801d378523e9312e6e7154a611d0d08b985a4666
4
- data.tar.gz: 465d7748ab7a2b842ff2b3cc80bffb826d46cd5d9f4e913ed15c95ef8c6955bd
3
+ metadata.gz: 9d395d2c71aea47ece41fdb8904a8c2135b4b18ae35f602e262d38778a56bdfb
4
+ data.tar.gz: ae7f24b7b01f040f0df1acc375834ac880c4299368b2ac0c8721ab26fbff1866
5
5
  SHA512:
6
- metadata.gz: 38be013225e94cd3796a6562c0b8cb437f77d300a12f56ffe6ec8504d224ada53cfd29ed5e8443ad9ba37caa62e55ef46f45a12fccf77f1d38b1f6f66ae055e9
7
- data.tar.gz: 3044a226fdc9f9ebec8d5ff5b2910d918f7e9e4ce322ba2e8d0d314320468dfa131d6a7a332efbf42868a5e83623cd48603406850c480ad55b0ac4577b983e5c
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.0.0)
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 (6.1.7.2)
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
- benchmark-ips (2.10.0)
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.0)
31
+ concurrent-ruby (1.2.3)
32
+ connection_pool (2.4.1)
26
33
  diff-lcs (1.5.0)
27
- ffi (1.15.5)
28
- ffi (1.15.5-java)
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.0)
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.12.0)
55
+ i18n (1.14.1)
48
56
  concurrent-ruby (~> 1.0)
49
- jar-dependencies (0.4.1)
50
- jaro_winkler (1.5.4)
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.8)
62
+ lumberjack (1.2.10)
58
63
  method_source (1.0.0)
59
- mini_portile2 (2.8.1)
60
- minitest (5.17.0)
64
+ minitest (5.21.2)
65
+ mutex_m (0.2.0)
61
66
  nenv (0.3.0)
62
- nokogiri (1.13.10)
63
- mini_portile2 (~> 2.8.0)
67
+ nokogiri (1.16.5-arm64-darwin)
64
68
  racc (~> 1.4)
65
- nokogiri (1.13.10-java)
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.22.1)
71
- parser (3.2.1.0)
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
- pry (0.14.2-java)
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
- psych (4.0.6-java)
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.6)
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.1)
93
+ rspec-core (3.12.2)
96
94
  rspec-support (~> 3.12.0)
97
- rspec-expectations (3.12.2)
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.3)
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.0)
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.11.0)
118
+ ruby-progressbar (1.13.0)
119
+ ruby2_keywords (0.0.5)
121
120
  shellany (0.0.1)
122
- spoon (0.0.6)
123
- ffi
124
- stringio (3.0.5)
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
- ruby
134
- universal-java-11
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.3.7
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.4"
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 = { id: 1, app_key: "com.test", app_version: "1.0.0", app_name: "testapp" }
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: 5, warmup: 2)
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: 5, warmup: 2)
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
- self.value = value
9
- self.conform_errors = errors
8
+ @value = value
9
+ @conform_errors = errors
10
10
  end
11
11
 
12
- attr_accessor :value
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
@@ -6,9 +6,7 @@ module Definition
6
6
  # Example:
7
7
  # Nil
8
8
  def Nil # rubocop:disable Naming/MethodName
9
- Types::Lambda.new(:nil) do |value|
10
- conform_with(value) if value.nil?
11
- end
9
+ Types::Nil.new
12
10
  end
13
11
  end
14
12
  end
@@ -37,7 +37,11 @@ module Definition
37
37
  end
38
38
 
39
39
  def _definition
40
- @_definition ||= ::Definition.Keys {}
40
+ @_definition ||= if superclass == ::Definition::Model
41
+ ::Definition.Keys {}
42
+ else
43
+ superclass._definition.dup
44
+ end
41
45
  end
42
46
  end
43
47
 
@@ -21,46 +21,23 @@ module Definition
21
21
  end
22
22
 
23
23
  def conform(value)
24
- Conformer.new(self).conform(value)
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(value)
17
- Conformer.new(self).conform(value)
18
- end
16
+ def conform(values)
17
+ return non_array_error(values) unless values.is_a?(Array)
19
18
 
20
- def error_renderer
21
- ErrorRenderers::Leaf
22
- end
19
+ errors = false
23
20
 
24
- class Conformer
25
- def initialize(definition)
26
- self.definition = definition
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
- def conform(value)
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
- private
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
- attr_accessor :definition
34
+ def error_renderer
35
+ ErrorRenderers::Leaf
36
+ end
46
37
 
47
- def errors(results)
48
- errors = []
49
- results.each_with_index do |result, index|
50
- next if result.passed?
38
+ private
51
39
 
52
- errors << KeyConformError.new(
53
- definition,
54
- "Item #{result.value.inspect} did not conform to #{definition.name}",
55
- key: index,
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
- def conform_all(values)
63
- values.map do |value|
64
- definition.item_definition.conform(value)
65
- end
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
- def non_array_error(value)
69
- ConformResult.new(value, errors: [
70
- ConformError.new(definition,
71
- "Non-Array value does not conform with #{definition.name}")
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 << { key: key, definition: definition }
12
+ required_definitions[key] = definition
13
13
  end
14
14
 
15
15
  def optional(key, definition, **opts)
16
- optional_definitions << { key: key, definition: definition }
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
- self.required_definitions += other.required_definitions
35
- self.optional_definitions += other.optional_definitions
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.map { |key, definition| { key: key, definition: definition } }
61
- self.optional_definitions = opt.map { |key, definition| { key: key, definition: definition } }
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 conform(value)
67
- Conformer.new(self, value).conform
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 keys
71
- (required_definitions + optional_definitions).map { |hash| hash[:key] }
72
- end
73
-
74
- class Conformer
75
- def initialize(definition, value)
76
- self.definition = definition
77
- self.value = value
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
- def conform
84
- return invalid_input_result unless valid_input_type?
82
+ return wrong_type_result(value) unless value.is_a?(Hash)
85
83
 
86
- values = conform_all_keys
87
- add_extra_key_errors unless definition.ignore_extra_keys
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
- ConformResult.new(values, errors: errors)
90
+ errors.push(key_error(definition, key, result))
91
+ else
92
+ errors << missing_key_error(key)
93
+ end
90
94
  end
91
95
 
92
- private
93
-
94
- attr_accessor :errors
95
-
96
- def invalid_input_result
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
- def valid_input_type?
104
- value.is_a?(Hash)
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
- def add_extra_key_errors
108
- extra_keys = @not_conformed_value_keys.keys
109
- return if extra_keys.empty?
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
- def conform_all_keys
122
- conform_definitions(definition.required_definitions, required: true)
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
- def conform_definitions(keys, required:)
129
- keys.each do |hash|
130
- key = hash[:key]
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
- # Rubcop rules are disabled for performance optimization purposes
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
- # If the input value has a key then its value is conformed against the configured definition
150
- result = key_definition.conform(value[key])
151
- @conform_result_value[key] = result.value
152
- return if result.passed?
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
- errors.push(KeyConformError.new(key_definition,
155
- "#{definition.name} fails validation for key #{key}",
156
- key: key,
157
- sub_errors: result.error_tree))
158
- end
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
- def missing_key_error(key)
161
- KeyConformError.new(definition,
162
- "#{definition.name} is missing key #{key.inspect}",
163
- key: key,
164
- i18n_key: "keys.has_missing_key")
165
- end
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
- attr_accessor :definition, :value
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
- self.error_message = error_message
31
+ @error_message = error_message
32
32
  end
33
33
  end
34
34
  include Dsl
35
35
 
36
36
  def initialize(definition)
37
- self.definition = definition
37
+ @definition = definition
38
38
  end
39
39
 
40
40
  def conform(value)
41
- lambda_result = instance_exec(value, &definition.conformity_test_lambda)
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, error_message)
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, error_message)
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
@@ -13,55 +13,31 @@ module Definition
13
13
  end
14
14
 
15
15
  include Dsl
16
- attr_accessor :definitions
16
+ attr_reader :definitions
17
17
 
18
18
  def initialize(name, *args)
19
- self.definitions = *args
19
+ @definitions = *args
20
20
  super(name)
21
21
  end
22
22
 
23
23
  def conform(value)
24
- Conformer.new(self).conform(value)
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
@@ -7,3 +7,4 @@ require "definition/types/keys"
7
7
  require "definition/types/lambda"
8
8
  require "definition/types/type"
9
9
  require "definition/types/each"
10
+ require "definition/types/nil"
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "delegate"
4
+
3
5
  module Definition
4
6
  class InvalidValueObjectError < StandardError
5
7
  attr_accessor :conform_result
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Definition
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.2"
5
5
  end
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.0.0
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: 2023-02-15 00:00:00.000000000 Z
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.23
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