smart_validator 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 69d6afe21ce43c069bc1b567d47ae1b844182c6fac2d83017613274a3787018e
4
+ data.tar.gz: f349084e071c1edaed03fae856c8799f053144c7a79e65cb07a5252ed644b51b
5
+ SHA512:
6
+ metadata.gz: b3d3660286a1991c372dfc98f95c37fa10fc531ebd6ed24aa6083c8c776376a86f329d6104eb55bd0935a445da7b244c4b9b157eedca84f6eba570a11487bd55
7
+ data.tar.gz: 70f2a6e985189aa11bfb932b8026edd4932126fcca922cd95c8643dea661698e4462fc8d3bdcd4b9df778facced655e4d34b90522661e3a38db2c969f7da32b2
@@ -0,0 +1,39 @@
1
+ name: Build
2
+ on: [push, pull_request]
3
+ env:
4
+ FULL_COVERAGE_CHECK: true
5
+ jobs:
6
+ full-check:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - uses: ruby/setup-ruby@v1
11
+ with:
12
+ ruby-version: 3
13
+ bundler-cache: true
14
+ - name: Run Linter
15
+ run: bundle exec ci-helper RubocopLint
16
+ - name: Check missed spec suffixes
17
+ run: bundle exec ci-helper CheckSpecSuffixes --extra-paths spec/*.rb --ignored-paths spec/*_helper.rb
18
+ - name: Run specs
19
+ run: bundle exec ci-helper RunSpecs
20
+ - name: Audit
21
+ run: bundle exec ci-helper BundlerAudit
22
+ - name: Coveralls
23
+ uses: coverallsapp/github-action@master
24
+ with:
25
+ github-token: ${{ secrets.GITHUB_TOKEN }}
26
+ specs:
27
+ runs-on: ubuntu-latest
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ ruby: [3.0, 3.1, 3.2]
32
+ steps:
33
+ - uses: actions/checkout@v2
34
+ - uses: ruby/setup-ruby@v1
35
+ with:
36
+ ruby-version: ${{ matrix.ruby }}
37
+ bundler-cache: true
38
+ - name: Run specs
39
+ run: bundle exec rspec
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ .rspec_status
10
+ /.idea
11
+ .ruby-version
12
+ *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,32 @@
1
+ inherit_gem:
2
+ armitage-rubocop:
3
+ - lib/rubocop.general.yml
4
+ - lib/rubocop.rake.yml
5
+ - lib/rubocop.rspec.yml
6
+
7
+ AllCops:
8
+ TargetRubyVersion: 3.0
9
+ NewCops: enable
10
+ Include:
11
+ - lib/**/*.rb
12
+ - spec/**/*.rb
13
+ - Gemfile
14
+ - Rakefile
15
+ - smart_validator.gemspec
16
+ - bin/console
17
+ DisplayCopNames: true
18
+ SuggestExtensions: false
19
+
20
+ Naming/MethodParameterName:
21
+ AllowedNames: ["x", "y", "z"]
22
+
23
+ Style/HashConversion:
24
+ Exclude:
25
+ - spec/**/*_spec.rb
26
+
27
+ Naming/RescuedExceptionsVariableName:
28
+ Enabled: false
29
+
30
+ Naming/FileName:
31
+ Exclude:
32
+ - lib/smart-validator.rb
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ [0.4.0] - 2024-03-15
5
+ - Moved to `smart-rb` (`CadoLabs`);
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,158 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ smart_validator (0.4.0)
5
+ qonfig (~> 0.28)
6
+ smart_initializer (~> 0.11)
7
+ smart_schema (~> 0.11)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (7.1.3.2)
13
+ base64
14
+ bigdecimal
15
+ concurrent-ruby (~> 1.0, >= 1.0.2)
16
+ connection_pool (>= 2.2.5)
17
+ drb
18
+ i18n (>= 1.6, < 2)
19
+ minitest (>= 5.1)
20
+ mutex_m
21
+ tzinfo (~> 2.0)
22
+ armitage-rubocop (1.59.0)
23
+ rubocop (= 1.59.0)
24
+ rubocop-capybara (= 2.19.0)
25
+ rubocop-factory_bot (= 2.24.0)
26
+ rubocop-performance (= 1.20.1)
27
+ rubocop-rails (= 2.23.1)
28
+ rubocop-rake (= 0.6.0)
29
+ rubocop-rspec (= 2.25.0)
30
+ ast (2.4.2)
31
+ base64 (0.2.0)
32
+ bigdecimal (3.1.7)
33
+ bundler-audit (0.9.1)
34
+ bundler (>= 1.2.0, < 3)
35
+ thor (~> 1.0)
36
+ ci-helper (0.6.0)
37
+ colorize (~> 1.1)
38
+ dry-inflector (~> 1.0)
39
+ umbrellio-sequel-plugins (~> 0.14)
40
+ coderay (1.1.3)
41
+ colorize (1.1.0)
42
+ concurrent-ruby (1.2.3)
43
+ connection_pool (2.4.1)
44
+ diff-lcs (1.5.1)
45
+ docile (1.4.0)
46
+ drb (2.2.1)
47
+ dry-inflector (1.0.0)
48
+ i18n (1.14.4)
49
+ concurrent-ruby (~> 1.0)
50
+ json (2.7.1)
51
+ language_server-protocol (3.17.0.3)
52
+ method_source (1.0.0)
53
+ minitest (5.22.3)
54
+ mutex_m (0.2.0)
55
+ parallel (1.24.0)
56
+ parser (3.3.0.5)
57
+ ast (~> 2.4.1)
58
+ racc
59
+ pry (0.14.2)
60
+ coderay (~> 1.1)
61
+ method_source (~> 1.0)
62
+ qonfig (0.28.0)
63
+ racc (1.7.3)
64
+ rack (3.0.9.1)
65
+ rainbow (3.1.1)
66
+ rake (13.1.0)
67
+ regexp_parser (2.9.0)
68
+ rexml (3.2.6)
69
+ rspec (3.13.0)
70
+ rspec-core (~> 3.13.0)
71
+ rspec-expectations (~> 3.13.0)
72
+ rspec-mocks (~> 3.13.0)
73
+ rspec-core (3.13.0)
74
+ rspec-support (~> 3.13.0)
75
+ rspec-expectations (3.13.0)
76
+ diff-lcs (>= 1.2.0, < 2.0)
77
+ rspec-support (~> 3.13.0)
78
+ rspec-mocks (3.13.0)
79
+ diff-lcs (>= 1.2.0, < 2.0)
80
+ rspec-support (~> 3.13.0)
81
+ rspec-support (3.13.1)
82
+ rubocop (1.59.0)
83
+ json (~> 2.3)
84
+ language_server-protocol (>= 3.17.0)
85
+ parallel (~> 1.10)
86
+ parser (>= 3.2.2.4)
87
+ rainbow (>= 2.2.2, < 4.0)
88
+ regexp_parser (>= 1.8, < 3.0)
89
+ rexml (>= 3.2.5, < 4.0)
90
+ rubocop-ast (>= 1.30.0, < 2.0)
91
+ ruby-progressbar (~> 1.7)
92
+ unicode-display_width (>= 2.4.0, < 3.0)
93
+ rubocop-ast (1.31.2)
94
+ parser (>= 3.3.0.4)
95
+ rubocop-capybara (2.19.0)
96
+ rubocop (~> 1.41)
97
+ rubocop-factory_bot (2.24.0)
98
+ rubocop (~> 1.33)
99
+ rubocop-performance (1.20.1)
100
+ rubocop (>= 1.48.1, < 2.0)
101
+ rubocop-ast (>= 1.30.0, < 2.0)
102
+ rubocop-rails (2.23.1)
103
+ activesupport (>= 4.2.0)
104
+ rack (>= 1.1)
105
+ rubocop (>= 1.33.0, < 2.0)
106
+ rubocop-ast (>= 1.30.0, < 2.0)
107
+ rubocop-rake (0.6.0)
108
+ rubocop (~> 1.0)
109
+ rubocop-rspec (2.25.0)
110
+ rubocop (~> 1.40)
111
+ rubocop-capybara (~> 2.17)
112
+ rubocop-factory_bot (~> 2.22)
113
+ ruby-progressbar (1.13.0)
114
+ sequel (5.78.0)
115
+ bigdecimal
116
+ simplecov (0.22.0)
117
+ docile (~> 1.1)
118
+ simplecov-html (~> 0.11)
119
+ simplecov_json_formatter (~> 0.1)
120
+ simplecov-html (0.12.3)
121
+ simplecov-lcov (0.8.0)
122
+ simplecov_json_formatter (0.1.4)
123
+ smart_engine (0.17.0)
124
+ smart_initializer (0.11.1)
125
+ qonfig (~> 0.24)
126
+ smart_engine (~> 0.16)
127
+ smart_types (~> 0.8)
128
+ smart_schema (0.11.0)
129
+ smart_engine (~> 0.17)
130
+ smart_types (~> 0.8)
131
+ smart_types (0.8.0)
132
+ smart_engine (~> 0.11)
133
+ symbiont-ruby (0.7.0)
134
+ thor (1.3.1)
135
+ tzinfo (2.0.6)
136
+ concurrent-ruby (~> 1.0)
137
+ umbrellio-sequel-plugins (0.14.0.192)
138
+ sequel
139
+ symbiont-ruby
140
+ unicode-display_width (2.5.0)
141
+
142
+ PLATFORMS
143
+ arm64-darwin-23
144
+ ruby
145
+
146
+ DEPENDENCIES
147
+ armitage-rubocop (~> 1.59)
148
+ bundler-audit (~> 0.9)
149
+ ci-helper (~> 0.6)
150
+ pry (~> 0.14)
151
+ rake (~> 13.1)
152
+ rspec (~> 3.13)
153
+ simplecov (~> 0.22)
154
+ simplecov-lcov (~> 0.8)
155
+ smart_validator!
156
+
157
+ BUNDLED WITH
158
+ 2.5.3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 AnotherRegularDude (https://github.com/AnotherRegularDude)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # SmartCore::Validator
2
+
3
+ ---
4
+
5
+ <img src="https://github.com/Cado-Labs/cado-labs-resources/blob/main/cado_labs_supporting_rounded.svg" alt="Supported by Cado Labs" />
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```ruby
12
+ gem 'smart_validator'
13
+ ```
14
+
15
+ ```shell
16
+ bundle install
17
+ # --- or ---
18
+ gem install smart_validator
19
+ ```
20
+
21
+ ```ruby
22
+ require 'smart_core/validator'
23
+ ```
24
+
25
+ ---
26
+
27
+ ## Usage
28
+
29
+ - soon (you can check specs for usage examples at this moment)
30
+
31
+ ---
32
+
33
+ ## Contributing
34
+
35
+ - Fork it ( https://github.com/smart-rb/smart_validator )
36
+ - Create your feature branch (`git checkout -b feature/my-new-feature`)
37
+ - Commit your changes (`git commit -am '[feature_context] Add some feature'`)
38
+ - Push to the branch (`git push origin feature/my-new-feature`)
39
+ - Create new Pull Request
40
+
41
+ ## License
42
+
43
+ Released under MIT License.
44
+
45
+ ## Authors
46
+
47
+ [AnotherRegularDude] (https://github.com/AnotherRegularDude)
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop'
6
+ require 'rubocop/rake_task'
7
+ require 'rubocop-performance'
8
+ require 'rubocop-rspec'
9
+ require 'rubocop-rake'
10
+
11
+ RuboCop::RakeTask.new(:rubocop) do |t|
12
+ config_path = File.expand_path(File.join('.rubocop.yml'), __dir__)
13
+ t.options = ['--config', config_path]
14
+ t.requires << 'rubocop-rspec'
15
+ t.requires << 'rubocop-performance'
16
+ t.requires << 'rubocop-rake'
17
+ end
18
+
19
+ RSpec::Core::RakeTask.new(:rspec)
20
+
21
+ task default: :rspec
data/bin/console ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'smart_core/validator'
6
+ require 'pry'
7
+
8
+ Pry.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator::ClassState::Container
4
+ attr_accessor :schema_class
5
+ attr_accessor :configuration
6
+ attr_accessor :rules
7
+
8
+ class << self
9
+ def new(*args)
10
+ super.freeze
11
+ end
12
+
13
+ def create_base_container
14
+ new(build_base_config)
15
+ end
16
+
17
+ private
18
+
19
+ # TODO: Use global config until #configure is called on the class.
20
+ def build_base_config
21
+ config = Qonfig::DataSet.build do
22
+ setting :errors_managing_type, :error_per_attribute
23
+ setting :smart_initializer do
24
+ # TODO: After calling configure method,
25
+ # also apply changes to the #__settings__ of the Contract class.
26
+ compose SmartCore::Initializer::Configuration.config.class
27
+ end
28
+
29
+ validate :errors_managing_type do |value|
30
+ SmartCore::Validator::ErrorsController::ALLOWED_MANAGING_TYPES.key?(value)
31
+ end
32
+ end
33
+
34
+ config.tap(&:freeze!)
35
+ end
36
+ end
37
+
38
+ def initialize(duplicated_configuration)
39
+ self.schema_class = Class.new(SmartCore::Schema)
40
+ self.configuration = duplicated_configuration
41
+ self.rules = []
42
+ end
43
+
44
+ private
45
+
46
+ def initialize_copy(parent_container)
47
+ super
48
+ self.schema_class = Class.new(SmartCore::Schema)
49
+ self.rules = []
50
+ self.configuration = parent_container.configuration.dup
51
+ freeze
52
+ end
53
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::ClassState
4
+ module InitializeMixin
5
+ def self.included(mod)
6
+ super
7
+
8
+ mod.singleton_class.attr_accessor :__state_container__
9
+ mod.singleton_class.send(:protected, :__state_container__=)
10
+ mod.instance_variable_set(:@__state_container__, Container.create_base_container)
11
+ add_delegators_to(mod)
12
+ end
13
+
14
+ def self.add_delegators_to(mod)
15
+ mod.instance_eval do
16
+ extend Forwardable
17
+ extend SingleForwardable
18
+
19
+ def_single_delegators :__state_container__, :schema_class, :configuration, :rules
20
+ def_single_delegators :configuration, :configure, :settings
21
+ def_instance_delegators 'self.class', :settings, :rules, :schema_class
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::ClassState
4
+ require_relative 'class_state/container'
5
+ require_relative 'class_state/initialize_mixin'
6
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator
4
+ class Contract
5
+ include SmartCore::Initializer
6
+ include ClassState::InitializeMixin
7
+
8
+ class << self
9
+ def inherited(subclass)
10
+ super
11
+ subclass.__state_container__ = __state_container__.dup
12
+ end
13
+
14
+ def param(*)
15
+ raise NotImplementedError, 'params are not permitted to use in validators'
16
+ end
17
+
18
+ # NOTE: In Ruby 2.6.x need to initialize instance without arguments if they not expected.
19
+ def call(data_to_validate, **kwargs)
20
+ instance = kwargs.empty? ? new : new(**kwargs)
21
+ instance.call(data_to_validate)
22
+ end
23
+
24
+ def schema(&block)
25
+ schema_class.schema(&block)
26
+ end
27
+
28
+ # TODO: Delegate all this logic to the Rules.
29
+ def rule(checked_value, &block)
30
+ rules << Rule.new(checked_value, block)
31
+ end
32
+
33
+ def rule_for_each(*checked_values, &block)
34
+ checked_values.each { |checked_value| rule(checked_value, &block) }
35
+ end
36
+ end
37
+
38
+ def call(data_to_validate)
39
+ self.data = Utils.deeply_symbolize_freeze(data_to_validate)
40
+ self.errors_controller = ErrorsController.resolve_from(settings.errors_managing_type)
41
+ self.rule_execution_context = build_rule_execution_context
42
+
43
+ result = build_schema_instance.validate(data)
44
+ errors_controller.process_smart_schema_result!(result)
45
+ return build_result if errors_controller.validation_fails?
46
+
47
+ check_with_rules!
48
+ build_result
49
+ end
50
+
51
+ private
52
+
53
+ attr_accessor :data
54
+ attr_accessor :errors_controller
55
+ attr_accessor :rule_execution_context
56
+
57
+ def build_rule_execution_context
58
+ RuleExecutionContext.new(data, __options__)
59
+ end
60
+
61
+ def build_schema_instance
62
+ schema_class.new
63
+ end
64
+
65
+ def build_result
66
+ Result.build_from_state(data, errors_controller)
67
+ end
68
+
69
+ def check_with_rules!
70
+ rules.each do |rule|
71
+ next if errors_controller.error_will_be_skipped?(rule.checked_attr)
72
+
73
+ result = rule_execution_context.execute!(rule)
74
+ errors_controller.process_rule!(result)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator::Errors
4
+ extend Forwardable
5
+ include Enumerable
6
+
7
+ def_delegators :@errors_container, :each, :as_json, :to_json
8
+
9
+ def initialize
10
+ self.errors_container = {}
11
+ end
12
+
13
+ def safely_add!(attr_path:, error_code:)
14
+ errors_container[attr_path.to_s] ||= error_code
15
+ end
16
+
17
+ def safely_merge!(attr_path:, errors_enum:)
18
+ errors_container[attr_path.to_s] ||= Set.new
19
+ errors_container[attr_path.to_s].merge(errors_enum)
20
+ end
21
+
22
+ def [](attr_path)
23
+ errors_container[attr_path.to_s]
24
+ end
25
+
26
+ def freeze
27
+ super.tap { errors_container.transform_values(&:freeze) }
28
+ end
29
+
30
+ def to_h
31
+ errors_container.dup
32
+ end
33
+
34
+ def attribute_already_exists?(attr_path)
35
+ errors_container.key?(attr_path.to_s)
36
+ end
37
+
38
+ private
39
+
40
+ attr_accessor :errors_container
41
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator::ErrorsController::Base
4
+ attr_reader :errors
5
+
6
+ def initialize
7
+ @errors = SmartCore::Validator::Errors.new
8
+ end
9
+
10
+ def process_smart_schema_result!(result)
11
+ result.errors.each do |attr_path, error_codes|
12
+ apply_data_to_errors!(attr_path, *error_codes)
13
+ end
14
+ end
15
+
16
+ def process_rule!(result)
17
+ return if result == nil
18
+
19
+ apply_data_to_errors!(result.attr_path, result.code)
20
+ end
21
+
22
+ def validation_fails?
23
+ @validation_fails ||= errors.any?
24
+ end
25
+
26
+ # @!method apply_data_to_errors!
27
+ # @!method error_will_be_skipped?
28
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::ErrorsController
4
+ class ErrorPerAttribute < Base
5
+ def error_will_be_skipped?(attr_path)
6
+ errors.attribute_already_exists?(attr_path)
7
+ end
8
+
9
+ private
10
+
11
+ def apply_data_to_errors!(attr_path, *error_codes)
12
+ errors.safely_add!(attr_path: attr_path, error_code: error_codes.first)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::ErrorsController
4
+ class ManyErrorsPerAttribute < Base
5
+ def error_will_be_skipped?(_attr_path)
6
+ false
7
+ end
8
+
9
+ private
10
+
11
+ def apply_data_to_errors!(attr_path, *error_codes)
12
+ errors.safely_merge!(attr_path: attr_path, errors_enum: error_codes)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::ErrorsController
4
+ require_relative 'errors_controller/base'
5
+ require_relative 'errors_controller/error_per_attribute'
6
+ require_relative 'errors_controller/many_errors_per_attribute'
7
+
8
+ extend self
9
+
10
+ ALLOWED_MANAGING_TYPES = {
11
+ error_per_attribute: 'ErrorPerAttribute',
12
+ many_errors_per_attribute: 'ManyErrorsPerAttribute'
13
+ }.freeze
14
+
15
+ def resolve_from(managing_type)
16
+ unless ALLOWED_MANAGING_TYPES.key?(managing_type)
17
+ raise ArgumentError, "invalid handling type. allowed is: #{ALLOWED_MANAGING_TYPES.keys}"
18
+ end
19
+
20
+ name = ALLOWED_MANAGING_TYPES[managing_type]
21
+ ::SmartCore::Validator::ErrorsController.const_get(name).new
22
+ end
23
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator::Result
4
+ attr_accessor :errors
5
+ attr_accessor :data
6
+ attr_accessor :failed
7
+
8
+ def self.build_from_state(input_data, errors_controller)
9
+ res = new(
10
+ data: input_data,
11
+ errors: errors_controller.errors,
12
+ failed: errors_controller.validation_fails?
13
+ )
14
+ res.freeze
15
+ end
16
+
17
+ def initialize(errors:, data:, failed:)
18
+ self.errors = errors
19
+ self.data = data
20
+ self.failed = failed
21
+ end
22
+
23
+ def success?
24
+ !failed
25
+ end
26
+
27
+ def freeze
28
+ super.tap do
29
+ errors.freeze
30
+ data.freeze
31
+ end
32
+ end
33
+
34
+ alias_method :failed?, :failed
35
+ alias_method :to_h, :data
36
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator::Rule
4
+ attr_reader :checked_attr
5
+
6
+ def initialize(checked_attr, checking_block)
7
+ @checked_attr = checked_attr.to_s
8
+ @attr_path = @checked_attr.split('.').map(&:to_sym)
9
+ @checking_block = checking_block
10
+ end
11
+
12
+ def executable?(data)
13
+ checked_path, checked_key = @attr_path[..-2], @attr_path.last
14
+ data = data.dig(*checked_path) if checked_path.any?
15
+ data&.key?(checked_key)
16
+ end
17
+
18
+ def value_from(data)
19
+ data.dig(*@attr_path)
20
+ end
21
+
22
+ def to_proc
23
+ @checking_block
24
+ end
25
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator::RuleExecutionContext::Failure < StandardError
4
+ attr_reader :code
5
+
6
+ def initialize(code)
7
+ @code = code
8
+ super(code)
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ SmartCore::Validator::RuleExecutionContext::Result = Struct.new(:attr_path, :code)
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SmartCore::Validator::RuleExecutionContext
4
+ require_relative 'rule_execition_context/failure'
5
+ require_relative 'rule_execition_context/result'
6
+
7
+ def initialize(data, dependencies_to_bind)
8
+ self.data = data
9
+ bind_to_self!(dependencies_to_bind)
10
+ end
11
+
12
+ def execute!(rule)
13
+ self.value = rule.value_from(data)
14
+ instance_eval(&rule) if rule.executable?(data)
15
+ nil
16
+ rescue Failure => e
17
+ Result.new(rule.checked_attr, e.code).freeze
18
+ end
19
+
20
+ private
21
+
22
+ attr_accessor :data
23
+ attr_accessor :value
24
+
25
+ def bind_to_self!(dependencies)
26
+ dependencies.each do |name, value|
27
+ if singleton_class.method_defined?(name)
28
+ raise ArgumentError, "dependency name conflicts with standard method: #{name}"
29
+ end
30
+
31
+ define_singleton_method(name) { value }
32
+ end
33
+ end
34
+
35
+ def failure(code)
36
+ raise Failure, code
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore::Validator::Utils
4
+ extend self
5
+
6
+ def deeply_symbolize_freeze(hash)
7
+ hash.dup.freeze.transform_keys(&:to_sym).transform_values do |value|
8
+ next value.dup.freeze unless value.is_a?(Hash)
9
+
10
+ deeply_symbolize_freeze(value)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartCore
4
+ module Validator
5
+ VERSION = '0.4.0'
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'qonfig'
4
+ require 'forwardable'
5
+ require 'smart_core/schema'
6
+ require 'smart_core/initializer'
7
+
8
+ module SmartCore
9
+ module Validator
10
+ ERROR_CODE_HANDLING_TYPES = %i[one many].freeze
11
+
12
+ require_relative 'validator/version'
13
+ require_relative 'validator/utils'
14
+ require_relative 'validator/errors_controller'
15
+ require_relative 'validator/errors'
16
+ require_relative 'validator/class_state'
17
+ require_relative 'validator/rule'
18
+ require_relative 'validator/rule_execution_context'
19
+ require_relative 'validator/result'
20
+ require_relative 'validator/contract'
21
+ end
22
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/smart_core/validator/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.0')
7
+
8
+ spec.name = 'smart_validator'
9
+ spec.version = SmartCore::Validator::VERSION
10
+ spec.authors = ['JustAnotherDude']
11
+ spec.email = ['vanyaz158@gmail.com']
12
+
13
+ spec.summary = 'Custom DSL-first validator with a conviniet declarative API.'
14
+ spec.description = 'Validation layer for any business logic with a declarative rule-oriented API.'
15
+ spec.homepage = 'https://github.com/smart-rb/smart_validator'
16
+ spec.license = 'MIT'
17
+
18
+ spec.metadata['homepage_uri'] =
19
+ spec.homepage
20
+ spec.metadata['source_code_uri'] =
21
+ 'https://github.com/smart-rb/smart_validator/blob/master'
22
+ spec.metadata['changelog_uri'] =
23
+ 'https://github.com/smart-rb/smart_validator/blob/master/CHANGELOG.md'
24
+
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
+ end
28
+
29
+ spec.bindir = 'exe'
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
32
+
33
+ spec.add_dependency 'qonfig', '~> 0.28'
34
+ spec.add_dependency 'smart_initializer', '~> 0.11'
35
+ spec.add_dependency 'smart_schema', '~> 0.11'
36
+
37
+ spec.add_development_dependency 'bundler-audit', '~> 0.9'
38
+ spec.add_development_dependency 'ci-helper', '~> 0.6'
39
+ spec.add_development_dependency 'pry', '~> 0.14'
40
+ spec.add_development_dependency 'rake', '~> 13.1'
41
+ spec.add_development_dependency 'rspec', '~> 3.13'
42
+ spec.add_development_dependency 'armitage-rubocop', '~> 1.59'
43
+ spec.add_development_dependency 'simplecov', '~> 0.22'
44
+ spec.add_development_dependency 'simplecov-lcov', '~> 0.8'
45
+ end
metadata ADDED
@@ -0,0 +1,231 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smart_validator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.0
5
+ platform: ruby
6
+ authors:
7
+ - JustAnotherDude
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: qonfig
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.28'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.28'
27
+ - !ruby/object:Gem::Dependency
28
+ name: smart_initializer
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.11'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.11'
41
+ - !ruby/object:Gem::Dependency
42
+ name: smart_schema
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.11'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler-audit
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.9'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.9'
69
+ - !ruby/object:Gem::Dependency
70
+ name: ci-helper
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.6'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.6'
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.14'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.14'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '13.1'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '13.1'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.13'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.13'
125
+ - !ruby/object:Gem::Dependency
126
+ name: armitage-rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.59'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.59'
139
+ - !ruby/object:Gem::Dependency
140
+ name: simplecov
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.22'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.22'
153
+ - !ruby/object:Gem::Dependency
154
+ name: simplecov-lcov
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.8'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.8'
167
+ description: Validation layer for any business logic with a declarative rule-oriented
168
+ API.
169
+ email:
170
+ - vanyaz158@gmail.com
171
+ executables: []
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - ".github/workflows/test.yml"
176
+ - ".gitignore"
177
+ - ".rspec"
178
+ - ".rubocop.yml"
179
+ - CHANGELOG.md
180
+ - Gemfile
181
+ - Gemfile.lock
182
+ - LICENSE.txt
183
+ - README.md
184
+ - Rakefile
185
+ - bin/console
186
+ - bin/setup
187
+ - lib/smart_core/validator.rb
188
+ - lib/smart_core/validator/class_state.rb
189
+ - lib/smart_core/validator/class_state/container.rb
190
+ - lib/smart_core/validator/class_state/initialize_mixin.rb
191
+ - lib/smart_core/validator/contract.rb
192
+ - lib/smart_core/validator/errors.rb
193
+ - lib/smart_core/validator/errors_controller.rb
194
+ - lib/smart_core/validator/errors_controller/base.rb
195
+ - lib/smart_core/validator/errors_controller/error_per_attribute.rb
196
+ - lib/smart_core/validator/errors_controller/many_errors_per_attribute.rb
197
+ - lib/smart_core/validator/result.rb
198
+ - lib/smart_core/validator/rule.rb
199
+ - lib/smart_core/validator/rule_execition_context/failure.rb
200
+ - lib/smart_core/validator/rule_execition_context/result.rb
201
+ - lib/smart_core/validator/rule_execution_context.rb
202
+ - lib/smart_core/validator/utils.rb
203
+ - lib/smart_core/validator/version.rb
204
+ - smart_validator.gemspec
205
+ homepage: https://github.com/smart-rb/smart_validator
206
+ licenses:
207
+ - MIT
208
+ metadata:
209
+ homepage_uri: https://github.com/smart-rb/smart_validator
210
+ source_code_uri: https://github.com/smart-rb/smart_validator/blob/master
211
+ changelog_uri: https://github.com/smart-rb/smart_validator/blob/master/CHANGELOG.md
212
+ post_install_message:
213
+ rdoc_options: []
214
+ require_paths:
215
+ - lib
216
+ required_ruby_version: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: '3.0'
221
+ required_rubygems_version: !ruby/object:Gem::Requirement
222
+ requirements:
223
+ - - ">="
224
+ - !ruby/object:Gem::Version
225
+ version: '0'
226
+ requirements: []
227
+ rubygems_version: 3.5.3
228
+ signing_key:
229
+ specification_version: 4
230
+ summary: Custom DSL-first validator with a conviniet declarative API.
231
+ test_files: []