smart_schema 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0a86fa55dff1a9608dd4bd87eccb26eb9f9cf7ea1fd5699e800754d1c006814d
4
- data.tar.gz: 1bcdce646a6ea5db5494493c024bff6fbdab390bcc4445d7801047a644c272cd
3
+ metadata.gz: ad6ef4371489ef23da9a5b48a6acd744f3b471492407ab5e5b0201b35e5a4d76
4
+ data.tar.gz: 05f6c88bb367f406b4d401a5ac2349e17cda800ed46d5be6555b292c8b782d5b
5
5
  SHA512:
6
- metadata.gz: 17f908f2ddcc72d096093af737b9b80214d3f0cbca2fd7064e95b81036731e9f94205cad5b5a8db25c4eac3b61bb22a1e670f7f3876540b9c73518da5fc279ad
7
- data.tar.gz: 17361851cca2d1d4380ceb82c8fc3a138203fc075ac5286a1de87ab9e0fcf76dd095b6aa2d61347a89157aea806404ec5cd2347641f313dbb0ad8ac4faacdabc
6
+ metadata.gz: 0ec964a29f97595aa11087531ca29872f041c13182edfa6c8766e16472218db0cb9b1a91886a28cc7484761ab5dc5ec9a6dcbc9087d8ed3a35931d927f7582ad
7
+ data.tar.gz: 9d64b474fa69b251a0f88abd6c82f01cd6d629c6bee51c113d15481f53df77c296c4e510c5af904bacc5a4be570b080517f6721afd6180e211b9c90e39b5c68e
@@ -6,6 +6,7 @@ inherit_gem:
6
6
 
7
7
  AllCops:
8
8
  TargetRubyVersion: 2.7.1
9
+ NewCops: enable
9
10
  Include:
10
11
  - lib/**/*.rb
11
12
  - spec/**/*.rb
@@ -1,6 +1,20 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ # [0.3.0] - 2020-12-03
5
+ ### Added
6
+ - Support for *strict* and *non-strict* schemas;
7
+ - `strict!` DSL directive marks your schema as a strict schema (your hash can not have extra keys);
8
+ - `non_strict!` DSL directive marks your schema as non-strict schema (your hash can have extra keys);
9
+ - use `strict!` in any schema's context place to mark your current schema context as a strict;
10
+ - use `non_strict` in any schema's context place to mark your current schema context as a strict;
11
+ - use `schema(:strict)` to globally define strict schema (default behavior);
12
+ - use `schema(:non_strict)` to globally define non-strict schema;
13
+ - nested schemas inherits strict behavior from outer schemas;
14
+ - root schema is `:strict` by default;
15
+ - schema reopening without mode attribute does not change original schema mode
16
+ (you should manually pass a mode attribute to redefine already defined schema mode);
17
+
4
18
  # [0.2.0] - 2020-11-09
5
19
  ### Added
6
20
  - `#errors` now includes `#extra_keys` too (`:extra_key` error code for each extra key);
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- smart_schema (0.2.0)
4
+ smart_schema (0.3.0)
5
5
  smart_engine (~> 0.8)
6
6
  smart_types (~> 0.1)
7
7
 
@@ -82,8 +82,8 @@ GEM
82
82
  simplecov-html (~> 0.11)
83
83
  simplecov-html (0.12.3)
84
84
  smart_engine (0.8.0)
85
- smart_types (0.1.0)
86
- smart_engine (~> 0.6)
85
+ smart_types (0.2.0)
86
+ smart_engine (~> 0.7)
87
87
  thread_safe (0.3.6)
88
88
  tzinfo (1.2.8)
89
89
  thread_safe (~> 0.1)
@@ -103,4 +103,4 @@ DEPENDENCIES
103
103
  smart_schema!
104
104
 
105
105
  BUNDLED WITH
106
- 2.1.4
106
+ 2.2.0.rc.1
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Provides convenient and concise DSL to define complex schemas in easiest way and public validation interface to achieve a comfortable work with detailed validation result.
6
6
 
7
- Supports nested structures, type validation (via `smart_types`), required- and optional- schema keys, schema value presence validation, schema inheritance (soon), schema extending (soon) and schema composition (soon).
7
+ Supports nested structures, type validation (via `smart_types`), required- and optional- schema keys, *strict* and *non-strict* schemas, schema value presence validation, schema inheritance (soon), schema extending (soon) and schema composition (soon).
8
8
 
9
9
  Works in predicate style and in OOP/Monadic result object style. Enjoy :)
10
10
 
@@ -33,18 +33,36 @@ require 'smart_core/schema'
33
33
  - `nil` control: `filled`;
34
34
  - nested definitions: `do ... end`;
35
35
  - supported types: see `smart_types` gem;
36
+ - strict modes and strict behavior: `strict!`, `non_strict!`, `schema(:strict)`, `schema(:non_strict)`;
37
+ - `:strict` is used by default (in first `schema` invokation);
38
+ - you can make non-strict inner schemas inside strict schemas (and vise-versa);
39
+ - inner schemas inherits their's mode from their's nearest outer schema (and can have own mode too);
36
40
 
