stannum 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +25 -0
- data/README.md +85 -21
- data/lib/stannum/constraints/hashes/extra_keys.rb +7 -2
- data/lib/stannum/constraints/hashes/indifferent_extra_keys.rb +47 -0
- data/lib/stannum/constraints/hashes.rb +6 -2
- data/lib/stannum/constraints/properties/base.rb +124 -0
- data/lib/stannum/constraints/properties/do_not_match_property.rb +117 -0
- data/lib/stannum/constraints/properties/match_property.rb +117 -0
- data/lib/stannum/constraints/properties/matching.rb +112 -0
- data/lib/stannum/constraints/properties.rb +17 -0
- data/lib/stannum/constraints/tuples/extra_items.rb +1 -1
- data/lib/stannum/constraints/type.rb +1 -1
- data/lib/stannum/constraints.rb +1 -0
- data/lib/stannum/contracts/builder.rb +13 -2
- data/lib/stannum/contracts/indifferent_hash_contract.rb +13 -0
- data/lib/stannum/contracts/tuple_contract.rb +1 -1
- data/lib/stannum/entities/attributes.rb +218 -0
- data/lib/stannum/entities/constraints.rb +177 -0
- data/lib/stannum/entities/properties.rb +186 -0
- data/lib/stannum/entities.rb +13 -0
- data/lib/stannum/entity.rb +83 -0
- data/lib/stannum/errors.rb +3 -3
- data/lib/stannum/rspec/match_errors_matcher.rb +6 -6
- data/lib/stannum/rspec/validate_parameter_matcher.rb +7 -7
- data/lib/stannum/schema.rb +78 -37
- data/lib/stannum/struct.rb +12 -346
- data/lib/stannum/version.rb +1 -1
- data/lib/stannum.rb +3 -0
- metadata +25 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b13aa2cda5a99f544b2e7c281147f51b4e4fa02e394e4b6d017dcc2b1f03705d
|
4
|
+
data.tar.gz: e84dc0c10ccfd8989dcec729c485136c099257079df57cb44e82befb42ebf9b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd159ef522b377cb71e4ebc58d8e46987906a1b3a36a5e905965a5cef9dfcf09b3d3b6818519659a05a19b894b516af8d37ee17a60beb4c72fe4a5df04b2b97d
|
7
|
+
data.tar.gz: 806b0a8e067afee2185ba5164d85127543f4e3e821a2ca5582afa13135e6f1b4469f9c5cfb6461bfe3f376e17ec9d2c4580780f3b89d574764856593a660b763
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,34 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 0.3.0
|
4
|
+
|
5
|
+
### Constraints
|
6
|
+
|
7
|
+
- Implemented `Stannum::Constraints::Properties::MatchProperty`
|
8
|
+
- Implemented `Stannum::Constraints::Properties::DoNotMatchProperty`
|
9
|
+
|
10
|
+
### Contracts
|
11
|
+
|
12
|
+
- Added `#concat` to `Stannum::Contracts::Builder`.
|
13
|
+
|
14
|
+
### Entities
|
15
|
+
|
16
|
+
Implemented `Stannum::Entity`, a replacement for the existing `Stannum::Struct`.
|
17
|
+
|
18
|
+
Entitise are largely identical to structs, except for the constructor signature - entities require properties to be passed as keyword parameters, rather than as an attributes hash. Entities (and now Structs) are defined using composable modules.
|
19
|
+
|
20
|
+
- Implemented `Stannum::Entities::Attributes`.
|
21
|
+
- Implemented `Stannum::Entities::Constraints`.
|
22
|
+
- Implemented `Stannum::Entities::Properties`.
|
23
|
+
|
24
|
+
`Stannum::Struct` is now deprecated, and will be removed in a future release.
|
25
|
+
|
3
26
|
## 0.2.0
|
4
27
|
|
5
28
|
### Constraints
|
6
29
|
|
30
|
+
#### Parameter Constraints
|
31
|
+
|
7
32
|
- Implemented `Stannum::Constraints::Parameters::ExtraArguments`
|
8
33
|
- Implemented `Stannum::Constraints::Parameters::ExtraKeywords`
|
9
34
|
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ Stannum defines the following objects:
|
|
7
7
|
- [Constraints](#constraints): A validator object that responds to `#match`, `#matches?` and `#errors_for` for a given object.
|
8
8
|
- [Contracts](#contracts): A collection of constraints about an object or its properties. Obeys the `Constraint` interface.
|
9
9
|
- [Errors](#errors): Data object for storing validation errors. Supports arbitrary nesting of errors.
|
10
|
-
- [
|
10
|
+
- [Entities](#entities): Defines a mutable data object with a specified set of typed attributes.
|
11
11
|
|
12
12
|
## About
|
13
13
|
|
@@ -17,11 +17,11 @@ First and foremost, Stannum provides you with the tools to validate your data. U
|
|
17
17
|
|
18
18
|
Finally, you can combine your constraints into a `Stannum::Contract` to combine multiple validations of your object and its properties. Stannum provides pre-defined contracts for asserting on objects, `Array`s, `Hash`es, and even method parameters.
|
19
19
|
|
20
|
-
Stannum also defines the `Stannum::
|
20
|
+
Stannum also defines the `Stannum::Entity` module for defining structured data entities that are not tied to any framework or datastore. Stannum entities have more functionality and a friendlier interface than a core library `Struct`, provide more structure than a `Hash` or hash-like object (such as an `OpenStruct` or `Hashie::Mash`), and are completely independent from the source of the data. Need to load seed data from a YAML configuration file, perform operations in a SQL database, cross-reference with a MongoDB data store, and use an in-memory data array for lightning-fast tests? A `Stannum::Entity` won't fight you every step of the way.
|
21
21
|
|
22
22
|
### Why Stannum?
|
23
23
|
|
24
|
-
Stannum is not tied to any framework. You can create constraints and contracts to validate Ruby objects and
|
24
|
+
Stannum is not tied to any framework. You can create constraints and contracts to validate Ruby objects and Entities, data structures such as Arrays, Hashes, and Sets, and even framework objects such as `ActiveRecord::Model`s and `Mongoid::Document`s.
|
25
25
|
|
26
26
|
Still, most projects and applications use one framework to handle their data. Why use Stannum constraints?
|
27
27
|
|
@@ -33,7 +33,7 @@ Still, most projects and applications use one framework to handle their data. Wh
|
|
33
33
|
|
34
34
|
### Compatibility
|
35
35
|
|
36
|
-
Stannum is tested against Ruby (MRI) 2.
|
36
|
+
Stannum is tested against Ruby (MRI) 2.7 through 3.2.
|
37
37
|
|
38
38
|
### Documentation
|
39
39
|
|
@@ -331,7 +331,7 @@ contract.does_not_match?({ color: 'blue', shape: 'square'})
|
|
331
331
|
|
332
332
|
Note that for an object that partially matches the contract, both `#matches?` and `#does_not_match?` methods will return false. If you want to check whether **any** of the constraints do not match the object, use the `#matches?` method and apply the `!` boolean negation operator (or switch from an `if` to an `unless`).
|
333
333
|
|
334
|
-
####
|
334
|
+
#### Constraining Properties
|
335
335
|
|
336
336
|
Constraints can also define constraints on the *properties* of the matched object. This is a powerful feature for defining validations on objects and nested data structures. To define a property constraint, use the `property` macro in a contract constructor block, or use the `#add_property_constraint` method on an existing contract.
|
337
337
|
|
@@ -725,16 +725,20 @@ errors.first.message
|
|
725
725
|
|
726
726
|
Stannum uses the strategy pattern to determine how error messages are generated. You can pass the `strategy:` keyword to `#with_messages` to force Stannum to use the specified strategy, or set the `Stannum::Messages.strategy` property to define the default for your application. The default strategy for Stannum uses an I18n-like configuration file to define messages based on the type and optionally the data for each error.
|
727
727
|
|
728
|
-
<a id="
|
728
|
+
<a id="entities"></a>
|
729
729
|
|
730
|
-
###
|
730
|
+
### Entities
|
731
731
|
|
732
|
-
While constraints and contracts are used to validate data,
|
732
|
+
While constraints and contracts are used to validate data, entities are used to define and structure that data. Each `Stannum::Entity` contains a specific set of attributes, and each attribute has a type definition that is a `Class` or `Module` or the name of a Class or Module.
|
733
733
|
|
734
|
-
|
734
|
+
Entities are defined by creating a new class and including `Stannum::Entity`:
|
735
735
|
|
736
736
|
```ruby
|
737
|
+
require 'stannum'
|
738
|
+
|
737
739
|
class Gadget
|
740
|
+
include Stannum::Entity
|
741
|
+
|
738
742
|
attribute :name, String
|
739
743
|
attribute :description, String, optional: true
|
740
744
|
attribute :quantity, Integer, default: 0
|
@@ -763,21 +767,21 @@ gadget[:description]
|
|
763
767
|
|
764
768
|
Our `Gadget` class has three attributes: `#name`, `#description`, and `#quantity`, which we are defining using the `.attribute` class method.
|
765
769
|
|
766
|
-
We can initialize a gadget with values by passing the desired attributes to `.new`. We can read or write the attributes using either dot `.` notation or `#[]` notation. Finally, we can access all of a
|
770
|
+
We can initialize a gadget with values by passing the desired attributes to `.new`. We can read or write the attributes using either dot `.` notation or `#[]` notation. Finally, we can access all of a entity's attributes and values using the `#attributes` method.
|
767
771
|
|
768
|
-
`Stannum::
|
772
|
+
`Stannum::Entity` defines a number of helper methods for interacting with a entity's attributes:
|
769
773
|
|
770
774
|
- `#[](attribute)`: Returns the value of the given attribute.
|
771
775
|
- `#[]=(attribute, value)`: Writes the given value to the given attribute.
|
772
|
-
- `#assign_attributes(values)`: Updates the
|
776
|
+
- `#assign_attributes(values)`: Updates the entity's attributes using the given values. If an attribute is not given, that value is unchanged.
|
773
777
|
- `#attributes`: Returns a hash containing the attribute keys and values.
|
774
|
-
- `#attributes=(values)`: Sets the
|
778
|
+
- `#attributes=(values)`: Sets the entity's attributes to the given values. If an attribute is not given, that attribute is set to `nil`.
|
775
779
|
|
776
|
-
For all of the above methods, if a given attribute is invalid or the attribute is not defined on the
|
780
|
+
For all of the above methods, if a given attribute is invalid or the attribute is not defined on the entity, an `ArgumentError` will be raised.
|
777
781
|
|
778
782
|
#### Attributes
|
779
783
|
|
780
|
-
A
|
784
|
+
A entity's attributes are defined using the `.attribute` class method, and can be accessed and enumerated using the `.attributes` class method on the entity class or via the `::Attributes` constant. Internally, each attribute is represented by a `Stannum::Attribute` instance, which stores the attribute's `:name`, `:type`, and `:attributes`.
|
781
785
|
|
782
786
|
```ruby
|
783
787
|
Gadget::Attributes
|
@@ -796,11 +800,11 @@ Gadget.attributes[:quantity].options
|
|
796
800
|
|
797
801
|
##### Default Values
|
798
802
|
|
799
|
-
|
803
|
+
Entities can define default values for attributes by passing a `:default` value to the `.attribute` call.
|
800
804
|
|
801
805
|
```ruby
|
802
806
|
class LightsCounter
|
803
|
-
include Stannum::
|
807
|
+
include Stannum::Entity
|
804
808
|
|
805
809
|
attribute :count, Integer, default: 4
|
806
810
|
end
|
@@ -811,11 +815,11 @@ LightsCounter.new.count
|
|
811
815
|
|
812
816
|
##### Optional Attributes
|
813
817
|
|
814
|
-
|
818
|
+
Entity classes can also mark attributes as `optional`. When an entity is validated (see [Validation](#entities-validation), below), optional attributes will pass with a value of `nil`.
|
815
819
|
|
816
820
|
```ruby
|
817
821
|
class WhereWeAreGoing
|
818
|
-
include Stannum::
|
822
|
+
include Stannum::Entity
|
819
823
|
|
820
824
|
attribute :roads, Object, optional: true
|
821
825
|
end
|
@@ -823,11 +827,11 @@ end
|
|
823
827
|
|
824
828
|
`Stannum` supports both `:optional` and `:required` as keys. Passing either `optional: true` or `required: false` will mark the attribute as optional. Attributes are required by default.
|
825
829
|
|
826
|
-
<a id="
|
830
|
+
<a id="entities-validation"></a>
|
827
831
|
|
828
832
|
#### Validation
|
829
833
|
|
830
|
-
Each `Stannum::
|
834
|
+
Each `Stannum::Entity` automatically generates a contract that can be used to validate instances of the entity class. The contract can be accessed using the `.contract` class method or via the `::Contract` constant.
|
831
835
|
|
832
836
|
```ruby
|
833
837
|
class Gadget
|
@@ -1091,6 +1095,66 @@ constraint.matches?(:a_symbol)
|
|
1091
1095
|
#=> true
|
1092
1096
|
```
|
1093
1097
|
|
1098
|
+
#### Property Constraints
|
1099
|
+
|
1100
|
+
Property constraints match against the properties of the object.
|
1101
|
+
|
1102
|
+
**Do Not Match Property Constraint**
|
1103
|
+
|
1104
|
+
Matches if none of the values of the given properties are equal to the value of the expected property.
|
1105
|
+
|
1106
|
+
```ruby
|
1107
|
+
UpdatePassword = Struct.new(:old_password, :new_password)
|
1108
|
+
constraint = Stannum::Constraints::Properties::DoNotMatchProperty.new(
|
1109
|
+
:old_password,
|
1110
|
+
:new_password
|
1111
|
+
)
|
1112
|
+
|
1113
|
+
params = UpdatePassword.new('tronlives', 'ifightfortheusers')
|
1114
|
+
constraint.matches?(params)
|
1115
|
+
#=> true
|
1116
|
+
|
1117
|
+
params = UpdatePassword.new('tronlives', 'tronlives')
|
1118
|
+
constraint.matches?(params)
|
1119
|
+
#=> false
|
1120
|
+
constraint.errors_for(params)
|
1121
|
+
#=> [
|
1122
|
+
{
|
1123
|
+
path: [:confirmation],
|
1124
|
+
type: 'stannum.constraints.is_equal_to',
|
1125
|
+
data: { expected: '[FILTERED]', actual: '[FILTERED]' }
|
1126
|
+
}
|
1127
|
+
]
|
1128
|
+
```
|
1129
|
+
|
1130
|
+
**Match Property Constraint**
|
1131
|
+
|
1132
|
+
Matches if all the values of the given properties are equal to the value of the expected property.
|
1133
|
+
|
1134
|
+
```ruby
|
1135
|
+
ConfirmPassword = Struct.new(:password, :confirmation)
|
1136
|
+
constraint = Stannum::Constraints::Properties::MatchProperty.new(
|
1137
|
+
:password,
|
1138
|
+
:confirmation
|
1139
|
+
)
|
1140
|
+
|
1141
|
+
params = ConfirmPassword.new('tronlives', 'ifightfortheusers')
|
1142
|
+
constraint.matches?(params)
|
1143
|
+
#=> false
|
1144
|
+
constraint.errors_for(params)
|
1145
|
+
#=> [
|
1146
|
+
{
|
1147
|
+
path: [:confirmation],
|
1148
|
+
type: 'stannum.constraints.is_not_equal_to',
|
1149
|
+
data: { expected: '[FILTERED]', actual: '[FILTERED]' }
|
1150
|
+
}
|
1151
|
+
]
|
1152
|
+
|
1153
|
+
params = ConfirmPassword.new('tronlives', 'tronlives')
|
1154
|
+
constraint.matches?(params)
|
1155
|
+
#=> true
|
1156
|
+
```
|
1157
|
+
|
1094
1158
|
#### Type Constraints
|
1095
1159
|
|
1096
1160
|
Stannum also defines a set of built-in type constraints. Unless otherwise noted, these are identical to a [Type Constraint](#builtin-constraints-type) with the given Class.
|
@@ -6,12 +6,17 @@ require 'stannum/support/coercion'
|
|
6
6
|
module Stannum::Constraints::Hashes
|
7
7
|
# Constraint for validating the keys of a hash-like object.
|
8
8
|
#
|
9
|
+
# When using this constraint, the keys must be strings or symbols, and the
|
10
|
+
# hash keys must be of the same type. A constraint configured with string keys
|
11
|
+
# will not match a hash with symbol keys, and vice versa.
|
12
|
+
#
|
9
13
|
# @example
|
10
|
-
# keys = %[fuel mass size]
|
14
|
+
# keys = %i[fuel mass size]
|
11
15
|
# constraint = Stannum::Constraints::Hashes::ExpectedKeys.new(keys)
|
12
16
|
#
|
13
17
|
# constraint.matches?({}) #=> true
|
14
18
|
# constraint.matches?({ fuel: 'Monopropellant' }) #=> true
|
19
|
+
# constraint.matches?({ 'fuel' => 'Monopropellant' }) #=> false
|
15
20
|
# constraint.matches?({ electric: true, fuel: 'Xenon' }) #=> false
|
16
21
|
# constraint.matches?({ fuel: 'LF/O', mass: '1 ton', size: 'Medium' })
|
17
22
|
# #=> true
|
@@ -68,7 +73,7 @@ module Stannum::Constraints::Hashes
|
|
68
73
|
errors
|
69
74
|
end
|
70
75
|
|
71
|
-
# @return [
|
76
|
+
# @return [Set] the expected keys.
|
72
77
|
def expected_keys
|
73
78
|
keys = options[:expected_keys]
|
74
79
|
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/hashes'
|
4
|
+
require 'stannum/constraints/hashes/extra_keys'
|
5
|
+
|
6
|
+
module Stannum::Constraints::Hashes
|
7
|
+
# Constraint for validating the keys of an indifferent hash-like object.
|
8
|
+
#
|
9
|
+
# When using this constraint, the keys must be strings or symbols, but it does
|
10
|
+
# not matter which - a constraint configured with string keys will match a
|
11
|
+
# hash with symbol keys, and vice versa.
|
12
|
+
#
|
13
|
+
# @example
|
14
|
+
# keys = %i[fuel mass size]
|
15
|
+
# constraint = Stannum::Constraints::Hashes::ExpectedKeys.new(keys)
|
16
|
+
#
|
17
|
+
# constraint.matches?({}) #=> true
|
18
|
+
# constraint.matches?({ fuel: 'Monopropellant' }) #=> true
|
19
|
+
# constraint.matches?({ 'fuel' => 'Monopropellant' }) #=> true
|
20
|
+
# constraint.matches?({ electric: true, fuel: 'Xenon' }) #=> false
|
21
|
+
# constraint.matches?({ fuel: 'LF/O', mass: '1 ton', size: 'Medium' })
|
22
|
+
# #=> true
|
23
|
+
# constraint.matches?(
|
24
|
+
# { fuel: 'LF', mass: '2 tons', nuclear: true, size: 'Medium' }
|
25
|
+
# )
|
26
|
+
# #=> false
|
27
|
+
class IndifferentExtraKeys < Stannum::Constraints::Hashes::ExtraKeys
|
28
|
+
# @return [Set] the expected keys.
|
29
|
+
def expected_keys
|
30
|
+
keys = options[:expected_keys]
|
31
|
+
|
32
|
+
return indifferent_keys_for(keys) unless keys.is_a?(Proc)
|
33
|
+
|
34
|
+
indifferent_keys_for(keys.call)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def indifferent_keys_for(keys)
|
40
|
+
Set.new(
|
41
|
+
keys.reduce([]) do |ary, key|
|
42
|
+
ary << key.to_s << key.intern
|
43
|
+
end
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -5,7 +5,11 @@ require 'stannum/constraints'
|
|
5
5
|
module Stannum::Constraints
|
6
6
|
# Namespace for Hash-specific constraints.
|
7
7
|
module Hashes
|
8
|
-
autoload :ExtraKeys,
|
9
|
-
|
8
|
+
autoload :ExtraKeys,
|
9
|
+
'stannum/constraints/hashes/extra_keys'
|
10
|
+
autoload :IndifferentExtraKeys,
|
11
|
+
'stannum/constraints/hashes/indifferent_extra_keys'
|
12
|
+
autoload :IndifferentKey,
|
13
|
+
'stannum/constraints/hashes/indifferent_key'
|
10
14
|
end
|
11
15
|
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sleeping_king_studios/tools/toolbelt'
|
4
|
+
|
5
|
+
require 'stannum/constraints/properties'
|
6
|
+
|
7
|
+
module Stannum::Constraints::Properties
|
8
|
+
# Abstract base class for property constraints.
|
9
|
+
class Base < Stannum::Constraints::Base
|
10
|
+
# Default parameter names to filter out of errors.
|
11
|
+
FILTERED_PARAMETERS = %i[
|
12
|
+
passw
|
13
|
+
secret
|
14
|
+
token
|
15
|
+
_key
|
16
|
+
crypt
|
17
|
+
salt
|
18
|
+
certificate
|
19
|
+
otp
|
20
|
+
ssn
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
# @param property_names [Array<String, Symbol>] the name or names of the
|
24
|
+
# properties to match.
|
25
|
+
# @param options [Hash<Symbol, Object>] configuration options for the
|
26
|
+
# constraint. Defaults to an empty Hash.
|
27
|
+
#
|
28
|
+
# @option options allow_empty [true, false] if true, will match against an
|
29
|
+
# object with empty property values, such as an empty string.
|
30
|
+
# @option options allow_nil [true, false] if true, will match against an
|
31
|
+
# object with nil property values.
|
32
|
+
def initialize(*property_names, **options)
|
33
|
+
@property_names = property_names
|
34
|
+
|
35
|
+
validate_property_names
|
36
|
+
|
37
|
+
super(
|
38
|
+
allow_empty: !!options[:allow_empty],
|
39
|
+
allow_nil: !!options[:allow_nil],
|
40
|
+
property_names: property_names,
|
41
|
+
**options
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Array<String, Symbol>] the name or names of the properties to
|
46
|
+
# match.
|
47
|
+
attr_reader :property_names
|
48
|
+
|
49
|
+
# @return [true, false] if true, will match against an object with empty
|
50
|
+
# property values, such as an empty string.
|
51
|
+
def allow_empty?
|
52
|
+
options[:allow_empty]
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [true, false] if true, will match against an object with nil
|
56
|
+
# property values.
|
57
|
+
def allow_nil?
|
58
|
+
options[:allow_nil]
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def can_match_properties?(actual)
|
64
|
+
actual.respond_to?(:[])
|
65
|
+
end
|
66
|
+
|
67
|
+
def each_property(actual)
|
68
|
+
return to_enum(__method__, actual) unless block_given?
|
69
|
+
|
70
|
+
property_names.each do |property_name|
|
71
|
+
yield property_name, actual[property_name]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def empty?(value)
|
76
|
+
value.respond_to?(:empty?) && value.empty?
|
77
|
+
end
|
78
|
+
|
79
|
+
def filter_parameters?
|
80
|
+
return @filter_parameters unless @filter_parameters.nil?
|
81
|
+
|
82
|
+
filters = filtered_parameters.map { |param| Regexp.new(param.to_s) }
|
83
|
+
|
84
|
+
@filter_parameters =
|
85
|
+
property_names.any? do |property_name|
|
86
|
+
filters.any? { |filter| filter.match?(property_name.to_s) }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def filtered_parameters
|
91
|
+
return Rails.configuration.filter_parameters if defined?(Rails)
|
92
|
+
|
93
|
+
FILTERED_PARAMETERS
|
94
|
+
end
|
95
|
+
|
96
|
+
def invalid_object_errors(errors)
|
97
|
+
errors.add(
|
98
|
+
Stannum::Constraints::Signature::TYPE,
|
99
|
+
methods: %i[[]],
|
100
|
+
missing: %i[[]]
|
101
|
+
)
|
102
|
+
end
|
103
|
+
|
104
|
+
def skip_property?(value)
|
105
|
+
(allow_empty? && empty?(value)) || (allow_nil? && value.nil?)
|
106
|
+
end
|
107
|
+
|
108
|
+
def tools
|
109
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
110
|
+
end
|
111
|
+
|
112
|
+
def validate_property_names
|
113
|
+
if property_names.empty?
|
114
|
+
raise ArgumentError, "property names can't be empty"
|
115
|
+
end
|
116
|
+
|
117
|
+
property_names.each.with_index do |property_name, index|
|
118
|
+
tools
|
119
|
+
.assertions
|
120
|
+
.validate_name(property_name, as: "property name at #{index}")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/properties'
|
4
|
+
require 'stannum/constraints/properties/matching'
|
5
|
+
|
6
|
+
module Stannum::Constraints::Properties
|
7
|
+
# Compares the properties of the given object with the specified property.
|
8
|
+
#
|
9
|
+
# If none of the property values equal the expected value, the constraint will
|
10
|
+
# match the object; otherwise, if there are any matching values, the
|
11
|
+
# constraint will not match.
|
12
|
+
#
|
13
|
+
# @example Using an Properties::Match constraint
|
14
|
+
# UpdatePassword = Struct.new(:old_password, :new_password)
|
15
|
+
# constraint = Stannum::Constraints::Properties::DoNotMatchProperty.new(
|
16
|
+
# :old_password,
|
17
|
+
# :new_password
|
18
|
+
# )
|
19
|
+
#
|
20
|
+
# params = UpdatePassword.new('tronlives', 'ifightfortheusers')
|
21
|
+
# constraint.matches?(params)
|
22
|
+
# #=> true
|
23
|
+
#
|
24
|
+
# params = UpdatePassword.new('tronlives', 'tronlives')
|
25
|
+
# constraint.matches?(params)
|
26
|
+
# #=> false
|
27
|
+
# constraint.errors_for(params)
|
28
|
+
# #=> [
|
29
|
+
# {
|
30
|
+
# path: [:confirmation],
|
31
|
+
# type: 'stannum.constraints.is_equal_to',
|
32
|
+
# data: { expected: '[FILTERED]', actual: '[FILTERED]' }
|
33
|
+
# }
|
34
|
+
# ]
|
35
|
+
class DoNotMatchProperty < Stannum::Constraints::Properties::Matching
|
36
|
+
# The :type of the error generated for a matching object.
|
37
|
+
NEGATED_TYPE = Stannum::Constraints::Equality::TYPE
|
38
|
+
|
39
|
+
# The :type of the error generated for a non-matching object.
|
40
|
+
TYPE = Stannum::Constraints::Equality::NEGATED_TYPE
|
41
|
+
|
42
|
+
# @return [true, false] true if the property values match the reference
|
43
|
+
# property value; otherwise false.
|
44
|
+
def does_not_match?(actual)
|
45
|
+
return false unless can_match_properties?(actual)
|
46
|
+
|
47
|
+
expected = expected_value(actual)
|
48
|
+
|
49
|
+
return false if skip_property?(expected)
|
50
|
+
|
51
|
+
each_non_matching_property(
|
52
|
+
actual: actual,
|
53
|
+
expected: expected,
|
54
|
+
include_all: true
|
55
|
+
)
|
56
|
+
.none?
|
57
|
+
end
|
58
|
+
|
59
|
+
# (see Stannum::Constraints::Base#errors_for)
|
60
|
+
def errors_for(actual, errors: nil)
|
61
|
+
errors ||= Stannum::Errors.new
|
62
|
+
|
63
|
+
return invalid_object_errors(errors) unless can_match_properties?(actual)
|
64
|
+
|
65
|
+
expected = expected_value(actual)
|
66
|
+
matching = each_matching_property(actual: actual, expected: expected)
|
67
|
+
|
68
|
+
return generic_errors(errors) if matching.count.zero?
|
69
|
+
|
70
|
+
matching.each do |property_name, _|
|
71
|
+
errors[property_name].add(type, message: message)
|
72
|
+
end
|
73
|
+
|
74
|
+
errors
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [true, false] false if any of the property values match the
|
78
|
+
# reference property value; otherwise true.
|
79
|
+
def matches?(actual)
|
80
|
+
return false unless can_match_properties?(actual)
|
81
|
+
|
82
|
+
expected = expected_value(actual)
|
83
|
+
|
84
|
+
return true if skip_property?(expected)
|
85
|
+
|
86
|
+
each_matching_property(actual: actual, expected: expected).none?
|
87
|
+
end
|
88
|
+
alias match? matches?
|
89
|
+
|
90
|
+
# (see Stannum::Constraints::Base#negated_errors_for)
|
91
|
+
def negated_errors_for(actual, errors: nil) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
92
|
+
errors ||= Stannum::Errors.new
|
93
|
+
|
94
|
+
return invalid_object_errors(errors) unless can_match_properties?(actual)
|
95
|
+
|
96
|
+
expected = expected_value(actual)
|
97
|
+
matching = each_non_matching_property(
|
98
|
+
actual: actual,
|
99
|
+
expected: expected,
|
100
|
+
include_all: true
|
101
|
+
)
|
102
|
+
|
103
|
+
return generic_errors(errors) if matching.count.zero?
|
104
|
+
|
105
|
+
matching.each do |property_name, value|
|
106
|
+
errors[property_name].add(
|
107
|
+
negated_type,
|
108
|
+
message: negated_message,
|
109
|
+
expected: filter_parameters? ? '[FILTERED]' : expected_value(actual),
|
110
|
+
actual: filter_parameters? ? '[FILTERED]' : value
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
errors
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|