st_validation 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d05c5f917d6f84aa0156cde50c68081d41d0fc8328723008785c6056e3a9bf4
4
- data.tar.gz: bff39f336727f38a08f86f7ad8b282e9d610937cb4cb0ef564c302bef826abe5
3
+ metadata.gz: 187366e9fd5c511c575246ccfbc2034ca4e06479b9d29d9d0d462fb58fab4d45
4
+ data.tar.gz: 43691275ad1ebf833137919ac3096289129de3ea7da6eb06b750e0c9c1cffae2
5
5
  SHA512:
6
- metadata.gz: '0768162330257a317bbbc80b75ebafabbf8282784ee4a81d39cf1556672856b4c977fffc77e0e207c6556e707961a6781e4c258c765f336ee4624a31d4737959'
7
- data.tar.gz: 02e9b0e68e2ca20cb0a89a3f3e366dbd7c141bd33170331a1c4c7849df5101af9d10df32e475ed87f695065ec02ee9accb677a0231668c53a7419c41ec5e21d0
6
+ metadata.gz: f18f89aeea5ac68140c1f2c99ca1ea76201a9d8303688eb1318ca373abb2db116a1fc73cf70c29adc15ecdc0d2f0f2d81d3a989669397df701f66ae195e7450c
7
+ data.tar.gz: 1de6b46630ef92a01706d6e37d76f47f63a76f01b4a9540a3a4a99a7b13bd748d19fb37b5804abd8eeca8e9d38522840231c3012220004350158733aa2827062
@@ -5,3 +5,6 @@ cache: bundler
5
5
  rvm:
6
6
  - 2.5.3
7
7
  before_install: gem install bundler -v 2.0.2
8
+ script:
9
+ - bundle exec rake build
10
+ - bundle exec rake spec
data/README.org CHANGED
@@ -1,5 +1,7 @@
1
1
  #+TITLE: St. Validation
2
2
 
