smart_schema 0.0.0 → 0.1.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile.lock +27 -16
  4. data/README.md +133 -1
  5. data/lib/smart_core/schema.rb +40 -0
  6. data/lib/smart_core/schema/checker.rb +81 -0
  7. data/lib/smart_core/schema/checker/reconciler.rb +85 -0
  8. data/lib/smart_core/schema/checker/reconciler/constructor.rb +28 -0
  9. data/lib/smart_core/schema/checker/reconciler/matcher.rb +53 -0
  10. data/lib/smart_core/schema/checker/reconciler/matcher/result.rb +86 -0
  11. data/lib/smart_core/schema/checker/reconciler/matcher/result_finalizer.rb +136 -0
  12. data/lib/smart_core/schema/checker/rules.rb +85 -0
  13. data/lib/smart_core/schema/checker/rules/base.rb +105 -0
  14. data/lib/smart_core/schema/checker/rules/extra_keys.rb +22 -0
  15. data/lib/smart_core/schema/checker/rules/extra_keys/failure.rb +36 -0
  16. data/lib/smart_core/schema/checker/rules/extra_keys/success.rb +21 -0
  17. data/lib/smart_core/schema/checker/rules/optional.rb +22 -0
  18. data/lib/smart_core/schema/checker/rules/options.rb +56 -0
  19. data/lib/smart_core/schema/checker/rules/options/empty.rb +43 -0
  20. data/lib/smart_core/schema/checker/rules/options/filled.rb +49 -0
  21. data/lib/smart_core/schema/checker/rules/options/type.rb +86 -0
  22. data/lib/smart_core/schema/checker/rules/required.rb +22 -0
  23. data/lib/smart_core/schema/checker/rules/requirement.rb +9 -0
  24. data/lib/smart_core/schema/checker/rules/requirement/optional.rb +36 -0
  25. data/lib/smart_core/schema/checker/rules/requirement/required.rb +36 -0
  26. data/lib/smart_core/schema/checker/rules/requirement/result.rb +95 -0
  27. data/lib/smart_core/schema/checker/rules/result.rb +9 -0
  28. data/lib/smart_core/schema/checker/rules/result/base.rb +44 -0
  29. data/lib/smart_core/schema/checker/rules/result/failure.rb +41 -0
  30. data/lib/smart_core/schema/checker/rules/result/success.rb +15 -0
  31. data/lib/smart_core/schema/checker/rules/type_aliases.rb +51 -0
  32. data/lib/smart_core/schema/checker/rules/verifier.rb +71 -0
  33. data/lib/smart_core/schema/checker/rules/verifier/result.rb +75 -0
  34. data/lib/smart_core/schema/checker/verifiable_hash.rb +65 -0
  35. data/lib/smart_core/schema/dsl.rb +57 -0
  36. data/lib/smart_core/schema/errors.rb +11 -0
  37. data/lib/smart_core/schema/key_control.rb +39 -0
  38. data/lib/smart_core/schema/result.rb +50 -0
  39. data/lib/smart_core/schema/version.rb +1 -1
  40. data/smart_schema.gemspec +14 -7
  41. metadata +83 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 324e316565a37d67eac797e5deff2308973fbb339c16c739dfff3cbfa7a4c7f1
4
- data.tar.gz: 4cf06afadcdca2b90fdf90492b95b79b448145ff453748069f2e5cd0ba0a6dcf
3
+ metadata.gz: 686b8bd1172752321dd2992f2cbe2aeb5230493142afb019c8c062347f215d97
4
+ data.tar.gz: f3395ec6d78bf5e8d6407018bb890e90043a8b730ec52ed1cabc090ab92953e2
5
5
  SHA512:
