smart_schema 0.2.0 → 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 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