37
41
  ```ruby
38
42
  class MySchema < SmartCore::Schema
39
- schema do
43
+ # you can mark strict mode in root schema here:
44
+ #
45
+ # non_strict!
46
+ #
47
+ # -- or --
48
+ #
49
+ # strict!
50
+
51
+ schema do # or here with `schema(:strict)` (default in first time) or `schema(:non_strict)`
40
52
  required(:key) do
53
+ # inherits `:strict`
41
54
  optional(:data).type(:string).filled
42
55
  optional(:value).type(:numeric)
43
56
  required(:name).type(:string)
44
57
 
45
58
  required(:nested) do
59
+ # inherits `:strict`
46
60
  optional(:version).filled
47
61
  end
62
+
63
+ optional(:another_nested) do
64
+ non_strict! # marks current nested schema as `:non_strict`
65
+ end
48
66
  end
49
67
 
50
68
  required(:another_key).filled
@@ -55,6 +73,30 @@ class MySchema < SmartCore::Schema
55
73
  # schema do
56
74
  # required(:third_key).filled.type(:string)
57
75
  # end
76
+
77
+ # you can redefine strict behavior of already defined schema:
78
+ #
79
+ # schema(:non_strict) do
80
+ # ...
81
+ # end
82
+ #
83
+ # -- or --
84
+ #
85
+ # schema do
86
+ # non_strict!
87
+ # end
88
+ #
89
+ # -- or --
90
+ #
91
+ # non_strict!
92
+
93
+ # you can redefine nested schema behavior:
94
+ #
95
+ # schema do
96
+ # optional(:another_nested) do
97
+ # strict! # change from :non_strict to :strict
98
+ # end
99
+ # end
58
100
  end
59
101
  ```
60
102
 
