definition 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA256:
3
- metadata.gz: 6b8b872d14fc97965062a65fcc1d75987cfd7b21c448fe4f61dfda1f11fd003b
4
- data.tar.gz: 3195829dcc11b18abe5fa12826cefb2d16f70ce8d2bb127f715dc4ee6193b181
2
+ SHA1:
3
+ metadata.gz: 2e17d7407705fa6a48ed0a3852b73abe91688fbe
4
+ data.tar.gz: 118a8ad2dc4663c8666a25b68614f89f8546ab16
5
5
  SHA512:
6
- metadata.gz: 4861a2c4f8ee02c9706546b24dbf9040edc02e6ba8d05f6038529f7132d72d35403de1e079d46d40bb5b00e1d5e65fa2be93781cff4e920da58f5e835f84f242
7
- data.tar.gz: 53c7365d464f2fed498ebb405dac60bffd5d960dd643485ff571c06c3bb76ca593bd7a2758295011e27f1c35cc38c7da687935f280fad03704791d5b412387ed
6
+ metadata.gz: fe00bb7ab22dbc910ab68df1e054e5d15618d3e02d91a266c2f1fb07c48a4d95553c0071fbe3f5e3274d446d833491efd1753cca8d6ef625c25aa17747c129c6
7
+ data.tar.gz: 5cee41aff7c8c40cb54c533f6424046152fdcdfd9a971e17fc11209ec833b21e3834d77158c200430678d9683e9fcdc510aac0527838276fc502a74ab91b5451
data/Changelog.md CHANGED
@@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.5.0] - 2019-03-28
8
+ ### Added
9
+ - CoercibleValueObject Definition for better nesting of ValueObjects
10
+ - Nilable Definition as shortcut for nil OR some other definition
11
+ - Option for Keys Definition to ignore unexpected keys
12
+ ### Fixed
13
+ - Error hash was missing some errors in a few cases
14
+ - `ConformResult.error_hash` was empty for Definitions without any `Keys` Definition
15
+ ### Changed
16
+ - And Definition stops processing after the first failure
17
+
7
18
  ## [0.4.0] - 2019-03-22
8
19
  ### Added
9
20
  - Added support for default values to Keys Definition
data/README.md CHANGED
@@ -111,6 +111,28 @@ IntegerArray.new([1,2,"3"]) # => Definition::InvalidValueObjectError: Not all it
111
111
 
112
112
  You can access the conform result object via `InvalidValueObjectError#conform_result`
113
113
 
114
+ #### Nesting value Objects
115
+
116
+ Value objects can be nested by either using the value object itself as type definition,
117
+ or by using the `CoercibleValueObject` Definition. The latter would convert input
118
+ hashes that conform with the value objects schema to an instance of the value object.
119
+
120
+ ```ruby
121
+ class IntegerArray < Definition::ValueObject
122
+ definition(Definition.Each(Definition.Type(Integer)))
123
+ end
124
+
125
+ class User < Definition::ValueObject
126
+ definition(Definition.Keys do
127
+ required :username, Definition.Type(String)
128
+ required :scores, Definition.CoercibleValueObject(IntegerArray)
129
+ end)
130
+ end
131
+
132
+ object = User.new(username: "John", scores: [1,2,3])
133
+ object.scores.class.name # => IntegerArray
134
+ ```
135
+
114
136
  ### Conforming Hashes
115
137
 
116
138
  Hashes can be conformed by using the `Keys` definition. It allows you to configure
@@ -130,6 +152,25 @@ Definition.Keys do
130
152
  end
131
153
  ```
132
154
 
155
+ #### Ignoring unexpected keys
156
+
157
+ By default the `Keys` Definition does not conform with input hashes that contains
158
+ keys that are not defined in the Definition. You can set the `:ignore_extra_keys`
159
+ option to disable this.
160
+
161
+ ```ruby
162
+ schema = Definition.Keys do
163
+ option :ignore_extra_keys
164
+
165
+ required :title, Definition.NonEmptyString
166
+ optional :publication_date, Definition.Type(Time)
167
+ end
168
+
169
+ conform_result = schema.conform({title: "My first blog post", body: "Shortest one ever!", publication_date: Time.new})
170
+ conform_result.passed? # => true
171
+ conform_result.value # => {title: "My first blog post", publication_date: 2018-12-30 11:43:00 UTC}
172
+ ```
173
+
133
174
  ### Validating types
134
175
 
135
176
  This will validate that the value is of the specified type.
@@ -159,17 +200,17 @@ Definition.CoercibleType(Float).conform("0.1").value # => 0.1
159
200
  ### Combining multiple definitions with "And"
160
201
 
161
202
  ```ruby
162
- Definition.And(definition1, definition2)
203
+ Definition.And(definition1, definition2, ...)
163
204
  ```
164
205
 
165
206
  This definition will only conform if all definitions conform. The definitions will
166
207
  be processed from left to right and the output of the previous will be the input
167
- of the next.
208
+ of the next. Processing of the And-Definition stops as soon as one definition does not conform.
168
209
 
169
210
  ### Combining multiple definitions with "Or"
170
211
 
171
212
  ```ruby
172
- Definition.Or(definition1, definition2)
213
+ Definition.Or(definition1, definition2, ...)
173
214
  ```
174
215
 
175
216
  This definition will conform if at least one definition conforms. The definitions will
@@ -291,11 +332,20 @@ Definition.NonEmpty.conform({ a: 1 }) # => pass
291
332
  Definition.Nil.conform(nil) # => pass
292
333
  ```
293
334
 