3
+ [[https://rubygems.org/gems/st_validation][https://badge.fury.io/rb/st_validation.svg]] [[https://travis-ci.org/Nondv/st_validation.rb][https://travis-ci.org/Nondv/st_validation.rb.svg?branch=master]]
4
+
3
5
  Incredibly simple and customisable validation DSL
4
6
 
5
7
  #+BEGIN_SRC ruby
@@ -37,6 +39,8 @@ is_valid_user.call(
37
39
  - [[#maybe-optional-values][Maybe (optional values)]]
38
40
  - [[#tinkering-dsl][Tinkering DSL]]
39
41
  - [[#important-note][Important note!]]
42
+ - [[#explain][explain]]
43
+ - [[#testing][Testing]]
40
44
  - [[#contributing][Contributing]]
41
45
  - [[#license][License]]
42
46
 
@@ -182,6 +186,45 @@ The process stops when no transformation changed the blueprint.
182
186
 
183
187
  Do *not* rely on order; it's not guarantueed.
184
188
 
189
+ ** explain
190
+
191
+ For development purposes there's a =#explain= method defined in =StValidation::AbstractValidator=.
192
+ The purpose of it is to show why a value didn't pass validation.
193
+
194
+ For your custom validators you should implement =#generate_explanation(value)= method.
195
+
196
+ #+BEGIN_SRC ruby
197
+ validator = StValidation.build(
198
+ id: Integer,
199
+ email: String,
200
+ )
201
+
202
+ validator.explain(
203
+ id: '123',
204
+ email: 'user@example.com'
205
+ )
206
+ # ==> { id: 'Expected Integer got String' }
207
+ #+END_SRC
208
+
209
+ ** Testing
210
+
211
+ There's a rspec matcher:
212
+
213
+ #+BEGIN_SRC ruby
214
+ require 'st_validation/rspec'
215
+
216
+ RSpec.describe 'user hash' do
217
+ it 'matches schema' do
218
+ user = build_user_hash
219
+ expect(user).to pass_st_validation(
220
+ id: Integer,
221
+ name: String,
222
+ age: Set[NilClass, Integer]
223
+ )
224
+ end
225
+ end
226
+
227
+ #+END_SRC
185
228
 
186
229
  * Contributing
187
230
 
@@ -1,11 +1,23 @@
1
1
  module StValidation
2
2
  class AbstractValidator
3
- def call
3
+ def call(_value)
4
4
  raise 'implement this'
5
5
  end
6
6
 
7
7
  def to_proc
8
8
  ->(x) { call(x) }
9
9
  end
10
+
11
+ def explain(value)
12
+ generate_explanation(value)
13
+ rescue StandardError => error
14
+ "#explain failed with #{error.class}: #{error.message}"
15
+ end
16
+
17
+ private
18
+
19
+ def generate_explanation(_value)
20
+ raise "#{self.class}#generate_explanation is not implemented"
21
+ end
10
22
  end
11
23
  end
@@ -0,0 +1,21 @@
1
+ require 'pp'
2
+ require 'rspec/expectations'
3
+
4
+ RSpec::Matchers.define :pass_st_validation do |validator|
5
+ unless validator.is_a?(StValidation::AbstractValidator)
6
+ validator = StValidation.build(validator)
7
+ end
8
+
9
+ match do |actual|
10
+ validator.call(actual)
11
+ end
12
+
13
+ failure_message do |actual|
14
+ output = PP.pp(validator.explain(actual), '')
15
+ "value didn't pass St. Validation. #explain output:\n#{output}"
16
+ end
17
+
18
+ failure_message_when_negated do |_actual|
19
+ "value wasn't supposed pass St. Validation"
20
+ end
21
+ end
@@ -1,5 +1,25 @@
1
+ require_relative 'abstract_validator'
2
+
1
3
  module StValidation
2
4
  class ValidatorFactory
5
+ class ProcValidatorWrapper < AbstractValidator
6
+ def initialize(proc_object)
7
+ @proc_object = proc_object
8
+ end
9
+
10
+ def call(value)
11
+ @proc_object.call(value)
12
+ end
13
+
14
+ private
15
+
16
+ def generate_explanation(value)
17
+ return nil if call(value)
18
+
19
+ @proc_object.source_location
20
+ end
21
+ end
22
+
3
23
  attr_reader :transformations
4
24
 
5
25
  def initialize(transformations = [])
@@ -13,8 +33,9 @@ module StValidation
13
33
  result = transformations.reduce(result) { |res, t| t.call(res, self) }
14
34
  break if result == old
15
35
  end
36
+ result = ProcValidatorWrapper.new(result) if result.is_a?(Proc)
16
37
 
17
- raise InvalidBlueprintError unless result.is_a?(Proc) || result.is_a?(AbstractValidator)
38
+ raise InvalidBlueprintError unless result.is_a?(AbstractValidator)
18
39
 
19
40
  result
20
41
  end
@@ -12,6 +12,14 @@ module StValidation
12
12
 
13
13
  value.all?(&@validator)
14
14
  end
15
+
16
+ private
17
+
18
+ def generate_explanation(value)
19
+ return 'not an array' unless value.is_a?(Array)
20
+
21
+ value.map { |e| @validator.explain(e) }
22
+ end
15
23
  end
16
24
  end
17
25
  end
@@ -10,6 +10,14 @@ module StValidation
10
10
  def call(value)
11
11
  value.is_a?(@klass)
12
12
  end
13
+
14
+ private
15
+
16
+ def generate_explanation(value)
17
+ return nil if call(value)
18
+
19
+ "expected #{@klass} got #{value.class}"
20
+ end
13
21
  end
14
22
  end
15
23
  end
@@ -8,8 +8,7 @@ module StValidation
8
8
  end
9
9
 
10
10
  def call(value)
11
- return false unless value.is_a?(Hash) &&
12
- (value.keys - validators.keys).empty?
11
+ return false unless value.is_a?(Hash) && extra_keys(value).empty?
13
12
 
14
13
  validators.each { |k, v| return false unless v.call(value[k]) }
15
14
  true
@@ -18,6 +17,22 @@ module StValidation
18
17
  private
19
18
 
20
19
  attr_reader :validators
20
+
21
+ def generate_explanation(value)
22
+ return 'not a hash' unless value.is_a?(Hash)
23
+
24
+ result = validators
25
+ .reduce({}) { |a, (k, v)| a.merge(k => v.explain(value[k])) }
26
+ .compact
27
+
28
+ extra_keys(value).each { |k| result[k] = 'extra key detected' }
29
+
30
+ result.empty? ? nil : result
31
+ end
32
+
33
+ def extra_keys(hash)
34
+ hash.keys - validators.keys
35
+ end
21
36
  end
22
37
  end
23
38
  end
@@ -16,6 +16,12 @@ module StValidation
16
16
  def call(value)
17
17
  @validators.any? { |v| v.call(value) }
18
18
  end
19
+
20
+ private
21
+
22
+ def generate_explanation(value)
23
+ @validators.map { |v| v.explain(value) }
24
+ end
19
25
  end
20
26
  end
21
27
  end
@@ -1,3 +1,3 @@
1
1
  module StValidation
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: st_validation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Non
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-07 00:00:00.000000000 Z
11
+ date: 2019-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -71,6 +71,7 @@ files:
71
71
  - lib/st_validation.rb
72
72
  - lib/st_validation/abstract_validator.rb
73
73
  - lib/st_validation/errors.rb
74
+ - lib/st_validation/rspec.rb
74
75
  - lib/st_validation/validator_factory.rb
75
76
  - lib/st_validation/validators/array_validator.rb
76
77
  - lib/st_validation/validators/class_validator.rb