6
- metadata.gz: 7ba48da56544751547a3a716cf49a23c32fe6c28ff0a8b8657398b8fd30df526229bfbdd51b942b5a4be72305f619bb88903e8fe24e576edc68aa5f6bcce921a
7
- data.tar.gz: d9d08dc497ce96c42927f76fc30b65959aa355486f4648a453137aab29db1379606e054419fe67f07ef4007c6765c382a17417bf08968dd1a4311688be8a23b9
6
+ metadata.gz: f64e1350c2422b40e275638b0719b49f0300cb8c7e3e9e7e179029a94afba9d455fd8c8d5c82d83b8a5604eb4df1c2ed4cf3b4dd0af20ecef35628f89bf6fc9b
7
+ data.tar.gz: 1fb7354eeb85ae75471fc3075ddcbf6bc50a1b4c8f115a34c045a5a29a689fb7705a776fe006a3a8276c2f7b58796336e126192c3108bd93134cff3a0348e797
@@ -1,2 +1,6 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
+
4
+ # [0.1.0] - 2020-08-25
5
+
6
+ - Release :)
@@ -2,6 +2,8 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  smart_schema (0.1.0)
5
+ smart_engine (~> 0.8)
6
+ smart_types (~> 0.1)
5
7
 
6
8
  GEM
7
9
  remote: https://rubygems.org/
@@ -12,22 +14,27 @@ GEM
12
14
  minitest (~> 5.1)
13
15
  tzinfo (~> 1.1)
14
16
  zeitwerk (~> 2.2, >= 2.2.2)
15
- armitage-rubocop (0.88.0)
16
- rubocop (= 0.88.0)
17
+ armitage-rubocop (0.89.1)
18
+ rubocop (= 0.89.1)
17
19
  rubocop-performance (= 1.7.1)
18
- rubocop-rails (= 2.6.0)
20
+ rubocop-rails (= 2.7.1)
19
21
  rubocop-rake (= 0.5.1)
20
- rubocop-rspec (= 1.42.0)
22
+ rubocop-rspec (= 1.43.2)
21
23
  ast (2.4.1)
22
- concurrent-ruby (1.1.6)
24
+ coderay (1.1.3)
25
+ concurrent-ruby (1.1.7)
23
26
  diff-lcs (1.4.4)
24
27
  docile (1.3.2)
25
28
  i18n (1.8.5)
26
29
  concurrent-ruby (~> 1.0)
30
+ method_source (1.0.0)
27
31
  minitest (5.14.1)
28
32
  parallel (1.19.2)
29
33
  parser (2.7.1.4)
30
34
  ast (~> 2.4.1)
35
+ pry (0.13.1)
36
+ coderay (~> 1.1)
37
+ method_source (~> 1.0)
31
38
  rack (2.2.3)
32
39
  rainbow (3.0.0)
33
40
  rake (13.0.1)
@@ -46,32 +53,35 @@ GEM
46
53
  diff-lcs (>= 1.2.0, < 2.0)
47
54
  rspec-support (~> 3.9.0)
48
55
  rspec-support (3.9.3)
49
- rubocop (0.88.0)
56
+ rubocop (0.89.1)
50
57
  parallel (~> 1.10)
51
58
  parser (>= 2.7.1.1)
52
59
  rainbow (>= 2.2.2, < 4.0)
53
60
  regexp_parser (>= 1.7)
54
61
  rexml
55
- rubocop-ast (>= 0.1.0, < 1.0)
62
+ rubocop-ast (>= 0.3.0, < 1.0)
56
63
  ruby-progressbar (~> 1.7)
57
64
  unicode-display_width (>= 1.4.0, < 2.0)
58
- rubocop-ast (0.2.0)
59
- parser (>= 2.7.0.1)
65
+ rubocop-ast (0.3.0)
66
+ parser (>= 2.7.1.4)
60
67
  rubocop-performance (1.7.1)
61
68
  rubocop (>= 0.82.0)
62
- rubocop-rails (2.6.0)
69
+ rubocop-rails (2.7.1)
63
70
  activesupport (>= 4.2.0)
64
71
  rack (>= 1.1)
65
- rubocop (>= 0.82.0)
72
+ rubocop (>= 0.87.0)
66
73
  rubocop-rake (0.5.1)
67
74
  rubocop
68
- rubocop-rspec (1.42.0)
69
- rubocop (>= 0.87.0)
75
+ rubocop-rspec (1.43.2)
76
+ rubocop (~> 0.87)
70
77
  ruby-progressbar (1.10.1)
71
- simplecov (0.18.5)
78
+ simplecov (0.19.0)
72
79
  docile (~> 1.1)
73
80
  simplecov-html (~> 0.11)
74
81
  simplecov-html (0.12.2)