335
+ #### Boolean
336
+
337
+ ```ruby
338
+ Definition.Boolean.conform(tru) # => pass
339
+ ```
340
+
294
341
  #### All types
295
342
 
296
343
  ```ruby
297
344
  Definition.Equal(5).conform(5) # => pass
298
345
  Definition.Equal("foo").conform("foo") # => pass
346
+
347
+ Definition.Nilable(Definition.Type(String)).conform(nil) # => pass
348
+ Definition.Nilable(Definition.Type(String)).conform("foo") # => pass
299
349
  ```
300
350
 
301
351
  ### Examples
@@ -33,14 +33,14 @@ module Definition
33
33
  def error_hash
34
34
  {}.tap do |error_hash|
35
35
  errors.each do |error|
36
- next if error.error_path.empty?
36
+ path_hash = if error.error_path.empty?
37
+ { "" => error }
38
+ else
39
+ error.error_path.reverse
40
+ .inject([error]) { |errors, key| { key => errors } }
41
+ end
37
42
 
38
- path_hash = error.error_path.reverse
39
- .inject([error]) { |errors, key| { key => errors } }
40
-
41
- error_hash.deep_merge!(path_hash) do |_key, old, new|
42
- old + new if old.is_a?(Array) && new.is_a?(Array) # concat arrays during deep_merge
43
- end
43
+ merge_error_hash(error_hash, path_hash)
44
44
  end
45
45
  end
46
46
  end
@@ -51,6 +51,16 @@ module Definition
51
51
 
52
52
  private
53
53
 
54
+ def merge_error_hash(hash, new_hash)
55
+ hash.deep_merge!(new_hash) do |_key, old, new|
56
+ if old.is_a?(Array) && new.is_a?(Hash) # Dont replace Hashes with arrays
57
+ new
58
+ elsif old.is_a?(Array) && new.is_a?(Array) # concat arrays during deep_merge
59
+ old + new
60
+ end
61
+ end
62
+ end
63
+
54
64
  def find_next_parent_key_error(error)
55
65
  current = error
56
66
  loop do
@@ -76,5 +76,24 @@ module Definition
76
76
  def Boolean # rubocop:disable Style/MethodName
77
77
  Types::Or.new(:boolean, Type(TrueClass), Type(FalseClass))
78
78
  end
79
+
80
+ # Example:
81
+ # CoercibleValueObject(ValueObjectClass)
82
+ def CoercibleValueObject(klass) # rubocop:disable Style/MethodName
83
+ Types::Or.new(:coercible_value_object,
84
+ Definition.Type(klass), # If its of ther correct type already this will let it pass already
85
+ And(
86
+ klass, # First make sure that the input could be coerced to 'klass'
87
+ Lambda("value_object_coercion", context: { value_object_class: klass }) do |value|
88
+ conform_with(klass.new(value)) # Actually coerce the input to klass
89
+ end
90
+ ))
91
+ end
92
+
93
+ # Example:
94
+ # Nilable(Definition.Type(Integer))
95
+ def Nilable(definition) # rubocop:disable Style/MethodName
96
+ Types::Or.new(:nullable, Nil(), definition)
97
+ end
79
98
  end
80
99
  end
@@ -51,6 +51,7 @@ module Definition
51
51
  result = definition.conform(value)
52
52
  value = result.value
53
53
  results << result
54
+ break unless result.passed?
54
55
  end
55
56
  results
56
57
  end
@@ -22,16 +22,26 @@ module Definition
22
22
  def default(key, value)
23
23
  defaults[key] = value
24
24
  end
25
+
26
+ def option(option_name)
27
+ case option_name
28
+ when :ignore_extra_keys
29
+ self.ignore_extra_keys = true
30
+ else
31
+ raise "Option #{option_name} is not defined"
32
+ end
33
+ end
25
34
  end
26
35
 
27
36
  include Dsl
28
- attr_accessor :required_definitions, :optional_definitions, :defaults
37
+ attr_accessor :required_definitions, :optional_definitions, :defaults, :ignore_extra_keys
29
38
 
30
- def initialize(name, req: {}, opt: {}, defaults: {})
39
+ def initialize(name, req: {}, opt: {}, defaults: {}, options: {})
31
40
  super(name)
32
41
  self.required_definitions = req
33
42
  self.optional_definitions = opt
34
43
  self.defaults = defaults
44
+ self.ignore_extra_keys = options.fetch(:ignore_extra_keys, false)
35
45
  end
36
46
 
37
47
  def conform(value)
@@ -50,7 +60,7 @@ module Definition
50
60
  end
51
61
 
52
62
  def conform
53
- add_extra_key_errors
63
+ add_extra_key_errors unless definition.ignore_extra_keys
54
64
  add_missing_key_errors
55
65
  values = conform_all_keys
56
66
 
@@ -6,6 +6,8 @@ module Definition
6
6
  module Types
7
7
  class Type < Base
8
8
  def initialize(name, klass, &coerce)
9
+ raise "#{klass.inspect} is not a class" unless klass.is_a?(Class)
10
+
9
11
  self.klass = klass
10
12
  self.coerce = coerce
11
13
  super(name, context: { class: klass })
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Definition
4
- VERSION = "0.4.0"
4
+ VERSION = "0.5.0"
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: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Goltermann
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-03-22 00:00:00.000000000 Z
11
+ date: 2019-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -299,7 +299,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
299
299
  version: '0'
300
300
  requirements: []
301
301
  rubyforge_project:
302
- rubygems_version: 2.7.6
302
+ rubygems_version: 2.2.2
303
303
  signing_key:
304
304
  specification_version: 4
305
305
  summary: Simple and composable validation and coercion of data structures inspired