light_service-validated_context 0.2.2 → 0.3.0
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/Gemfile.lock +1 -1
- data/README.md +91 -24
- data/lib/light_service/validated_context/expected_key_verifier.rb +3 -5
- data/lib/light_service/validated_context/fail_on_validation_error.rb +8 -0
- data/lib/light_service/validated_context/key_verifier.rb +21 -2
- data/lib/light_service/validated_context/promised_key_verifier.rb +3 -5
- data/lib/light_service/validated_context/validated_key.rb +9 -1
- data/lib/light_service/validated_context/version.rb +1 -1
- data/lib/light_service/validated_context.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c16c17a54c9867f59ccbf92ec6d969760eb396719ecda969e12d2fec2b063623
|
4
|
+
data.tar.gz: 62103f8748ab7efc04cad3253d5dc5aabd153ae62e4d022ca0427365dc2f657f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79c63b97672442aabfdc1215f1ca1e5c92293508da7ab3cea9e23e6453a1b9017f240501af69aa7423780f4b9b77b3ae5eecb53aa4b7d80dbaecb0285b94271e
|
7
|
+
data.tar.gz: 003447c4164440525f526cca2859f3151ebaeb7cc7ba4e32d3ef1f2022b3aa9418ab699400975d4bbe659444678c6df1226716959c00d6c2ccad2a944e547d13
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -47,26 +47,57 @@ require 'light_service/validated_context'
|
|
47
47
|
|
48
48
|
## Usage
|
49
49
|
|
50
|
+
The plugin enables you to pass `VK` (`ValidatedKeys`) objects as arguments to built-ins `expects` and
|
51
|
+
`promises` macros.
|
52
|
+
|
53
|
+
This is how you'd usually write an `Action` in LightService:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class ActionOne
|
57
|
+
extend LightService::Action
|
58
|
+
|
59
|
+
expects :age
|
60
|
+
promises :text
|
61
|
+
|
62
|
+
executed do |context|
|
63
|
+
validate_age!(context)
|
64
|
+
|
65
|
+
# Do something...
|
66
|
+
|
67
|
+
context.text = 'Long live and prosperity'
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.validate_age!(context)
|
71
|
+
context.fail_and_return!(':age must be an Integer') unless context.age.is_a? Integer
|
72
|
+
context.fail_and_return!('Sorry, you are too young m8') if (context.age <= 30)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
and this is how `light_service-validated_context` enables you to write
|
78
|
+
|
50
79
|
```ruby
|
51
80
|
class ActionOne
|
52
81
|
extend LightService::Action
|
53
82
|
|
54
|
-
expects VK.new(:email, Types::Strict::String)
|
55
83
|
expects VK.new(:age, Types::Coercible::Integer.constrained(gt: 30))
|
56
|
-
|
57
|
-
promises VK.new(:text, Types::Strict::String.constrained(max_size: 10).default('foobar'))
|
84
|
+
promises VK.new(:text, Types::Strict::String.constrained(max_size: 10).default('Long live and prosperity'))
|
58
85
|
|
59
86
|
executed do |context|
|
60
|
-
# something
|
87
|
+
# Do something
|
61
88
|
end
|
62
89
|
end
|
90
|
+
```
|
91
|
+
|
92
|
+
and you'll get validations for free
|
63
93
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
94
|
+
```ruby
|
95
|
+
ActionOne.execute(age: '19')
|
96
|
+
# [App::ActionOne][:age] "19" violates constraints (gt?(30, 19) failed) (LightService::ExpectedKeysNotInContextError)
|
97
|
+
ActionOne.execute(age: 37)
|
98
|
+
# LightService::Context({:age=>37, :text=>"Long live and prosperity"}, success: true, message: '', error_code: nil, skip_remaining: false, aliases: {})
|
99
|
+
ActionOne.execute(age: 37, text: 'Too long too pass the constrain')
|
100
|
+
# [App::ActionOne][:text] "Too long too pass the constrain" violates constraints (max_size?(24, "Too long too pass the constrain") failed) (LightService::PromisedKeysNotInContextError)
|
70
101
|
```
|
71
102
|
|
72
103
|
Since all the validation and coercion logic is delegated to `dry-types`, you can
|
@@ -75,7 +106,7 @@ read more about what you can achieve at https://dry-rb.org/gems/dry-types/1.2/
|
|
75
106
|
`VK` objects needs to be created with 2 positional arguments:
|
76
107
|
|
77
108
|
- key name as a symbol
|
78
|
-
- A type
|
109
|
+
- A type declaration from `dry-types` (`Tyeps` namespace is already setup for you)
|
79
110
|
|
80
111
|
`VK` and `ValidatedKey` (equivalent) are short aliases for `LightService::Context::ValidatedKey`.
|
81
112
|
They are created only if not already defined in the global space. You're free to use the namespaced
|
@@ -83,20 +114,53 @@ form to avoid name collisions.
|
|
83
114
|
|
84
115
|
You can find more usage example in `spec/support/test_doubles.rb`
|
85
116
|
|
117
|
+
### Custom validation error message
|
118
|
+
|
119
|
+
You can set a custom validation error message when instantiating a `VK`
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
VK.new(:my_integer, Types::Strict::Integer, message: 'Custom validatio message for :my_integer key')
|
123
|
+
```
|
124
|
+
|
125
|
+
Messages translated via `I18n` are supported too, following stardard `light-service`'s configuration
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
VK.new(:my_integer, Types::Strict::Integer, message: :my_integer_error_message)
|
129
|
+
```
|
130
|
+
|
131
|
+
### Raise vs fail
|
132
|
+
|
133
|
+
By default, following original `light-service` implementation, a validation error will raise
|
134
|
+
and error.
|
135
|
+
|
136
|
+
May you prefere to fail the action populating outcome's message with error message:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
class ActionFailInsteadOfRaise
|
140
|
+
extend LightService::Action
|
141
|
+
extend LightService::Context::FailOnValidationError
|
142
|
+
|
143
|
+
expects VK.new(:foo, Types::String)
|
144
|
+
|
145
|
+
executed do |context|
|
146
|
+
# do something
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
result = ActionFailInsteadOfRaise.execute(foo: 12)
|
151
|
+
result.message # Here you'll find the validation(s) message(s)
|
152
|
+
```
|
153
|
+
|
86
154
|
## Why validation matters?
|
87
155
|
|
88
|
-
In OO programming there's a rule (strict or "of thumb", IDK) that says to
|
89
|
-
invalid object
|
90
|
-
sense to my eyes whenever I'm working with an object already initialized and in memory, but I cannot
|
91
|
-
trust its internal status.
|
156
|
+
In OO programming there's a rule (strict or "of thumb", IDK) that says to never instantiate an
|
157
|
+
invalid object - given the object itself has the concept of _validity_.
|
92
158
|
|
93
|
-
|
94
|
-
|
95
|
-
(which is The state of an Action/Organizer) are mostly the same.
|
159
|
+
How many times do you find yourself working with an object already initialized and in memory, but
|
160
|
+
you cannot trust its internal status?
|
96
161
|
|
97
|
-
|
98
|
-
|
99
|
-
logical branches into my logic. E.g.:
|
162
|
+
If you cannot trust the state, given the state is internal or delegated to a context object,
|
163
|
+
you'll have to do a bunch of validation-oriented logical branches into your logic. E.g.:
|
100
164
|
|
101
165
|
```ruby
|
102
166
|
class HugAFriend
|
@@ -110,7 +174,7 @@ class HugAFriend
|
|
110
174
|
end
|
111
175
|
```
|
112
176
|
|
113
|
-
The `if` in this uber-trivial example exists just due to
|
177
|
+
The `if` in this uber-trivial example exists just due to untrusted state.
|
114
178
|
|
115
179
|
Let's re-imagine the code given an `executed` block that totally trusts the context:
|
116
180
|
|
@@ -122,6 +186,7 @@ class HugAFriend
|
|
122
186
|
expects VK.new(:friend, Types.Instance(Friend))
|
123
187
|
# Or a less usual approach could be to trust duck typing
|
124
188
|
# expects VK.new(:friend, Types::Interface(:hug))
|
189
|
+
# Actually not all friends do appreciate hugs nor other forms of physical contact :P
|
125
190
|
|
126
191
|
executed do |context|
|
127
192
|
context.friend.hug
|
@@ -131,6 +196,8 @@ end
|
|
131
196
|
|
132
197
|
## Comparison with similar gems
|
133
198
|
|
199
|
+
A brief comparison about what similar gems offer to work with validation.
|
200
|
+
|
134
201
|
This is a comparison table I've done using my own limited experience w/ other solutions
|
135
202
|
and/or reading projects' READMEs. Don't take my word for it. And if I was wrong understanding
|
136
203
|
some features, feel free to drop me a line on Mastodon [@alessandrofazzi@mastodon.uno](https://mastodon.uno/@alessandrofazzi)
|
@@ -140,11 +207,11 @@ some features, feel free to drop me a line on Mastodon [@alessandrofazzi@mastodo
|
|
140
207
|
| presence | ✅ | ✅ | ❌ | ⚠️ Only input, not output | ✅ |
|
141
208
|
| static default | ✅ | ✅ | ❌ | ✅ | ✅ |
|
142
209
|
| dynamic default | ✅ | ✅ | ❌ | ✅ | ✅ |
|
143
|
-
| raise or fail control | ❌ | ✅ | ❌ | ❓ |
|
210
|
+
| raise or fail control | ❌ | ✅ | ❌ | ❓ | ✅ |
|
144
211
|
| type check | ❌ | ✅ | ❌ | ✅ | ✅ |
|
145
212
|
| data structure type check | ❌ | ❌ | ❌ | ❌ | ✅ |
|
146
213
|
| optional | ⚠️ through `default` | ✅ through `allow_nil` (which defaults to `true` 🤔 ❓) | ❌ | ⚠️ through `default` | ✅ |
|
147
|
-
|
|
214
|
+
| 1st party code | ✅ | ✅ | ✅ | ❌ ActiveModel::Validation | ❌ Dry::Types |
|
148
215
|
|
149
216
|
|
150
217
|
## Development
|
@@ -18,14 +18,12 @@ module ValidatedContext
|
|
18
18
|
action.expected_keys
|
19
19
|
end
|
20
20
|
|
21
|
-
def throw_error_predicate(
|
21
|
+
def throw_error_predicate(_keys)
|
22
22
|
type_check_and_coerce_keys!(raw_keys)
|
23
23
|
|
24
|
-
|
24
|
+
return false if are_all_keys_valid?
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
true
|
26
|
+
should_throw_on_validation_error?
|
29
27
|
end
|
30
28
|
end
|
31
29
|
end
|
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
module ValidatedContext
|
4
4
|
module KeyVerifier
|
5
|
+
def initialize(context, action)
|
6
|
+
@validation_errors = []
|
7
|
+
|
8
|
+
super(context, action)
|
9
|
+
end
|
10
|
+
|
11
|
+
# rubocop:disable Metrics/AbcSize
|
12
|
+
# Refactoring this is out of my scope ATM
|
5
13
|
def type_check_and_coerce_keys!(keys)
|
6
14
|
errors = []
|
7
15
|
|
@@ -11,12 +19,16 @@ module ValidatedContext
|
|
11
19
|
begin
|
12
20
|
context[key.label] = key.type[context[key.label] || Dry::Types::Undefined]
|
13
21
|
rescue Dry::Types::CoercionError => e
|
14
|
-
errors <<
|
22
|
+
errors << (
|
23
|
+
LightService::Configuration.localization_adapter.failure(key.message, action) ||
|
24
|
+
"[#{action}][:#{key.label}] #{e.message}"
|
25
|
+
)
|
15
26
|
end
|
16
27
|
end
|
17
|
-
|
28
|
+
|
18
29
|
@validation_errors = errors
|
19
30
|
end
|
31
|
+
# rubocop:enable Metrics/AbcSize
|
20
32
|
|
21
33
|
def are_all_keys_valid?
|
22
34
|
@validation_errors.none?
|
@@ -25,5 +37,12 @@ module ValidatedContext
|
|
25
37
|
def error_message
|
26
38
|
@validation_errors.join(', ')
|
27
39
|
end
|
40
|
+
|
41
|
+
def should_throw_on_validation_error?
|
42
|
+
return true unless action.respond_to?(:fail_on_validation_error?) && action.fail_on_validation_error?
|
43
|
+
|
44
|
+
context.fail!(error_message)
|
45
|
+
false
|
46
|
+
end
|
28
47
|
end
|
29
48
|
end
|
@@ -18,14 +18,12 @@ module ValidatedContext
|
|
18
18
|
action.promised_keys
|
19
19
|
end
|
20
20
|
|
21
|
-
def throw_error_predicate(
|
21
|
+
def throw_error_predicate(_keys)
|
22
22
|
type_check_and_coerce_keys!(raw_keys)
|
23
23
|
|
24
|
-
|
24
|
+
return false if are_all_keys_valid?
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
true
|
26
|
+
should_throw_on_validation_error?
|
29
27
|
end
|
30
28
|
end
|
31
29
|
end
|
@@ -1,7 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ValidatedContext
|
4
|
-
ValidatedKey
|
4
|
+
class ValidatedKey
|
5
|
+
attr_reader :label, :type, :message
|
6
|
+
|
7
|
+
def initialize(label, type, message: nil)
|
8
|
+
@label = label
|
9
|
+
@type = type
|
10
|
+
@message = message
|
11
|
+
end
|
12
|
+
|
5
13
|
def to_sym
|
6
14
|
label.to_sym
|
7
15
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: light_service-validated_context
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- "'Alessandro Fazzi'"
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dry-types
|
@@ -69,6 +69,7 @@ files:
|
|
69
69
|
- lib/light_service/validated_context.rb
|
70
70
|
- lib/light_service/validated_context/context.rb
|
71
71
|
- lib/light_service/validated_context/expected_key_verifier.rb
|
72
|
+
- lib/light_service/validated_context/fail_on_validation_error.rb
|
72
73
|
- lib/light_service/validated_context/key_verifier.rb
|
73
74
|
- lib/light_service/validated_context/promised_key_verifier.rb
|
74
75
|
- lib/light_service/validated_context/validated_key.rb
|