aux 0.1.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/MIT-LICENSE +7 -0
- data/aux.gemspec +2 -5
- data/lib/aux/carriers/failure.rb +47 -0
- data/lib/aux/carriers/success.rb +42 -0
- data/lib/aux/carriers.rb +59 -0
- data/lib/aux/pluggable/class_methods.rb +7 -12
- data/lib/aux/pluggable/connector.rb +25 -31
- data/lib/aux/pluggable/dependency.rb +5 -4
- data/lib/aux/pluggable/utilities.rb +9 -8
- data/lib/aux/pluggable.rb +12 -2
- data/lib/aux/registry/entry.rb +27 -0
- data/lib/aux/registry.rb +32 -0
- data/lib/aux/validations/error.rb +6 -6
- data/lib/aux/validations/error_handler.rb +125 -0
- data/lib/aux/validations/errors.rb +28 -54
- data/lib/aux/validations.rb +3 -1
- data/lib/aux/version.rb +1 -1
- data/lib/aux.rb +1 -0
- metadata +29 -51
- data/lib/aux/validations/errors_tree_presenter.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a5a8f2a8523d27a026f50b5e10f3d87c0603a7e2a6710ec0f788b824075c98e0
|
4
|
+
data.tar.gz: 9e5873e8a90853626176568ddd63013a09e5dff9da442a54e6f5955344e2461f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a4a734ffb338fdb30efc987eaadc47378f733818681a126ceddd2d609116ced8cf23474607e8c5e633d65d7f71c232081c2da04b51ef2c840c242d6a1850b26e
|
7
|
+
data.tar.gz: 4a8982b1e6d9e1763676537c8ffb8c900f7209af29c096dab88acced564e5b1d25ef6f17644f2227e40fc872bea30a25f2db5a541a48c3e314db92444026bf68
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright (c) 2022 Evgeny Boyko
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/aux.gemspec
CHANGED
@@ -4,17 +4,14 @@ Gem::Specification.new do |specification|
|
|
4
4
|
specification.name = 'aux'
|
5
5
|
specification.version = Aux::VERSION
|
6
6
|
specification.summary = 'Supplementary tools for more effective development'
|
7
|
-
specification.files = Dir['LICENSE', 'README.md', 'aux.gemspec', 'lib/**/*']
|
7
|
+
specification.files = Dir['MIT-LICENSE', 'README.md', 'aux.gemspec', 'lib/**/*']
|
8
8
|
|
9
9
|
specification.authors = ['Evgeny Boyko']
|
10
10
|
specification.email = ['eboyko@eboyko.ru']
|
11
11
|
specification.homepage = 'https://github.com/eboyko/aux'
|
12
12
|
|
13
|
+
specification.add_dependency 'concurrent-ruby', '~> 1.2', '>= 1.2.3'
|
13
14
|
specification.add_dependency 'activemodel', '>= 6.1', '< 8'
|
14
|
-
specification.add_dependency 'dry-container', '>= 0.9.0', '<= 0.11'
|
15
|
-
|
16
|
-
specification.add_development_dependency 'zeitwerk', '~> 2.5'
|
17
|
-
specification.add_development_dependency 'rubocop', '~> 1.36.0'
|
18
15
|
|
19
16
|
specification.required_ruby_version = '>= 2.7.1'
|
20
17
|
specification.metadata['rubygems_mfa_required'] = 'true'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aux
|
4
|
+
module Carriers
|
5
|
+
# Describes a service failure
|
6
|
+
class Failure
|
7
|
+
# @!attribute [r] code
|
8
|
+
# @return [Symbol]
|
9
|
+
# @!attribute [r] details
|
10
|
+
# @return [Object, nil]
|
11
|
+
# @!attribute [r] payload
|
12
|
+
# @return [Object, nil]
|
13
|
+
attr_reader :code, :details, :payload
|
14
|
+
|
15
|
+
# @param code [Symbol]
|
16
|
+
# @param details [Object, nil]
|
17
|
+
# @param payload [Object, nil]
|
18
|
+
def initialize(code, details = nil, payload = nil)
|
19
|
+
@code = code
|
20
|
+
@details = details
|
21
|
+
@payload = payload
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [FalseClass]
|
25
|
+
def succeeded?
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!method successful?
|
30
|
+
# @return [FalseClass]
|
31
|
+
alias successful? succeeded?
|
32
|
+
|
33
|
+
# @!method success?
|
34
|
+
# @return [FalseClass]
|
35
|
+
alias success? succeeded?
|
36
|
+
|
37
|
+
# @return [TrueClass]
|
38
|
+
def failed?
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
# @!method failure?
|
43
|
+
# @return [TrueClass]
|
44
|
+
alias failure? failed?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aux
|
4
|
+
module Carriers
|
5
|
+
class Success
|
6
|
+
# @!attribute [r] code
|
7
|
+
# @return [Symbol]
|
8
|
+
# @!attribute [r] payload
|
9
|
+
# @return [Object, nil]
|
10
|
+
attr_reader :code, :payload
|
11
|
+
|
12
|
+
# @param code [Symbol]
|
13
|
+
# @param payload [Object, nil]
|
14
|
+
def initialize(code, payload = nil)
|
15
|
+
@code = code
|
16
|
+
@payload = payload
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [TrueClass]
|
20
|
+
def succeeded?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
# @!method successful?
|
25
|
+
# @return [TrueClass]
|
26
|
+
alias successful? succeeded?
|
27
|
+
|
28
|
+
# @!method success?
|
29
|
+
# @return [TrueClass]
|
30
|
+
alias success? succeeded?
|
31
|
+
|
32
|
+
# @return [FalseClass]
|
33
|
+
def failed?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!method failure?
|
38
|
+
# @return [FalseClass]
|
39
|
+
alias failure? failed?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/aux/carriers.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aux/carriers/success'
|
4
|
+
require 'aux/carriers/failure'
|
5
|
+
|
6
|
+
module Aux
|
7
|
+
module Carriers
|
8
|
+
private
|
9
|
+
|
10
|
+
# @overload success
|
11
|
+
# @return [Success]
|
12
|
+
# @overload success(code)
|
13
|
+
# @param code [Symbol]
|
14
|
+
# @return [Success]
|
15
|
+
# @overload success(payload)
|
16
|
+
# @param payload [Object]
|
17
|
+
# @return [Success]
|
18
|
+
# @overload success(code, payload)
|
19
|
+
# @param code [Symbol]
|
20
|
+
# @param payload [Object]
|
21
|
+
# @return [Success]
|
22
|
+
def success(*arguments)
|
23
|
+
case arguments
|
24
|
+
in [Symbol] | [Symbol, Object]
|
25
|
+
Success.new(*arguments)
|
26
|
+
in [Object]
|
27
|
+
Success.new(:ok, *arguments)
|
28
|
+
else
|
29
|
+
Success.new(:ok)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @overload failure
|
34
|
+
# @return [Failure]
|
35
|
+
# @overload failure(code)
|
36
|
+
# @param code [Symbol]
|
37
|
+
# @return [Failure]
|
38
|
+
# @overload failure(details)
|
39
|
+
# @param details [Object]
|
40
|
+
# @return [Failure]
|
41
|
+
# @overload failure(code, details)
|
42
|
+
# @param code [Symbol]
|
43
|
+
# @param details [Object]
|
44
|
+
# @return [Failure]
|
45
|
+
# @overload failure(code, details, payload)
|
46
|
+
# @param code [Symbol]
|
47
|
+
# @param details [Object]
|
48
|
+
# @param payload [Object]
|
49
|
+
# @return [Failure]
|
50
|
+
def failure(*arguments)
|
51
|
+
case arguments
|
52
|
+
in [Symbol] | [Symbol, Object] | [Symbol, Object, Object]
|
53
|
+
Failure.new(*arguments)
|
54
|
+
else
|
55
|
+
Failure.new(:internal_server_error, *arguments)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -20,7 +20,7 @@ module Aux
|
|
20
20
|
|
21
21
|
# Configure dependencies that come from the registry
|
22
22
|
pluggable.dependencies.each do |dependency|
|
23
|
-
instance.instance_variable_set("@#{dependency.pointer}", dependency.target)
|
23
|
+
instance.instance_variable_set(:"@#{dependency.pointer}", dependency.target)
|
24
24
|
|
25
25
|
# The next lines are required to make resolved dependencies available
|
26
26
|
# by its reader methods within customized initialization
|
@@ -28,7 +28,7 @@ module Aux
|
|
28
28
|
# TODO: Discuss whether this is appropriate behaviour
|
29
29
|
next if instance.respond_to?(dependency.pointer, true)
|
30
30
|
|
31
|
-
define_method(dependency.pointer) { instance_variable_get("@#{dependency.pointer}") }
|
31
|
+
define_method(dependency.pointer) { instance_variable_get(:"@#{dependency.pointer}") }
|
32
32
|
next unless dependency.private
|
33
33
|
|
34
34
|
private(dependency.pointer)
|
@@ -37,7 +37,7 @@ module Aux
|
|
37
37
|
# Configure dependencies that come from the customized initialization procedure
|
38
38
|
dependencies_overrides = keyword_arguments.slice(*dependencies_keys)
|
39
39
|
dependencies_overrides.each do |pointer, target|
|
40
|
-
instance.instance_variable_set("@#{pointer}", target)
|
40
|
+
instance.instance_variable_set(:"@#{pointer}", target)
|
41
41
|
end
|
42
42
|
|
43
43
|
# Run the origin's initialization procedure if any other arguments given
|
@@ -52,24 +52,19 @@ module Aux
|
|
52
52
|
|
53
53
|
# @param initialize [TrueClass, FalseClass]
|
54
54
|
# @param memoize [TrueClass, FalseClass]
|
55
|
-
# @param scope [
|
55
|
+
# @param scope [Symbol, String, TrueClass, nil]
|
56
56
|
# @param as [Symbol, String, nil]
|
57
57
|
def register(initialize: false, memoize: false, scope: true, as: nil)
|
58
|
-
@_pluggable.register(
|
59
|
-
initialization_required: initialize,
|
60
|
-
memoization_required: memoize,
|
61
|
-
namespace: scope,
|
62
|
-
code: as
|
63
|
-
)
|
58
|
+
@_pluggable.register(initialize, memoize, scope, as)
|
64
59
|
end
|
65
60
|
|
66
61
|
# @param code [Symbol, String]
|
67
|
-
# @param initialization_block [
|
62
|
+
# @param initialization_block [Proc, nil]
|
68
63
|
# @param scope [TrueClass, Symbol, String, nil]
|
69
64
|
# @param private [TrueClass, FalseClass]
|
70
65
|
# @param as [Symbol, String, nil]
|
71
66
|
def resolve(code, initialization_block = nil, scope: true, private: true, as: nil)
|
72
|
-
@_pluggable.resolve(code,
|
67
|
+
@_pluggable.resolve(code, scope, private, as, initialization_block)
|
73
68
|
end
|
74
69
|
end
|
75
70
|
end
|
@@ -3,32 +3,26 @@
|
|
3
3
|
module Aux
|
4
4
|
module Pluggable
|
5
5
|
# Describes the bridge between a pluggable class and the registry
|
6
|
+
# @!visibility private
|
6
7
|
class Connector
|
7
8
|
# @!attribute [r] dependencies
|
8
9
|
# @return [Array<Dependency>]
|
9
10
|
attr_reader :dependencies
|
10
11
|
|
11
12
|
# @param subject [Class]
|
12
|
-
# @param registry [
|
13
|
+
# @param registry [Aux::Registry]
|
13
14
|
def initialize(subject, registry)
|
14
15
|
@subject = subject
|
15
16
|
@registry = registry
|
16
17
|
@dependencies = []
|
17
|
-
|
18
|
-
configure
|
19
18
|
end
|
20
19
|
|
21
|
-
# @param initialization_required [
|
22
|
-
# @param memoization_required [
|
23
|
-
# @param namespace [Symbol] the namespace to register the subject
|
24
|
-
# @param code [Symbol] an alternate name
|
25
|
-
def register(initialization_required
|
26
|
-
configure(
|
27
|
-
initialization_required: initialization_required,
|
28
|
-
memoization_required: memoization_required,
|
29
|
-
namespace: namespace,
|
30
|
-
code: code
|
31
|
-
)
|
20
|
+
# @param initialization_required [TrueClass, FalseClass] whether the subject requires initialization before use
|
21
|
+
# @param memoization_required [TrueClass, FalseClass] whether the subject should be memoized
|
22
|
+
# @param namespace [Symbol, String, TrueClass, nil] the namespace to register the subject
|
23
|
+
# @param code [Symbol, String, nil] an alternate name
|
24
|
+
def register(initialization_required, memoization_required, namespace, code)
|
25
|
+
configure(initialization_required, memoization_required, namespace, code)
|
32
26
|
|
33
27
|
# Register the subject in the registry using the provided options
|
34
28
|
@registry.register(@cipher, memoize: @memoization_required) do
|
@@ -37,41 +31,41 @@ module Aux
|
|
37
31
|
end
|
38
32
|
|
39
33
|
# @param code [Symbol, String] the name of the dependency
|
40
|
-
# @param
|
41
|
-
# @param namespace [TrueClass, Symbol, String, nil] whether to resolve the dependency in the same namespace
|
34
|
+
# @param namespace [Symbol, String, TrueClass, nil] whether to resolve the dependency in the same namespace
|
42
35
|
# @param private [TrueClass, FalseClass] whether to make the dependency private
|
43
|
-
# @param as [Symbol] an internal alias name for the dependency
|
44
|
-
#
|
45
|
-
|
46
|
-
|
47
|
-
|
36
|
+
# @param as [Symbol, String, nil] an internal alias name for the dependency
|
37
|
+
# @param initialization_block [Proc, nil] an optional block used to initialize the dependency
|
38
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
39
|
+
def resolve(code, namespace, private, as, initialization_block = nil)
|
40
|
+
cipher = Utilities.dependency_cipher(@subject.name, namespace, code)
|
41
|
+
load_class(cipher)
|
48
42
|
|
49
|
-
dependency = Dependency.new(@registry.resolve(cipher),
|
43
|
+
dependency = Dependency.new(@registry.resolve(cipher), as || code, private, initialization_block)
|
50
44
|
@dependencies.push(dependency)
|
51
45
|
|
52
46
|
if @initialization_required
|
53
47
|
@subject.class_eval do
|
54
|
-
define_method(dependency.pointer) { instance_variable_get("@#{dependency.pointer}") }
|
48
|
+
define_method(dependency.pointer) { instance_variable_get(:"@#{dependency.pointer}") }
|
55
49
|
private(dependency.pointer) if dependency.private
|
56
50
|
end
|
57
51
|
else
|
58
|
-
@subject.instance_variable_set("@#{dependency.pointer}", dependency.target)
|
52
|
+
@subject.instance_variable_set(:"@#{dependency.pointer}", dependency.target)
|
59
53
|
@subject.singleton_class.class_eval do
|
60
|
-
define_method(dependency.pointer) { instance_variable_get("@#{dependency.pointer}") }
|
54
|
+
define_method(dependency.pointer) { instance_variable_get(:"@#{dependency.pointer}") }
|
61
55
|
private(dependency.pointer) if dependency.private
|
62
56
|
end
|
63
57
|
end
|
64
58
|
end
|
65
|
-
# rubocop:enable
|
59
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
66
60
|
|
67
61
|
private
|
68
62
|
|
69
|
-
# @param initialization_required [TrueClass, FalseClass
|
70
|
-
# @param memoization_required [TrueClass, FalseClass
|
71
|
-
# @param namespace [Symbol, String, nil]
|
63
|
+
# @param initialization_required [TrueClass, FalseClass]
|
64
|
+
# @param memoization_required [TrueClass, FalseClass]
|
65
|
+
# @param namespace [TrueClass, Symbol, String, nil]
|
72
66
|
# @param code [Symbol, String, nil]
|
73
|
-
def configure(initialization_required
|
74
|
-
@cipher = Utilities.dependency_cipher(@subject.name,
|
67
|
+
def configure(initialization_required, memoization_required, namespace, code)
|
68
|
+
@cipher = Utilities.dependency_cipher(@subject.name, namespace, code)
|
75
69
|
@initialization_required = initialization_required
|
76
70
|
@memoization_required = memoization_required
|
77
71
|
@namespace = namespace
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Aux
|
4
4
|
module Pluggable
|
5
5
|
# Describes the dependency and its preferences
|
6
|
+
# @!visibility private
|
6
7
|
class Dependency
|
7
8
|
# @!attribute [r] target
|
8
9
|
# @return [Object]
|
@@ -10,14 +11,14 @@ module Aux
|
|
10
11
|
# @return [Symbol, String]
|
11
12
|
# @!attribute [r] private
|
12
13
|
# @return [Boolean]
|
13
|
-
attr_reader :
|
14
|
+
attr_reader :target, :pointer, :private
|
14
15
|
|
15
16
|
# @param target [Object]
|
16
|
-
# @param initialization_block [Proc, nil]
|
17
17
|
# @param pointer [Symbol, String]
|
18
18
|
# @param private [Boolean]
|
19
|
-
|
20
|
-
|
19
|
+
# @param initialization_block [Proc, nil]
|
20
|
+
def initialize(target, pointer, private, initialization_block = nil)
|
21
|
+
@target = initialization_block&.call(target) || target
|
21
22
|
@pointer = pointer
|
22
23
|
@private = private
|
23
24
|
end
|
@@ -3,28 +3,29 @@
|
|
3
3
|
module Aux
|
4
4
|
module Pluggable
|
5
5
|
# Random methods for internal usage
|
6
|
+
# @!visibility private
|
6
7
|
module Utilities
|
7
8
|
# First, we need to determine the appropriate namespace (also called the scope) in which to resolve something.
|
8
9
|
# By default, we assume that the developers want to resolve a dependency from the same namespace as the
|
9
10
|
# referencing class. Another approach is to allow developers to set the correct scope themselves.
|
10
11
|
#
|
11
|
-
# @param subject [
|
12
|
-
# @param scope [
|
13
|
-
# @param code [
|
12
|
+
# @param subject [String]
|
13
|
+
# @param scope [Symbol, String, TrueClass, nil]
|
14
|
+
# @param code [Symbol, String, nil]
|
14
15
|
# @return [String]
|
15
|
-
def self.dependency_cipher(subject, scope
|
16
|
+
def self.dependency_cipher(subject, scope, code)
|
16
17
|
native_cipher = dependency_native_cipher(subject)
|
17
18
|
native_cipher_partitions = native_cipher.rpartition('.')
|
18
|
-
scope =
|
19
|
-
code =
|
19
|
+
scope = native_cipher_partitions.first if scope == true
|
20
|
+
code = native_cipher_partitions.last if code.nil?
|
20
21
|
|
21
22
|
[scope, code].reject { |part| part.nil? || part.empty? }.join('.')
|
22
23
|
end
|
23
24
|
|
24
|
-
# @param subject [
|
25
|
+
# @param subject [String]
|
25
26
|
# @return [String]
|
26
27
|
def self.dependency_native_cipher(subject)
|
27
|
-
subject.dup.gsub(
|
28
|
+
subject.dup.gsub('::', '.').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase
|
28
29
|
end
|
29
30
|
end
|
30
31
|
end
|
data/lib/aux/pluggable.rb
CHANGED
@@ -4,11 +4,14 @@ require 'aux/pluggable/class_methods'
|
|
4
4
|
require 'aux/pluggable/connector'
|
5
5
|
require 'aux/pluggable/dependency'
|
6
6
|
require 'aux/pluggable/utilities'
|
7
|
+
require 'aux/registry'
|
7
8
|
|
8
9
|
module Aux
|
9
10
|
# Describes interface that makes any class able to register itself as well as resolve dependencies
|
10
11
|
# rubocop:disable Style/ClassVars
|
11
12
|
module Pluggable
|
13
|
+
@@registry = Aux::Registry.new
|
14
|
+
|
12
15
|
# Extends the including class with ClassMethods and initializes a new Connector instance
|
13
16
|
# @param base [Class] the class that includes this module
|
14
17
|
def self.included(base)
|
@@ -19,12 +22,19 @@ module Aux
|
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
22
|
-
# @
|
25
|
+
# @yield configure the module
|
26
|
+
# @yieldparam pluggable [Module<Aux::Pluggable>]
|
27
|
+
# @yieldparam registry [Aux::Registry]
|
28
|
+
def self.configure
|
29
|
+
yield(self, @@registry) if block_given?
|
30
|
+
end
|
31
|
+
|
32
|
+
# @param registry [Aux::Registry]
|
23
33
|
def self.registry=(registry)
|
24
34
|
@@registry = registry
|
25
35
|
end
|
26
36
|
|
27
|
-
# @return [
|
37
|
+
# @return [Aux::Registry]
|
28
38
|
def self.registry
|
29
39
|
@@registry
|
30
40
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aux
|
4
|
+
class Registry
|
5
|
+
# Describes a registered dependency
|
6
|
+
# @!visibility private
|
7
|
+
class Entry
|
8
|
+
# @param constructor [Proc]
|
9
|
+
# @param memoization_required [TrueClass, FalseClass]
|
10
|
+
def initialize(constructor, memoization_required)
|
11
|
+
@constructor = constructor
|
12
|
+
@memoization_required = memoization_required
|
13
|
+
|
14
|
+
@mutex = Thread::Mutex.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Object]
|
18
|
+
def call
|
19
|
+
return @constructor.call unless @memoization_required
|
20
|
+
|
21
|
+
@call ||= @mutex.synchronize do
|
22
|
+
@constructor.call
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/aux/registry.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'aux/registry/entry'
|
4
|
+
require 'concurrent/map'
|
5
|
+
|
6
|
+
module Aux
|
7
|
+
# Registry for dependency injection
|
8
|
+
class Registry
|
9
|
+
def initialize
|
10
|
+
@prototypes = ::Concurrent::Map.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param key [Symbol, String]
|
14
|
+
# @param memoize [TrueClass, FalseClass]
|
15
|
+
# @param constructor [Proc]
|
16
|
+
def register(key, memoize: false, &constructor)
|
17
|
+
@prototypes.put(key.to_s, Entry.new(constructor, memoize))
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param key [Symbol, String]
|
21
|
+
# @return [Object]
|
22
|
+
def resolve(key)
|
23
|
+
@prototypes.fetch(key.to_s).call
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param key [Symbol, String]
|
27
|
+
# @return [TrueClass, FalseClass]
|
28
|
+
def key?(key)
|
29
|
+
@prototypes.key?(key.to_s)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -6,22 +6,22 @@ module Aux
|
|
6
6
|
class Error
|
7
7
|
# @!attribute [r] attribute
|
8
8
|
# @return [Symbol]
|
9
|
-
# @!attribute [r] type
|
10
|
-
# @return [Symbol]
|
11
9
|
# @!attribute [r] scope
|
12
10
|
# @return [String]
|
11
|
+
# @!attribute [r] type
|
12
|
+
# @return [Symbol]
|
13
13
|
# @!attribute [r] details
|
14
14
|
# @return [Hash]
|
15
|
-
attr_reader :attribute, :
|
15
|
+
attr_reader :attribute, :scope, :type, :details
|
16
16
|
|
17
17
|
# @param attribute [Symbol]
|
18
|
-
# @param type [Symbol]
|
19
18
|
# @param scope [String]
|
19
|
+
# @param type [Symbol]
|
20
20
|
# @param details [Hash]
|
21
|
-
def initialize(attribute,
|
21
|
+
def initialize(attribute, scope, type, **details)
|
22
22
|
@attribute = attribute
|
23
|
-
@type = type
|
24
23
|
@scope = scope
|
24
|
+
@type = type
|
25
25
|
@details = details
|
26
26
|
end
|
27
27
|
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aux
|
4
|
+
module Validations
|
5
|
+
# Describes methods that represent validation errors in simple structures pretty enough for the API response.
|
6
|
+
module ErrorHandler
|
7
|
+
private
|
8
|
+
|
9
|
+
DEFAULT_ERROR_SCOPE_PREFIX = 'errors'
|
10
|
+
GENERIC_ERROR_SCOPE = 'errors.generic'
|
11
|
+
|
12
|
+
# @overload format_errors(error_batch, scope)
|
13
|
+
# @param error_batch [Array]
|
14
|
+
# @param scope [Symbol, String, nil]
|
15
|
+
# @raise [ArgumentError]
|
16
|
+
# @return [Array<Hash>]
|
17
|
+
# @overload format_errors(error_collection, scope)
|
18
|
+
# @param error_collection [Errors]
|
19
|
+
# @param scope [Symbol, String, nil]
|
20
|
+
# @raise [ArgumentError]
|
21
|
+
# @return [Array<Hash>]
|
22
|
+
# @overload format_errors(error, scope)
|
23
|
+
# @param scope [Symbol, String, nil]
|
24
|
+
# @param error [Error]
|
25
|
+
# @raise [ArgumentError]
|
26
|
+
# @return [Hash]
|
27
|
+
def format_errors(payload, scope = nil)
|
28
|
+
case payload
|
29
|
+
when Array
|
30
|
+
format_error_batch(payload, scope)
|
31
|
+
when Errors
|
32
|
+
format_error_collection(payload, scope)
|
33
|
+
when Error
|
34
|
+
format_error(payload, scope)
|
35
|
+
else
|
36
|
+
raise ArgumentError
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param batch [Array<Error, Errors>]
|
41
|
+
# @param scope [Symbol, String, nil]
|
42
|
+
# @return [Array<Hash>]
|
43
|
+
def format_error_batch(batch, scope = nil)
|
44
|
+
batch.flat_map { |element| format_errors(element, scope) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param collection [Errors]
|
48
|
+
# @param scope [Symbol, String, nil]
|
49
|
+
def format_error_collection(collection, scope = nil)
|
50
|
+
return format_primary_error_collection(collection, scope || collection.scope) unless collection.attribute
|
51
|
+
|
52
|
+
format_secondary_error_collection(collection, "#{scope}.#{collection.attribute}")
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param collection [Errors]
|
56
|
+
# @param scope [Symbol, String, nil]
|
57
|
+
# @return [Array<Hash>]
|
58
|
+
def format_primary_error_collection(collection, scope = nil)
|
59
|
+
collection.group_by_attribute.map do |attribute, entities|
|
60
|
+
{
|
61
|
+
kind: entities.all?(Error) ? 'collection' : 'batch',
|
62
|
+
attribute: attribute,
|
63
|
+
errors: format_errors(entities, scope)
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param collection [Errors]
|
69
|
+
# @param scope [Symbol, String, nil]
|
70
|
+
# @return [Array<Hash>]
|
71
|
+
def format_secondary_error_collection(collection, scope = nil)
|
72
|
+
collection.group_by_attribute.flat_map do |_attribute, items|
|
73
|
+
format_errors(items.one? ? items.first : items, scope)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# @param error [Error]
|
78
|
+
# @param scope [Symbol, String, nil]
|
79
|
+
# @return [Hash]
|
80
|
+
def format_error(error, scope = nil)
|
81
|
+
{
|
82
|
+
type: error.type,
|
83
|
+
details: error.details,
|
84
|
+
message: format_message(error, scope)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
# @param error [Error]
|
89
|
+
# @param scope [Symbol, String, nil]
|
90
|
+
# @return [String]
|
91
|
+
def format_message(error, scope = nil)
|
92
|
+
scope ? format_secondary_message(error, scope) : format_primary_message(error)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @param error [Error]
|
96
|
+
# @return [String]
|
97
|
+
def format_primary_message(error)
|
98
|
+
I18n.translate(
|
99
|
+
error.type,
|
100
|
+
scope: "#{self.class::DEFAULT_ERROR_SCOPE_PREFIX}.#{error.scope}.#{error.attribute}",
|
101
|
+
default: proc { format_fallback_message(error) },
|
102
|
+
**error.details
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
# @param error [Error]
|
107
|
+
# @param scope [Symbol, String, nil]
|
108
|
+
# @return [String]
|
109
|
+
def format_secondary_message(error, scope)
|
110
|
+
I18n.translate(
|
111
|
+
error.type,
|
112
|
+
scope: "#{self.class::DEFAULT_ERROR_SCOPE_PREFIX}.#{scope}.#{error.attribute}",
|
113
|
+
default: proc { format_primary_message(error) },
|
114
|
+
**error.details
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @param error [Error]
|
119
|
+
# @return [String]
|
120
|
+
def format_fallback_message(error)
|
121
|
+
I18n.translate(error.type, scope: self.class::GENERIC_ERROR_SCOPE, default: error.type)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -1,11 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'aux/validations/error'
|
4
|
-
require 'aux/validations/errors_tree_presenter'
|
5
|
-
|
6
3
|
module Aux
|
7
4
|
module Validations
|
8
|
-
# Describes a
|
5
|
+
# Describes a collection of errors that can be initialized with an attribute name or an object to be validated
|
9
6
|
class Errors
|
10
7
|
# @!attribute [r] scope
|
11
8
|
# @return [String, nil]
|
@@ -14,16 +11,13 @@ module Aux
|
|
14
11
|
attr_reader :scope, :attribute
|
15
12
|
|
16
13
|
# @overload initialize(subject)
|
17
|
-
# @param
|
18
|
-
# @return [self]
|
14
|
+
# @param subject [Object]
|
19
15
|
# @overload initialize(attribute, errors)
|
20
|
-
# Use this approach to handle nested errors
|
21
16
|
# @param attribute [Symbol]
|
22
|
-
# @param errors [
|
23
|
-
|
24
|
-
|
25
|
-
@
|
26
|
-
@attribute = base.is_a?(Symbol) ? base : nil
|
17
|
+
# @param errors [Errors]
|
18
|
+
def initialize(subject, errors = [])
|
19
|
+
@scope = subject.is_a?(Symbol) ? nil : subject.class.name.underscore.tr('/', '.')
|
20
|
+
@attribute = subject.is_a?(Symbol) ? subject : nil
|
27
21
|
@errors = errors
|
28
22
|
end
|
29
23
|
|
@@ -34,35 +28,25 @@ module Aux
|
|
34
28
|
# @raise [StandardError]
|
35
29
|
# @overload add(attribute, errors)
|
36
30
|
# @param attribute [Symbol]
|
37
|
-
# @param errors [Array<
|
31
|
+
# @param errors [Array<Errors, Error>]
|
38
32
|
# @raise [StandardError]
|
39
33
|
# @overload add(attribute, errors)
|
40
34
|
# @param attribute [Symbol]
|
41
|
-
# @param errors [
|
35
|
+
# @param errors [Errors]
|
42
36
|
# @raise [StandardError]
|
43
|
-
def add(attribute,
|
44
|
-
case
|
37
|
+
def add(attribute, subject, **details)
|
38
|
+
case subject
|
45
39
|
when Symbol
|
46
|
-
add_error(attribute,
|
40
|
+
add_error(attribute, subject, **details)
|
47
41
|
when Array
|
48
|
-
add_nested_error(attribute,
|
49
|
-
when
|
50
|
-
add_nested_error(attribute, [
|
42
|
+
add_nested_error(attribute, subject)
|
43
|
+
when Errors
|
44
|
+
add_nested_error(attribute, [subject])
|
51
45
|
else
|
52
|
-
raise
|
46
|
+
raise(ArgumentError, "Unsupported argument given (#{subject.class})")
|
53
47
|
end
|
54
48
|
end
|
55
49
|
|
56
|
-
# @return [Hash]
|
57
|
-
def tree
|
58
|
-
tree_presenter.render(self)
|
59
|
-
end
|
60
|
-
|
61
|
-
# @return [Hash]
|
62
|
-
def group_by_attribute
|
63
|
-
errors.group_by(&:attribute)
|
64
|
-
end
|
65
|
-
|
66
50
|
def clear
|
67
51
|
self.errors = []
|
68
52
|
end
|
@@ -72,25 +56,20 @@ module Aux
|
|
72
56
|
errors.empty?
|
73
57
|
end
|
74
58
|
|
59
|
+
# @param block [Proc]
|
75
60
|
def map(&block)
|
76
61
|
errors.map(&block)
|
77
62
|
end
|
78
63
|
|
79
|
-
# @return [Array
|
80
|
-
def
|
81
|
-
errors.
|
82
|
-
if error.is_a?(Array)
|
83
|
-
error.map { |e| e.respond_to?(:all) ? e.all : e }
|
84
|
-
else
|
85
|
-
error.respond_to?(:all) ? error.all : error
|
86
|
-
end
|
87
|
-
end.flatten
|
64
|
+
# @return [Hash<Symbol, Array>]
|
65
|
+
def group_by_attribute
|
66
|
+
errors.group_by(&:attribute)
|
88
67
|
end
|
89
68
|
|
90
69
|
private
|
91
70
|
|
92
71
|
# @!attribute [rw] errors
|
93
|
-
# @return [Array<
|
72
|
+
# @return [Array<Error, Errors>]
|
94
73
|
attr_accessor :errors
|
95
74
|
|
96
75
|
# @param attribute [Symbol]
|
@@ -101,7 +80,7 @@ module Aux
|
|
101
80
|
end
|
102
81
|
|
103
82
|
# @param attribute [Symbol]
|
104
|
-
# @param nested_errors [
|
83
|
+
# @param nested_errors [Errors]
|
105
84
|
def add_nested_error(attribute, nested_errors)
|
106
85
|
errors.push(build_nested_error(attribute, nested_errors))
|
107
86
|
end
|
@@ -109,26 +88,21 @@ module Aux
|
|
109
88
|
# @param attribute [Symbol]
|
110
89
|
# @param type [Symbol]
|
111
90
|
# @param details [Hash]
|
112
|
-
# @return [
|
91
|
+
# @return [Error]
|
113
92
|
def build_error(attribute, type, **details)
|
114
|
-
|
93
|
+
factory.new(attribute, scope, type, **details)
|
115
94
|
end
|
116
95
|
|
117
96
|
# @param attribute [Symbol] 3
|
118
|
-
# @param errors [
|
119
|
-
# @return [
|
97
|
+
# @param errors [Errors]
|
98
|
+
# @return [Errors]
|
120
99
|
def build_nested_error(attribute, errors)
|
121
100
|
self.class.new(attribute, errors)
|
122
101
|
end
|
123
102
|
|
124
|
-
# @return [Class<
|
125
|
-
def
|
126
|
-
|
127
|
-
end
|
128
|
-
|
129
|
-
# @return [Class<Aux::Validations::Error>]
|
130
|
-
def errors_factory
|
131
|
-
Aux::Validations::Error
|
103
|
+
# @return [Class<Error>]
|
104
|
+
def factory
|
105
|
+
Error
|
132
106
|
end
|
133
107
|
end
|
134
108
|
end
|
data/lib/aux/validations.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'active_model'
|
3
4
|
require 'active_support'
|
4
|
-
require '
|
5
|
+
require 'aux/validations/error'
|
5
6
|
require 'aux/validations/errors'
|
7
|
+
require 'aux/validations/error_handler'
|
6
8
|
|
7
9
|
module Aux
|
8
10
|
# Describes enhancement of {::ActiveModel::Validations}
|
data/lib/aux/version.rb
CHANGED
data/lib/aux.rb
CHANGED
metadata
CHANGED
@@ -1,109 +1,87 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aux
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evgeny Boyko
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-08-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: concurrent-ruby
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
20
|
-
- - "
|
19
|
+
version: '1.2'
|
20
|
+
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
22
|
+
version: 1.2.3
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- - "
|
27
|
+
- - "~>"
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
30
|
-
- - "
|
29
|
+
version: '1.2'
|
30
|
+
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: 1.2.3
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: activemodel
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - ">="
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
40
|
-
- - "
|
39
|
+
version: '6.1'
|
40
|
+
- - "<"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '
|
42
|
+
version: '8'
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
47
|
- - ">="
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
50
|
-
- - "
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: '0.11'
|
53
|
-
- !ruby/object:Gem::Dependency
|
54
|
-
name: zeitwerk
|
55
|
-
requirement: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - "~>"
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '2.5'
|
60
|
-
type: :development
|
61
|
-
prerelease: false
|
62
|
-
version_requirements: !ruby/object:Gem::Requirement
|
63
|
-
requirements:
|
64
|
-
- - "~>"
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: '2.5'
|
67
|
-
- !ruby/object:Gem::Dependency
|
68
|
-
name: rubocop
|
69
|
-
requirement: !ruby/object:Gem::Requirement
|
70
|
-
requirements:
|
71
|
-
- - "~>"
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
version: 1.36.0
|
74
|
-
type: :development
|
75
|
-
prerelease: false
|
76
|
-
version_requirements: !ruby/object:Gem::Requirement
|
77
|
-
requirements:
|
78
|
-
- - "~>"
|
49
|
+
version: '6.1'
|
50
|
+
- - "<"
|
79
51
|
- !ruby/object:Gem::Version
|
80
|
-
version:
|
81
|
-
description:
|
52
|
+
version: '8'
|
53
|
+
description:
|
82
54
|
email:
|
83
55
|
- eboyko@eboyko.ru
|
84
56
|
executables: []
|
85
57
|
extensions: []
|
86
58
|
extra_rdoc_files: []
|
87
59
|
files:
|
60
|
+
- MIT-LICENSE
|
88
61
|
- README.md
|
89
62
|
- aux.gemspec
|
90
63
|
- lib/aux.rb
|
64
|
+
- lib/aux/carriers.rb
|
65
|
+
- lib/aux/carriers/failure.rb
|
66
|
+
- lib/aux/carriers/success.rb
|
91
67
|
- lib/aux/pluggable.rb
|
92
68
|
- lib/aux/pluggable/class_methods.rb
|
93
69
|
- lib/aux/pluggable/connector.rb
|
94
70
|
- lib/aux/pluggable/dependency.rb
|
95
71
|
- lib/aux/pluggable/utilities.rb
|
72
|
+
- lib/aux/registry.rb
|
73
|
+
- lib/aux/registry/entry.rb
|
96
74
|
- lib/aux/validations.rb
|
97
75
|
- lib/aux/validations/error.rb
|
76
|
+
- lib/aux/validations/error_handler.rb
|
98
77
|
- lib/aux/validations/errors.rb
|
99
|
-
- lib/aux/validations/errors_tree_presenter.rb
|
100
78
|
- lib/aux/version.rb
|
101
79
|
homepage: https://github.com/eboyko/aux
|
102
80
|
licenses:
|
103
81
|
- MIT
|
104
82
|
metadata:
|
105
83
|
rubygems_mfa_required: 'true'
|
106
|
-
post_install_message:
|
84
|
+
post_install_message:
|
107
85
|
rdoc_options: []
|
108
86
|
require_paths:
|
109
87
|
- lib
|
@@ -119,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
97
|
version: '0'
|
120
98
|
requirements: []
|
121
99
|
rubygems_version: 3.1.2
|
122
|
-
signing_key:
|
100
|
+
signing_key:
|
123
101
|
specification_version: 4
|
124
102
|
summary: Supplementary tools for more effective development
|
125
103
|
test_files: []
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Aux
|
4
|
-
module Validations
|
5
|
-
# Describes tree structure presentation scenario of errors
|
6
|
-
class ErrorsTreePresenter
|
7
|
-
class << self
|
8
|
-
# @return [Array, Hash, Aux::Validations::Error]
|
9
|
-
def render(payload)
|
10
|
-
case payload
|
11
|
-
when Array
|
12
|
-
process_array(payload)
|
13
|
-
when Aux::Validations::Errors
|
14
|
-
render_collection(payload)
|
15
|
-
when Aux::Validations::Error
|
16
|
-
payload
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
# @param errors [Array]
|
23
|
-
# @return [Array]
|
24
|
-
def process_array(errors)
|
25
|
-
errors.map { |item| render(item) }.flatten
|
26
|
-
end
|
27
|
-
|
28
|
-
# @param errors [Aux::Validations::Errors]
|
29
|
-
# @return [Array, Hash]
|
30
|
-
def render_collection(errors)
|
31
|
-
errors.attribute ? process_attribute_errors(errors) : process_errors(errors)
|
32
|
-
end
|
33
|
-
|
34
|
-
# @param errors [Aux::Validations::Errors]
|
35
|
-
# @return [Hash]
|
36
|
-
def process_errors(errors)
|
37
|
-
errors.group_by_attribute.transform_values do |items|
|
38
|
-
render(items.one? ? items.first : items)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# @param errors [Aux::Validations::Errors]
|
43
|
-
# @return [Array]
|
44
|
-
def process_attribute_errors(errors)
|
45
|
-
errors.group_by_attribute.map { |_attribute, items| render(items) }.flatten
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|