@@ -90,9 +132,11 @@ result = MySchema.new.validate(
90
132
  # #<SmartCore::Schema::Result:0x00007ffcd8926990
91
133
  # @errors={"key.data"=>[:non_filled], "key.value"=>[:invalid_type], "key.nested"=>[:required_key_not_found], "another_key"=>[:non_filled], "third_key"=>[:extra_key]},
92
134
  # @extra_keys=#<Set: {"third_key"}>,
135
+ # @spread_keys=#<Set: {}>, (coming soon (spread keys of non-strict schemas))
93
136
  # @source={:key=>{:data=>nil, :value=>"1", :name=>"D@iVeR"}, :another_key=>nil, :third_key=>"test"}>
94
137
 
95
138
  result.success? # => false
139
+ result.spread_keys # => <Set: {}> (coming soon (spread keys of non-strict schemas))
96
140
  result.extra_keys # => <Set: {"third_key"}>
97
141
  result.errors # =>
98
142
  {
@@ -108,16 +152,18 @@ Possible errors:
108
152
  - `:non_filled` (existing key has nil value);
109
153
  - `:invalid_type` (existing key has invalid type);
110
154
  - `:required_key_not_found` (required key does not exist);
111
- - `:extra_key` (a key that does not exist in schema);
155
+ - `:extra_key` (concrete key does not exist in schema);
112
156
 
113
157
  ---
114
158
 
115
159
  ## Roadmap
116
160
 
117
- - **(0.3.0)** - schema inheritance;
118
- - **(0.3.0)** - schema composition (`required(:key).schema(SchemaClass)`) (`compose_with(AnotherSchema)`);
119
- - **(0.4.0)** - dependable schema checking (sample: if one key exist (or not) we should check another (or not), and vice verca);
120
- - **(0.5.0)** - error messages (that are consistent with error codes);
161
+ - **(0.x.0)** - value-validation layer;
162
+ - **(0.x.0)** - error messages (that are consistent with error codes), with a support for error-code-auto-mappings for error messages via explicit hashes or via file (yaml, json and other formats);
163
+ - **(0.4.0)** - spread keys of non-strict schemas in validation result;
164
+ - **(0.5.0)** - schema inheritance;
165
+ - **(0.5.0)** - schema composition (`required(:key).schema(SchemaClass)`) (`compose_with(AnotherSchema)`);
166
+ - **(0.5.0)** - dependable schema checking (sample: if one key exist (or not) we should check another (or not), and vice verca) (mb `if(:_key_)` rule);
121
167
  - **(0.6.0)** - `smart_type-system` integration;
122
168
  - **(0.7.0)** - support for another data structures (such as YAML strings, JSON strings, `Struct`, `OpenStruct`s, custom `Object`s and etc);
123
169
  - **(0.8.0)** - think about pattern matching;
@@ -2,6 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  class SmartCore::Schema::Checker
6
7
  require_relative 'checker/verifiable_hash'
7
8
  require_relative 'checker/rules'
@@ -31,6 +32,24 @@ class SmartCore::Schema::Checker
31
32
  end
32
33
  end
33
34
 
35
+ # @param checker_invokations [Block]
36
+ # @return [void]
37
+ #
38
+ # @api private
39
+ # @since 0.3.0
40
+ def invoke_in_pipe(&checker_invokations)
41
+ thread_safe { instance_eval(&checker_invokations) }
42
+ end
43
+
44
+ # @param strict_mode [NilClass, String, Symbol]
45
+ # @return [void]
46
+ #
47
+ # @api private
48
+ # @since 0.3.0
49
+ def set_strict_mode(strict_mode)
50
+ thread_safe { apply_strict_mode(strict_mode) }
51
+ end
52
+
34
53
  # @param definitions [Block]
35
54
  # @return [void]
36
55
  #
@@ -70,6 +89,15 @@ class SmartCore::Schema::Checker
70
89
  Reconciler::Constructor.append_definitions(reconciler, &definitions)
71
90
  end
72
91
 
92
+ # @param strict_mode [NilClass, String, Symbol]
93
+ # @return [void]
94
+ #
95
+ # @api private
96
+ # @since 0.3.0
97
+ def apply_strict_mode(strict_mode)
98
+ Reconciler::Constructor.set_strict_mode(reconciler, strict_mode)
99
+ end
100
+
73
101
  # @param block [Block]
74
102
  # @return [Any]
75
103
  #
@@ -2,6 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  class SmartCore::Schema::Checker::Reconciler
6
7
  require_relative 'reconciler/constructor'
7
8
  require_relative 'reconciler/matcher'
@@ -10,8 +11,10 @@ class SmartCore::Schema::Checker::Reconciler
10
11
  #
11
12
  # @api private
12
13
  # @since 0.1.0
14
+ # @version 0.3.0
13
15
  def initialize
14
16
  @rules = SmartCore::Schema::Checker::Rules.new
17
+ @strict = Constructor::DEFAULT_STRICT_BEHAVIOR
15
18
  @lock = SmartCore::Engine::Lock.new
16
19
  end
17
20
 
@@ -40,15 +43,24 @@ class SmartCore::Schema::Checker::Reconciler
40
43
  thread_safe { rules }
41
44
  end
42
45
 
46
+ # @return [Boolean]
47
+ #
48
+ # @api private
49
+ # @since 0.3.0
50
+ def __strict?
51
+ thread_safe { @strict }
52
+ end
53
+
43
54
  # @param schema_key [String, Symbol]
44
55
  # @param nested_definitions [Block]
45
56
  # @return [SmartCore::Schema::Checker::Rules::Required]
46
57
  #
47
58
  # @api public
48
59
  # @since 0.1.0
60
+ # @version 0.3.0
49
61
  def required(schema_key, &nested_definitions)
50
62
  thread_safe do
51
- rule = SmartCore::Schema::Checker::Rules::Required.new(schema_key, &nested_definitions)
63
+ rule = SmartCore::Schema::Checker::Rules::Required.new(self, schema_key, &nested_definitions)
52
64
  rule.tap { rules[rule.schema_key] = rule }
53
65
  end
54
66
  end
@@ -59,13 +71,31 @@ class SmartCore::Schema::Checker::Reconciler
59
71
  #
60
72
  # @api public
61
73
  # @since 0.1.0
74
+ # @version 0.3.0
62
75
  def optional(schema_key, &nested_definitions)
63
76
  thread_safe do
64
- rule = SmartCore::Schema::Checker::Rules::Optional.new(schema_key, &nested_definitions)
77
+ rule = SmartCore::Schema::Checker::Rules::Optional.new(self, schema_key, &nested_definitions)
65
78
  rule.tap { rules[rule.schema_key] = rule }
66
79
  end
67
80
  end
68
81
 
82
+ # @param is_strict [Boolean]
83
+ # @return [void]
84
+ #
85
+ # @api public
86
+ # @since 0.3.0
87
+ def strict!(is_strict = Constructor::DEFAULT_STRICT_BEHAVIOR)
88
+ thread_safe { @strict = is_strict }
89
+ end
90
+
91
+ # @return [void]
92
+ #
93
+ # @api public
94
+ # @since 0.3.0
95
+ def non_strict!
96
+ thread_safe { strict!(Constructor::STRICT_MODES[:non_strict]) }
97
+ end
98
+
69
99
  private
70
100
 
71
101
  # @return [SmartCore::Schema::Checker::Rules]
@@ -2,7 +2,20 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  module SmartCore::Schema::Checker::Reconciler::Constructor
7
+ # @return [Hash<String,Boolean>]
8
+ #
9
+ # @api private
10
+ # @since 0.3.0
11
+ STRICT_MODES = { strict: true, 'strict' => true, non_strict: false, 'non_strict' => true }.freeze
12
+
13
+ # @return [Boolean]
14
+ #
15
+ # @pai private
16
+ # @since 0.3.0
17
+ DEFAULT_STRICT_BEHAVIOR = STRICT_MODES[:strict] # NOTE: means `strict by default`
18
+
6
19
  class << self
7
20
  # @param reconciler [SmartCore::Schema::Checker::Reconciler]
8
21
  # @param definitions [Proc]
@@ -14,6 +27,25 @@ module SmartCore::Schema::Checker::Reconciler::Constructor
14
27
  reconciler.instance_eval(&definitions)
15
28
  end
16
29
 
30
+ # @param reconciler [SmartCore::Schema::Checker::Reconciler]
31
+ # @param strict_mode [NilClass, String, Symbol]
32
+ # @return [void]
33
+ #
34
+ # @api private
35
+ # @since 0.3.0
36
+ def set_strict_mode(reconciler, strict_mode)
37
+ return if strict_mode == nil
38
+
39
+ is_strict = STRICT_MODES.fetch(strict_mode) do
40
+ raise(SmartCore::Schema::ArgumentError, <<~ERROR_MESSAGE)
41
+ Unsupported strict mode "#{strict_mode}".
42
+ SmartCore::Schema supports "strict" and "non_strict" modes only.
43
+ ERROR_MESSAGE
44
+ end
45
+
46
+ reconciler.strict!(is_strict)
47
+ end
48
+
17
49
  # @param definitions [Proc, NilClass]
18
50
  # @return [SmarCore::Schema::Checker::Reconciler]
19
51
  #
@@ -2,7 +2,9 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  module SmartCore::Schema::Checker::Reconciler::Matcher
7
+ require_relative 'matcher/options'
6
8
  require_relative 'matcher/result'
7
9
  require_relative 'matcher/result_finalizer'
8
10
 
@@ -13,39 +15,46 @@ module SmartCore::Schema::Checker::Reconciler::Matcher
13
15
  #
14
16
  # @api private
15
17
  # @since 0.1.0
18
+ # @version 0.3.0
16
19
  def match(reconciler, verifiable_hash)
20
+ matcher_options = Options.build_from(reconciler)
21
+
17
22
  Result.new(verifiable_hash).tap do |result|
18
- match_for_contract_keys(reconciler, verifiable_hash, result)
19
- match_for_extra_keys(reconciler, verifiable_hash, result)
23
+ match_for_contract_keys(reconciler, matcher_options, verifiable_hash, result)
24
+ match_for_extra_keys(reconciler, matcher_options, verifiable_hash, result)
20
25
  end
21
26
  end
22
27
 
23
28
  private
24
29
 
25
30
  # @param reconciler [SmartCore::Schema::Checker::Reconciler]
31
+ # @param matcher_options [SmartCore::Schema::Checker::Reconciler::Matcher::Options]
26
32
  # @param verifiable_hash [SmartCore::Schema::Checker::VerifiableHash]
27
33
  # @param result [SmartCore::Schema::Checker::Reconciler::Matcher::Result]
28
34
  # @return [void]
29
35
  #
30
36
  # @api private
31
37
  # @since 0.1.0
32
- def match_for_contract_keys(reconciler, verifiable_hash, result)
38
+ # @version 0.3.0
39
+ def match_for_contract_keys(reconciler, matcher_options, verifiable_hash, result)
33
40
  reconciler.__contract_rules.each_rule do |rule|
34
- verification_result = rule.__verify!(verifiable_hash)
41
+ verification_result = rule.__verify!(verifiable_hash, matcher_options)
35
42
  result.contract_key_result(verification_result)
36
43
  end
37
44
  end
38
45
 
39
46
  # @param reconciler [SmartCore::Schema::Checker::Reconciler]
47
+ # @param matcher_options [SmartCore::Schema::Checker::Reconciler::Matcher::Options]
40
48
  # @param verifiable_hash [SmartCore::Schema::Checker::VerifiableHash]
41
49
  # @param result [SmartCore::Schema::Checker::Reconciler::Matcher::Result]
42
50
  # @return [void]
43
51
  #
44
52
  # @api private
45
53
  # @since 0.1.0
46
- def match_for_extra_keys(reconciler, verifiable_hash, result)
54
+ # @version 0.3.0
55
+ def match_for_extra_keys(reconciler, matcher_options, verifiable_hash, result)
47
56
  verification_result = reconciler.__extra_keys_contract.__verify!(
48
- verifiable_hash, reconciler.__contract_rules
57
+ verifiable_hash, reconciler.__contract_rules, matcher_options
49
58
  )
50
59
  result.extra_keys_result(verification_result)
51
60
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.3.0
5
+ class SmartCore::Schema::Checker::Reconciler::Matcher::Options
6
+ class << self
7
+ # @param reconciler [SmartCore::Schema::Checker::Reconciler]
8
+ # @return [SmartCore::Schema::Checker::Reconciler::Matcher::Options]
9
+ #
10
+ # @api private
11
+ # @since 0.3.0
12
+ def build_from(reconciler)
13
+ new(reconciler.__strict?)
14
+ end
15
+ end
16
+
17
+ # @param is_strict_schema [Boolean]
18
+ # @return [void]
19
+ #
20
+ # @api private
21
+ # @since 0.3.0
22
+ def initialize(is_strict_schema)
23
+ @is_strict_schema = is_strict_schema
24
+ end
25
+
26
+ # @return [Boolean]
27
+ #
28
+ # @api private
29
+ # @since 0.3.0
30
+ def strict_schema?
31
+ @is_strict_schema
32
+ end
33
+ end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # TODO: refactor error data propagating (with Value Object)
4
+
3
5
  # @api private
4
6
  # @since 0.1.0
7
+ # @version 0.3.0
5
8
  module SmartCore::Schema::Checker::Reconciler::Matcher::ResultFinalizer
6
9
  # @return [Proc]
7
10
  #
@@ -15,6 +18,12 @@ module SmartCore::Schema::Checker::Reconciler::Matcher::ResultFinalizer
15
18
  # @since 0.1.0
16
19
  EXTRA_KEYS_CONTAINER_BUILDER = -> { Set.new }
17
20
 
21
+ # @return [Proc]
22
+ #
23
+ # @api private
24
+ # @since 0.3.0
25
+ SPREAD_KEYS_CONTAINER_BUILDER = -> { Set.new }
26
+
18
27
  # @return [Symbol]
19
28
  #
20
29
  # @api private
@@ -27,26 +36,32 @@ module SmartCore::Schema::Checker::Reconciler::Matcher::ResultFinalizer
27
36
  #
28
37
  # @ai privates
29
38
  # @since 0.1.0
39
+ # @version 0.3.0
40
+ # rubocop:disable Layout/LineLength
30
41
  def finalize(result)
31
- schema_errors, extra_keys = aggregate_errors(result)
32
- schema_errors, extra_keys = compile_errors(schema_errors, extra_keys)
33
- build_final_result(result, schema_errors, extra_keys)
42
+ schema_errors, extra_keys, spread_keys = aggregate_errors(result)
43
+ schema_errors, extra_keys, spread_keys = compile_errors(schema_errors, extra_keys, spread_keys)
44
+ build_final_result(result, schema_errors, extra_keys, spread_keys)
34
45
  end
46
+ # rubocop:enable Layout/LineLength
35
47
 
36
48
  private
37
49
 
38
50
  # @param result [SmartCore::Schema::Checker::Reconciler::Matcher::Result]
39
51
  # @param schema_errors [Hash<String,Array<Symbol>>]
40
52
  # @param extra_keys [Set<String>]
53
+ # @param spread_keys [Set<String>]
41
54
  # @return [SmartCore::Schema::Result]
42
55
  #
43
56
  # @api private
44
57
  # @since 0.1.0
45
- def build_final_result(result, schema_errors, extra_keys)
58
+ # @version 0.3.0
59
+ def build_final_result(result, schema_errors, extra_keys, spread_keys)
46
60
  SmartCore::Schema::Result.new(
47
61
  result.verifiable_hash.source,
48
62
  schema_errors.freeze,
49
- extra_keys.freeze
63
+ extra_keys.freeze,
64
+ SPREAD_KEYS_CONTAINER_BUILDER.call.freeze # TODO: spread_keys.freeze
50
65
  )
51
66
  end
52
67
 
@@ -58,6 +73,7 @@ module SmartCore::Schema::Checker::Reconciler::Matcher::ResultFinalizer
58
73
  def aggregate_errors(result)
59
74
  schema_errors = ERRORS_CONTAINER_BUILDER.call
60
75
  extra_keys = EXTRA_KEYS_CONTAINER_BUILDER.call
76
+ spread_keys = SPREAD_KEYS_CONTAINER_BUILDER.call
61
77
 
62
78
  result.each_result do |concrete_result|
63
79
  case concrete_result
@@ -65,15 +81,16 @@ module SmartCore::Schema::Checker::Reconciler::Matcher::ResultFinalizer
65
81
  aggregate_verifier_errors(concrete_result, schema_errors)
66
82
  when SmartCore::Schema::Checker::Rules::ExtraKeys::Failure
67
83
  aggregate_extra_keys_errors(concrete_result, extra_keys)
84
+ when SmartCore::Schema::Checker::Rules::ExtraKeys::Success
85
+ aggregate_spread_keys_notice(concrete_result, spread_keys)
68
86
  end
69
87
  end
70
88
 
71
- [schema_errors, extra_keys]
89
+ [schema_errors, extra_keys, spread_keys]
72
90
  end
73
91
 
74
92
  # @param result [SmartCore::Schema::Checker::Rules::Verifier::Result]
75
93
  # @param errors [Hash<String,Array<String>|Hash<String,Hash>>]
76
- # @param extra_keys [Set<String>]
77
94
  # @return [void]
78
95
  #
79
96
  # @api private
@@ -102,23 +119,41 @@ module SmartCore::Schema::Checker::Reconciler::Matcher::ResultFinalizer
102
119
  extra_keys.merge(result.extra_keys)
103
120
  end
104
121
 
122
+ # @param result [SmartCore::Schema::Checker::Rules::ExtraKeys::Success]
123
+ # @param spread_keys [Set<String>]
124
+ # @return [void]
125
+ #
126
+ # @api private
127
+ # @since 0.3.0
128
+ def aggregate_spread_keys_notice(result, spread_keys)
129
+ spread_keys.merge(result.spread_keys)
130
+ end
131
+
105
132
  # @param errors [Hash]
106
133
  # @param extra_keys [Set]
134
+ # @param spread_keys [Set]
135
+ # @param initial_error_key [NilClass, String]
107
136
  # @return [Array<Hash<String,Set<Symbol>>,Set<String>]
108
137
  #
109
138
  # @api private
110
139
  # @since 0.1.0
111
- # @version 0.2.0
112
- # rubocop:disable Metrics/AbcSize
113
- def compile_errors(errors, extra_keys, initial_error_key = nil)
140
+ # @version 0.3.0
141
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
142
+ def compile_errors(errors, extra_keys, spread_keys, initial_error_key = nil)
114
143
  compiled_errors = ERRORS_CONTAINER_BUILDER.call
115
144
  compiled_extra_keys = EXTRA_KEYS_CONTAINER_BUILDER.call
145
+ compiled_spread_keys = SPREAD_KEYS_CONTAINER_BUILDER.call
116
146
 
117
147
  compiled_extra_keys.merge(extra_keys).map! do |key|
118
148
  # dot-notated nested keys
119
149
  initial_error_key ? "#{initial_error_key}.#{key}" : key
120
150
  end
121
151
 
152
+ compiled_spread_keys.merge(spread_keys).map! do |key|
153
+ # dot-notated nested keys
154
+ initial_error_key ? "#{initial_error_key}.#{key}" : key
155
+ end
156
+
122
157
  errors.each_pair do |key, error_set|
123
158
  # dot-notated nested key
124
159
  compiled_key = initial_error_key ? "#{initial_error_key}.#{key}" : key
@@ -129,11 +164,13 @@ module SmartCore::Schema::Checker::Reconciler::Matcher::ResultFinalizer
129
164
  compiled_errors[compiled_key] << error
130
165
  when Array # nested errors
131
166
  sub_errors, sub_extra_keys = error
132
- compiled_sub_errors, compiled_sub_extra_keys = compile_errors(
133
- sub_errors, sub_extra_keys, compiled_key
167
+ sub_spread_keys = SPREAD_KEYS_CONTAINER_BUILDER.call
168
+ compiled_sub_errors, compiled_sub_extra_keys, compiled_sub_spread_keys = compile_errors(
169
+ sub_errors, sub_extra_keys, sub_spread_keys, compiled_key
134
170
  )
135
171
  compiled_errors.merge!(compiled_sub_errors)
136
172
  compiled_extra_keys.merge(compiled_sub_extra_keys)
173
+ compiled_spread_keys.merge(compiled_sub_spread_keys)
137
174
  end
138
175
  end
139
176
  end
@@ -142,8 +179,8 @@ module SmartCore::Schema::Checker::Reconciler::Matcher::ResultFinalizer
142
179
  compiled_errors[compiled_extra_key] << EXTRA_KEY_ERROR_CODE
143
180
  end
144
181
 
145
- [compiled_errors, compiled_extra_keys]
182
+ [compiled_errors, compiled_extra_keys, compiled_spread_keys]
146
183
  end
147
- # rubocop:enable Metrics/AbcSize
184
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
148
185
  end
149
186
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  class SmartCore::Schema::Checker::Rules::Base
6
7
  # @return [String]
7
8
  #
@@ -21,14 +22,23 @@ class SmartCore::Schema::Checker::Rules::Base
21
22
  # @since 0.1.0
22
23
  attr_reader :nested_reconciler
23
24
 
25
+ # @return [SmartCore::Schema::Checker::Reconciler]
26
+ #
27
+ # @api private
28
+ # @since 0.3.0
29
+ attr_reader :root_reconciler
30
+
31
+ # @param root_reconciler [SmartCore::Schema::Checker::Reconciler]
24
32
  # @param schema_key [String, Symbol]
25
33
  # @param nested_definitions [Block]
26
34
  # @return [void]
27
35
  #
28
36
  # @api private
29
37
  # @since 0.1.0
30
- def initialize(schema_key, &nested_definitions)
38
+ # @version 0.3.0
39
+ def initialize(root_reconciler, schema_key, &nested_definitions)
31
40
  @lock = SmartCore::Engine::Lock.new
41
+ @root_reconciler = root_reconciler
32
42
  @schema_key = SmartCore::Schema::KeyControl.normalize(schema_key)
33
43
  @options = SmartCore::Schema::Checker::Rules::Options.new(self)
34
44
  @nested_reconciler = nil
@@ -40,12 +50,16 @@ class SmartCore::Schema::Checker::Rules::Base
40
50
  # @return [SmartCore::Schema::Checker::Rules::Requirement::Required]
41
51
 
42
52
  # @param verifiable_hash [Hash<String|Symbol,Any>]
53
+ # @param matcher_options [SmartCore::Schema::Checker::Reconciler::Matcher::Options]
43
54
  # @return [SmartCore::Schema::Checker::Rules::Verifier::Result]
44
55
  #
45
56
  # @api private
46
57
  # @since 0.1.0
47
- def __verify!(verifiable_hash)
48
- SmartCore::Schema::Checker::Rules::Verifier.verify!(self, verifiable_hash)
58
+ # @version 0.3.0
59
+ def __verify!(verifiable_hash, matcher_options)
60
+ SmartCore::Schema::Checker::Rules::Verifier.verify!(
61
+ self, matcher_options, verifiable_hash
62
+ )
49
63
  end
50
64
 
51
65
  # @param required_type [String, Symbol, SmartCore::Types::Primitive]
@@ -84,11 +98,13 @@ class SmartCore::Schema::Checker::Rules::Base
84
98
  #
85
99
  # @api private
86
100
  # @since 0.1.0
101
+ # @version 0.3.0
87
102
  def define_nested_reconciler(&nested_definitions)
88
103
  return unless block_given?
89
104
 
90
105
  SmartCore::Schema::Checker::Reconciler::Constructor.tap do |constructor|
91
106
  @nested_reconciler = constructor.create if @nested_reconciler == nil
107
+ @nested_reconciler.strict!(root_reconciler.__strict?)
92
108
  constructor.append_definitions(@nested_reconciler, &nested_definitions)
93
109
  end
94
110
 
@@ -2,21 +2,30 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  module SmartCore::Schema::Checker::Rules::ExtraKeys
7
+ require_relative 'extra_keys/result'
6
8
  require_relative 'extra_keys/success'
7
9
  require_relative 'extra_keys/failure'
8
10
 
9
11
  class << self
10
12
  # @param verifiable_hash [SmartCore::Schema::Checker::VerifiableHash]
11
13
  # @param rules [SmartCore::Schema::Checker::Rules]
14
+ # @param matcher_options [SmartCore::Schema::Checker::Reconciler::Matcher::Options]
12
15
  # @return [SmartCore::Schema::Checker::Rules::ExtraKeys::Success]
13
16
  # @return [SmartCore::Schema::Checker::Rules::ExtraKeys::Failure]
14
17
  #
15
18
  # @api private
16
19
  # @since 0.1.0
17
- def __verify!(verifiable_hash, rules)
20
+ # @version 0.3.0
21
+ def __verify!(verifiable_hash, rules, matcher_options)
18
22
  extra_keys = verifiable_hash.keys - rules.keys
19
- extra_keys.empty? ? Success.new : Failure.new(extra_keys)
23
+
24
+ if extra_keys.empty? || (extra_keys.any? && !matcher_options.strict_schema?)
25
+ Success.new(extra_keys, matcher_options)
26
+ else
27
+ Failure.new(extra_keys, matcher_options)
28
+ end
20
29
  end
21
30
  end
22
31
  end
@@ -1,36 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # @api private
4
- # @since 0.1.0
5
- class SmartCore::Schema::Checker::Rules::ExtraKeys::Failure
6
- # @return extra_keys [Array<String>]
7
- #
3
+ module SmartCore::Schema::Checker::Rules::ExtraKeys
8
4
  # @api private
9
5
  # @since 0.1.0
10
- attr_reader :extra_keys
6
+ # @version 0.3.0
7
+ class Failure < Result
8
+ # @return [Boolean]
9
+ #
10
+ # @api private
11
+ # @since 0.1.0
12
+ def success?
13
+ false
14
+ end
11
15
 
12
- # @param extra_keys [Array<String>]
13
- # @return [void]
14
- #
15
- # @api private
16
- # @since 0.1.0
17
- def initialize(extra_keys)
18
- @extra_keys = extra_keys
19
- end
20
-
21
- # @return [Boolean]
22
- #
23
- # @api private
24
- # @since 0.1.0
25
- def success?
26
- false
27
- end
28
-
29
- # @return [Boolean]
30
- #
31
- # @api private
32
- # @since 0.1.0
33
- def failure?
34
- true
16
+ # @return [Boolean]
17
+ #
18
+ # @api private
19
+ # @since 0.1.0
20
+ def failure?
21
+ true
22
+ end
35
23
  end
36
24
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @apbstract
4
+ #
5
+ # @api private
6
+ # @since 0.3.0
7
+ class SmartCore::Schema::Checker::Rules::ExtraKeys::Result
8
+ # @return [Array<String>]
9
+ #
10
+ # @api private
11
+ # @since 0.3.0
12
+ attr_reader :extra_keys
13
+
14
+ # @return [SmartCore::Schema::Checker::Reconciler::Matcher::Options]
15
+ #
16
+ # @api private
17
+ # @since 0.3.0
18
+ attr_reader :matcher_options
19
+
20
+ # @param extra_keys [Array<String>]
21
+ # @param matcher_options [SmartCore::Schema::Checker::Reconciler::Matcher::Options]
22
+ # @return [void]
23
+ #
24
+ # @api private
25
+ # @since 0.1.0
26
+ # @version 0.3.0
27
+ def initialize(extra_keys, matcher_options)
28
+ @extra_keys = extra_keys
29
+ @matcher_options = matcher_options
30
+ end
31
+
32
+ # @!method success?
33
+ # @return [Boolean]
34
+
35
+ # @!method failure?
36
+ # @return [Boolean]
37
+ end
@@ -1,21 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # @api private
4
- # @since 0.1.0
5
- class SmartCore::Schema::Checker::Rules::ExtraKeys::Success
6
- # @return [Boolean]
7
- #
3
+ module SmartCore::Schema::Checker::Rules::ExtraKeys
8
4
  # @api private
9
5
  # @since 0.1.0
10
- def success?
11
- true
12
- end
6
+ # @version 0.3.0
7
+ class Success < Result
8
+ # @return [Array<String>]
9
+ #
10
+ # @api private
11
+ # @since 0.3.0
12
+ alias_method :spread_keys, :extra_keys
13
13
 
14
- # @return [Boolean]
15
- #
16
- # @api private
17
- # @since 0.1.0
18
- def failure?
19
- false
14
+ # @return [Boolean]
15
+ #
16
+ # @api private
17
+ # @since 0.1.0
18
+ def success?
19
+ true
20
+ end
21
+
22
+ # @return [Boolean]
23
+ #
24
+ # @api private
25
+ # @since 0.1.0
26
+ def failure?
27
+ false
28
+ end
20
29
  end
21
30
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  class SmartCore::Schema::Checker::Rules::Optional < SmartCore::Schema::Checker::Rules::Base
6
7
  # @return [SmartCore::Schema::Checker::Rules::Requirement::Optional]
7
8
  #
@@ -9,14 +10,16 @@ class SmartCore::Schema::Checker::Rules::Optional < SmartCore::Schema::Checker::
9
10
  # @since 0.1.0
10
11
  attr_reader :requirement
11
12
 
13
+ # @param root_reconciler [SmartCore::Schema::Checker::Reconciler]
12
14
  # @param schema_key [String, Symbol]
13
15
  # @param nested_definitions [Block]
14
16
  # @return [void]
15
17
  #
16
18
  # @api private
17
19
  # @since 0.1.0
18
- def initialize(schema_key, &nested_definitions)
19
- super(schema_key, &nested_definitions)
20
+ # @version 0.3.0
21
+ def initialize(root_reconciler, schema_key, &nested_definitions)
22
+ super(root_reconciler, schema_key, &nested_definitions)
20
23
  @requirement = SmartCore::Schema::Checker::Rules::Requirement::Optional.new(self)
21
24
  end
22
25
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # @api private
4
- # @since 0.1.0
5
3
  class SmartCore::Schema::Checker::Rules::Options
4
+ # @api private
5
+ # @since 0.1.0
6
6
  class Filled < Empty
7
- # @note Constant is used only for clarity (for other developers).
7
+ # @note Constant is used only for other developers.
8
8
  # @return [Symbol]
9
9
  #
10
10
  # @api private
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # @api private
4
- # @since 0.1.0
5
3
  class SmartCore::Schema::Checker::Rules::Options
4
+ # @api private
5
+ # @since 0.1.0
6
6
  class Type < Empty
7
7
  # @note Constant is used only for clarity (for other developers)
8
8
  # @return [Symbol]
@@ -2,6 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  class SmartCore::Schema::Checker::Rules::Required < SmartCore::Schema::Checker::Rules::Base
6
7
  # @return [SmartCore::Schema::Checker::Rules::Requirement::Required]
7
8
  #
@@ -9,14 +10,16 @@ class SmartCore::Schema::Checker::Rules::Required < SmartCore::Schema::Checker::
9
10
  # @since 0.1.0
10
11
  attr_reader :requirement
11
12
 
13
+ # @param root_reconciler [SmartCore::Schema::Checker::Reconciler]
12
14
  # @param schema_key [String, Symbol]
13
15
  # @param nested_definitions [Block]
14
16
  # @return [void]
15
17
  #
16
18
  # @api private
17
19
  # @since 0.1.0
18
- def initialize(schema_key, &nested_definitions)
19
- super(schema_key, &nested_definitions)
20
+ # @version 0.3.0
21
+ def initialize(root_reconciler, schema_key, &nested_definitions)
22
+ super(root_reconciler, schema_key, &nested_definitions)
20
23
  @requirement = SmartCore::Schema::Checker::Rules::Requirement::Required.new(self)
21
24
  end
22
25
  end
@@ -2,17 +2,20 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  module SmartCore::Schema::Checker::Rules::Verifier
6
7
  require_relative 'verifier/result'
7
8
 
8
9
  class << self
9
10
  # @param rule [SmartCore::Schema::Checker::Rules::Base]
11
+ # @param matcher_options [SmartCore::Schema::Checker::Reconciler::Matcher::Options]
10
12
  # @param verifiable_hash [SmartCore::Schema::Checker::VerifiableHash]
11
13
  # @return [SmartCore::Schema::Checker::Rules::Verifier::Result]
12
14
  #
13
15
  # @api private
14
16
  # @since 0.1.0
15
- def verify!(rule, verifiable_hash)
17
+ # @version 0.3.0
18
+ def verify!(rule, matcher_options, verifiable_hash)
16
19
  SmartCore::Schema::Checker::Rules::Verifier::Result.new(rule).tap do |result|
17
20
  requirement = result << check_requirement(rule, verifiable_hash)
18
21
  next result if requirement.required? && requirement.failure?
@@ -36,6 +36,7 @@ module SmartCore::Schema::DSL
36
36
 
37
37
  # @api private
38
38
  # @since 0.1.0
39
+ # @version 0.3.0
39
40
  module ClassMethods
40
41
  # @return [SmartCore::Schema::Checker]
41
42
  #
@@ -45,13 +46,36 @@ module SmartCore::Schema::DSL
45
46
  @__schema_checker__
46
47
  end
47
48
 
49
+ # @param strict_mode [NilClass, String, Symbol]
48
50
  # @param definitions [Block]
49
51
  # @return [void]
50
52
  #
53
+ # @note nil strict mode means `do not change current mode`
54
+ #
51
55
  # @api public
52
56
  # @since 0.1.0
53
- def schema(&definitions)
54
- __schema_checker__.append_schema_definitions(&definitions)
57
+ # @version 0.3.0
58
+ def schema(strict_mode = nil, &definitions)
59
+ __schema_checker__.invoke_in_pipe do
60
+ set_strict_mode(strict_mode)
61
+ append_schema_definitions(&definitions)
62
+ end
63
+ end
64
+
65
+ # @return [void]
66
+ #
67
+ # @api public
68
+ # @since 0.3.0
69
+ def strict!
70
+ __schema_checker__.set_strict_mode(:strict)
71
+ end
72
+
73
+ # @return [void]
74
+ #
75
+ # @api public
76
+ # @since 0.3.0
77
+ def non_strict!
78
+ __schema_checker__.set_strict_mode(:non_strict)
55
79
  end
56
80
  end
57
81
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  # @api private
4
4
  # @since 0.1.0
5
+ # @version 0.3.0
5
6
  class SmartCore::Schema::Result
6
7
  # @return [Hash<String,Any>]
7
8
  #
@@ -21,15 +22,26 @@ class SmartCore::Schema::Result
21
22
  # @since 0.1.0
22
23
  attr_reader :extra_keys
23
24
 
25
+ # @return [Set<String>]
26
+ #
27
+ # @api public
28
+ # @since 0.3.0
29
+ attr_reader :spread_keys
30
+
24
31
  # @param source [Hash<String|Symbol,Any>]
32
+ # @param errors [Hash<String,Array<Symbol>]
33
+ # @param extra_keys [Set<String>]
34
+ # @param spread_keys [Set<String>]
25
35
  # @return [void]
26
36
  #
27
37
  # @api private
28
38
  # @since 0.1.0
29
- def initialize(source, errors, extra_keys)
39
+ # @version 0.3.0
40
+ def initialize(source, errors, extra_keys, spread_keys)
30
41
  @source = source
31
42
  @errors = errors
32
43
  @extra_keys = extra_keys
44
+ @spread_keys = spread_keys
33
45
  end
34
46
 
35
47
  # @return [Boolean]
@@ -5,7 +5,7 @@ module SmartCore
5
5
  # @return [String]
6
6
  #
7
7
  # @api public
8
- # @since 0.2.0
9
- VERSION = '0.2.0'
8
+ # @since 0.3.0
9
+ VERSION = '0.3.0'
10
10
  end
11
11
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_schema
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-11-09 00:00:00.000000000 Z
11
+ date: 2020-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: smart_engine
@@ -147,12 +147,14 @@ files:
147
147
  - lib/smart_core/schema/checker/reconciler.rb
148
148
  - lib/smart_core/schema/checker/reconciler/constructor.rb
149
149
  - lib/smart_core/schema/checker/reconciler/matcher.rb
150
+ - lib/smart_core/schema/checker/reconciler/matcher/options.rb
150
151
  - lib/smart_core/schema/checker/reconciler/matcher/result.rb
151
152
  - lib/smart_core/schema/checker/reconciler/matcher/result_finalizer.rb
152
153
  - lib/smart_core/schema/checker/rules.rb
153
154
  - lib/smart_core/schema/checker/rules/base.rb
154
155
  - lib/smart_core/schema/checker/rules/extra_keys.rb
155
156
  - lib/smart_core/schema/checker/rules/extra_keys/failure.rb
157
+ - lib/smart_core/schema/checker/rules/extra_keys/result.rb
156
158
  - lib/smart_core/schema/checker/rules/extra_keys/success.rb
157
159
  - lib/smart_core/schema/checker/rules/optional.rb
158
160
  - lib/smart_core/schema/checker/rules/options.rb
@@ -200,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
202
  - !ruby/object:Gem::Version
201
203
  version: '0'
202
204
  requirements: []
203
- rubygems_version: 3.1.4
205
+ rubygems_version: 3.2.0.rc.1
204
206
  signing_key:
205
207
  specification_version: 4
206
208
  summary: SmartCore::Schema is a schema validator for Hash-like data structures