82
+ smart_engine (0.8.0)
83
+ smart_types (0.1.0)
84
+ smart_engine (~> 0.6)
75
85
  thread_safe (0.3.6)
76
86
  tzinfo (1.2.7)
77
87
  thread_safe (~> 0.1)
@@ -82,11 +92,12 @@ PLATFORMS
82
92
  ruby
83
93
 
84
94
  DEPENDENCIES
85
- armitage-rubocop (~> 0.88)
95
+ armitage-rubocop (~> 0.89)
86
96
  bundler (~> 2.1)
97
+ pry (~> 0.13)
87
98
  rake (~> 13.0)
88
99
  rspec (~> 3.9)
89
- simplecov (~> 0.18)
100
+ simplecov (~> 0.19)
90
101
  smart_schema!
91
102
 
92
103
  BUNDLED WITH
data/README.md CHANGED
@@ -1 +1,133 @@
1
- # SmartCore::Schema
1
+ # SmartCore::Schema [![Gem Version](https://badge.fury.io/rb/smart_schema.svg)](https://badge.fury.io/rb/smart_schema) [![Build Status](https://travis-ci.org/smart-rb/smart_schema.svg?branch=master)](https://travis-ci.org/smart-rb/smart_schema)
2
+
3
+ `SmartCore::Schema` is a schema validator for `Hash`-like data structures.
4
+
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
+
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).
8
+
9
+ Works in predicate style and in OOP/Monadic result object style. Enjoy :)
10
+
11
+ ## Installation
12
+
13
+ ```ruby
14
+ gem 'smart_schema'
15
+ ```
16
+
17
+ ```shell
18
+ bundle install
19
+ # --- or ---
20
+ gem install smart_schema
21
+ ```
22
+
23
+ ```ruby
24
+ require 'smart_core/schema'
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Synopsis
30
+
31
+ - key requirement: `required` and `optional`;
32
+ - type validation: `type`;
33
+ - `nil` control: `filled`;
34
+ - nested definitions: `do ... end`;
35
+ - supported types: see `smart_types` gem;
36
+
37
+ ```ruby
38
+ class MySchema < SmartCore::Schema
39
+ schema do
40
+ required(:key) do
41
+ optional(:data).type(:string).filled
42
+ optional(:value).type(:numeric)
43
+ required(:name).type(:string)
44
+
45
+ required(:nested) do
46
+ optional(:version).filled
47
+ end
48
+ end
49
+
50
+ required(:another_key).filled
51
+ end
52
+
53
+ # you can open already defined schema and continue schema definitioning:
54
+ #
55
+ # schema do
56
+ # required(:third_key).filled.type(:string)
57
+ # end
58
+ end
59
+ ```
60
+
61
+ ```ruby
62
+ MySchema.new.valid?({
63
+ key: {
64
+ data: '5',
65
+ value: 1,
66
+ name: 'D@iVeR'
67
+ nested: {}
68
+ }
69
+ another_key: true
70
+ }) # => true
71
+
72
+ MySchema.new.valid?({
73
+ key: {
74
+ data: nil,
75
+ value: 1,
76
+ name: 'D@iVeR'
77
+ nested: {}
78
+ }
79
+ }) # => false (missing :another_key, key->data is not filled)
80
+ ```
81
+
82
+ ```ruby
83
+ result = MySchema.new.validate(
84
+ key: { data: nil, value: '1', name: 'D@iVeR' },
85
+ another_key: nil,
86
+ third_key: 'test'
87
+ )
88
+
89
+ # => outputs:
90
+ # #<SmartCore::Schema::Result:0x00007ffcd8926990
91
+ # @errors={"key.data"=>[:non_filled], "key.value"=>[:invalid_type], "key.nested"=>[:required_key_not_found], "another_key"=>[:non_filled]},
92
+ # @extra_keys=#<Set: {"third_key"}>,
93
+ # @source={:key=>{:data=>nil, :value=>"1", :name=>"D@iVeR"}, :another_key=>nil, :third_key=>"test"}>
94
+
95
+ result.success? # => false
96
+ result.extra_keys # => <Set: {"third_key"}>
97
+ result.errors # =>
98
+ {
99
+ "key.data"=>[:non_filled],
100
+ "key.value"=>[:invalid_type],
101
+ "key.nested"=>[:required_key_not_found],
102
+ "another_key"=>[:non_filled]
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Roadmap
109
+
110
+ - **(0.2.0)** - schema inheritance;
111
+ - **(0.3.0)** - schema composition (`required(:key).schema(SchemaClass)`) (`compose_with(AnotherSchema)`);
112
+ - **(0.4.0)** - error messages (that are consistent with error codes);
113
+ - **(0.5.0)** - `smart_type-system` integration;
114
+ - **(0.6.0)** - support for another data structures (such as YAML strings, JSON strings, `Struct`, `OpenStruct`s, custom `Object`s and etc);
115
+ - **(0.7.0)** - think about pattern matching;
116
+
117
+ ---
118
+
119
+ ## Contributing
120
+
121
+ - Fork it ( https://github.com/smart-rb/smart_schema )
122
+ - Create your feature branch (`git checkout -b feature/my-new-feature`)
123
+ - Commit your changes (`git commit -am '[feature_context] Add some feature'`)
124
+ - Push to the branch (`git push origin feature/my-new-feature`)
125
+ - Create new Pull Request
126
+
127
+ ## License
128
+
129
+ Released under MIT License.
130
+
131
+ ## Authors
132
+
133
+ [Rustam Ibragimov](https://github.com/0exp)
@@ -1,5 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'smart_core'
4
+ require 'smart_core/types'
5
+ require 'set'
6
+
3
7
  # @api pulic
4
8
  # @since 0.1.0
5
9
  module SmartCore
@@ -7,5 +11,41 @@ module SmartCore
7
11
  # @since 0.1.0
8
12
  class Schema
9
13
  require_relative 'schema/version'
14
+ require_relative 'schema/errors'
15
+ require_relative 'schema/key_control'
16
+ require_relative 'schema/result'
17
+ require_relative 'schema/checker'
18
+ require_relative 'schema/dsl'
19
+
20
+ # @since 0.1.0
21
+ include SmartCore::Schema::DSL
22
+
23
+ # @param verifiable_hash [Hash<String|Symbol,Any>]
24
+ # @return [Boolean]
25
+ #
26
+ # @api public
27
+ # @since 0.1.0
28
+ def valid?(verifiable_hash)
29
+ validate(verifiable_hash).success?
30
+ end
31
+
32
+ # @param verifiable_hash [Hash<String|Symbol,Any>]
33
+ # @return [SmartCore::Schema::Result]
34
+ #
35
+ # @api public
36
+ # @since 0.1.0
37
+ def validate(verifiable_hash)
38
+ schema_checker.check!(verifiable_hash)
39
+ end
40
+
41
+ private
42
+
43
+ # @return [SmartCore::Schema::Checker]
44
+ #
45
+ # @api private
46
+ # @since 0.1.0
47
+ def schema_checker
48
+ self.class.__schema_checker__
49
+ end
10
50
  end
11
51
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class SmartCore::Schema::Checker
6
+ require_relative 'checker/verifiable_hash'
7
+ require_relative 'checker/rules'
8
+ require_relative 'checker/reconciler'
9
+
10
+ # @return [void]
11
+ #
12
+ # @api private
13
+ # @since 0.1.0
14
+ def initialize
15
+ @reconciler = Reconciler::Constructor.create
16
+ @lock = SmartCore::Engine::Lock.new
17
+ end
18
+
19
+ # @param verifiable_hash [Hash<String|Symbol,Any>]
20
+ # @return [SmartCore::Schema::Result]
21
+ #
22
+ # @api private
23
+ # @since 0.1.0
24
+ def check!(verifiable_hash)
25
+ thread_safe do
26
+ raise(SmartCore::Schema::ArgumentError, <<~ERROR_MESSAGE) unless verifiable_hash.is_a?(Hash)
27
+ Verifiable hash should be a type of ::Hash
28
+ ERROR_MESSAGE
29
+
30
+ reconciler.__match!(VerifiableHash.new(verifiable_hash)).complete!
31
+ end
32
+ end
33
+
34
+ # @param definitions [Block]
35
+ # @return [void]
36
+ #
37
+ # @api private
38
+ # @since 0.1.0
39
+ def append_schema_definitions(&definitions)
40
+ thread_safe { add_schema_definitions(&definitions) }
41
+ end
42
+
43
+ # @param another_checker [SmartCore::Schema::Checker]
44
+ # @return [SmartCore::Schema::Checker]
45
+ #
46
+ # @api private
47
+ # @since 0.1.0
48
+ def combine_with(another_checker)
49
+ thread_safe { self } # TODO (0.x.0): merge the definitions and return self
50
+ end
51
+
52
+ private
53
+
54
+ # @return [SmartCore::Schema::Checker::Reconciler]
55
+ #
56
+ # @api private
57
+ # @since 0.1.0
58
+ attr_reader :reconciler
59
+
60
+ # @param definitions [Block]
61
+ # @return [void]
62
+ #
63
+ # @api private
64
+ # @since 0.1.0
65
+ def add_schema_definitions(&definitions)
66
+ raise(SmartCore::Schema::ArgumentError, <<~ERROR_MESSAGE) unless block_given?
67
+ Schema definitions is not provided (you should provide Block argument)
68
+ ERROR_MESSAGE
69
+
70
+ Reconciler::Constructor.append_definitions(reconciler, &definitions)
71
+ end
72
+
73
+ # @param block [Block]
74
+ # @return [Any]
75
+ #
76
+ # @api private
77
+ # @since 0.1.0
78
+ def thread_safe(&block)
79
+ @lock.synchronize(&block)
80
+ end
81
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.1.0
5
+ class SmartCore::Schema::Checker::Reconciler
6
+ require_relative 'reconciler/constructor'
7
+ require_relative 'reconciler/matcher'
8
+
9
+ # @return [void]
10
+ #
11
+ # @api private
12
+ # @since 0.1.0
13
+ def initialize
14
+ @rules = SmartCore::Schema::Checker::Rules.new
15
+ @lock = SmartCore::Engine::Lock.new
16
+ end
17
+
18
+ # @param verifiable_hash [SmartCore::Schema::Checker::VerifiableHash]
19
+ # @return [void]
20
+ #
21
+ # @api private
22
+ # @since 0.1.0
23
+ def __match!(verifiable_hash)
24
+ thread_safe { SmartCore::Schema::Checker::Reconciler::Matcher.match(self, verifiable_hash) }
25
+ end
26
+
27
+ # @return [SmartCore::Schema::Checker::Rules::ExtraKeys]
28
+ #
29
+ # @api private
30
+ # @since 0.1.0
31
+ def __extra_keys_contract
32
+ SmartCore::Schema::Checker::Rules::ExtraKeys
33
+ end
34
+
35
+ # @return [SmartCore::Schema::Checker::Rules]
36
+ #
37
+ # @api private
38
+ # @since 0.1.0
39
+ def __contract_rules
40
+ thread_safe { rules }
41
+ end
42
+
43
+ # @param schema_key [String, Symbol]
44
+ # @param nested_definitions [Block]
45
+ # @return [SmartCore::Schema::Checker::Rules::Required]
46
+ #
47
+ # @api public
48
+ # @since 0.1.0
49
+ def required(schema_key, &nested_definitions)
50
+ thread_safe do
51
+ rule = SmartCore::Schema::Checker::Rules::Required.new(schema_key, &nested_definitions)
52
+ rule.tap { rules[rule.schema_key] = rule }
53
+ end
54
+ end
55
+
56
+ # @param schema_key [String, Symbol]
57
+ # @param nested_definitions [Block]
58
+ # @return [SmartCore::Schema::Checker::Rules::Optional]
59
+ #
60
+ # @api public
61
+ # @since 0.1.0
62
+ def optional(schema_key, &nested_definitions)
63
+ thread_safe do
64
+ rule = SmartCore::Schema::Checker::Rules::Optional.new(schema_key, &nested_definitions)
65
+ rule.tap { rules[rule.schema_key] = rule }
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ # @return [SmartCore::Schema::Checker::Rules]
72
+ #
73
+ # @api private
74
+ # @since 0.1.0
75
+ attr_reader :rules
76
+
77
+ # @param block [Block]
78
+ # @return [Any]
79
+ #
80
+ # @api private
81
+ # @since 0.1.0
82
+ def thread_safe(&block)
83
+ @lock.synchronize(&block)
84
+ end
85
+ end