servactory 1.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 (52) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +116 -0
  3. data/Rakefile +12 -0
  4. data/lib/servactory/base.rb +11 -0
  5. data/lib/servactory/configuration.rb +18 -0
  6. data/lib/servactory/context/configuration.rb +23 -0
  7. data/lib/servactory/context/dsl.rb +58 -0
  8. data/lib/servactory/context/store.rb +51 -0
  9. data/lib/servactory/errors/base.rb +7 -0
  10. data/lib/servactory/errors/failure.rb +7 -0
  11. data/lib/servactory/errors/input_argument_error.rb +7 -0
  12. data/lib/servactory/errors/internal_argument_error.rb +7 -0
  13. data/lib/servactory/errors/output_argument_error.rb +7 -0
  14. data/lib/servactory/input_arguments/checks/base.rb +23 -0
  15. data/lib/servactory/input_arguments/checks/inclusion.rb +45 -0
  16. data/lib/servactory/input_arguments/checks/must.rb +75 -0
  17. data/lib/servactory/input_arguments/checks/required.rb +54 -0
  18. data/lib/servactory/input_arguments/checks/type.rb +84 -0
  19. data/lib/servactory/input_arguments/collection.rb +19 -0
  20. data/lib/servactory/input_arguments/dsl.rb +27 -0
  21. data/lib/servactory/input_arguments/input_argument.rb +79 -0
  22. data/lib/servactory/input_arguments/tools/check.rb +74 -0
  23. data/lib/servactory/input_arguments/tools/find_unnecessary.rb +32 -0
  24. data/lib/servactory/input_arguments/tools/prepare.rb +89 -0
  25. data/lib/servactory/input_arguments/tools/rules.rb +38 -0
  26. data/lib/servactory/input_arguments/workbench.rb +40 -0
  27. data/lib/servactory/inputs.rb +7 -0
  28. data/lib/servactory/internal_arguments/checks/base.rb +17 -0
  29. data/lib/servactory/internal_arguments/checks/type.rb +55 -0
  30. data/lib/servactory/internal_arguments/collection.rb +19 -0
  31. data/lib/servactory/internal_arguments/dsl.rb +27 -0
  32. data/lib/servactory/internal_arguments/internal_argument.rb +33 -0
  33. data/lib/servactory/internal_arguments/tools/prepare.rb +58 -0
  34. data/lib/servactory/internal_arguments/workbench.rb +27 -0
  35. data/lib/servactory/output_arguments/checks/base.rb +17 -0
  36. data/lib/servactory/output_arguments/checks/type.rb +55 -0
  37. data/lib/servactory/output_arguments/collection.rb +19 -0
  38. data/lib/servactory/output_arguments/dsl.rb +27 -0
  39. data/lib/servactory/output_arguments/output_argument.rb +40 -0
  40. data/lib/servactory/output_arguments/tools/conflicts.rb +34 -0
  41. data/lib/servactory/output_arguments/tools/prepare.rb +58 -0
  42. data/lib/servactory/output_arguments/workbench.rb +31 -0
  43. data/lib/servactory/result.rb +21 -0
  44. data/lib/servactory/stage/dsl.rb +29 -0
  45. data/lib/servactory/stage/factory.rb +15 -0
  46. data/lib/servactory/stage/handyman.rb +35 -0
  47. data/lib/servactory/stage/method.rb +21 -0
  48. data/lib/servactory/stage/methods.rb +15 -0
  49. data/lib/servactory/utils.rb +11 -0
  50. data/lib/servactory/version.rb +11 -0
  51. data/lib/servactory.rb +34 -0
  52. metadata +237 -0
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InputArguments
5
+ class InputArgument
6
+ attr_reader :name,
7
+ :types,
8
+ :inclusion,
9
+ :must,
10
+ :array,
11
+ :required,
12
+ :internal,
13
+ :default
14
+
15
+ def initialize(name, type:, **options)
16
+ @name = name
17
+ @types = Array(type)
18
+
19
+ @inclusion = options.fetch(:inclusion, nil)
20
+ @must = options.fetch(:must, nil)
21
+ @array = options.fetch(:array, false)
22
+ @required = options.fetch(:required, true)
23
+ @internal = options.fetch(:internal, false)
24
+ @default = options.fetch(:default, nil)
25
+ end
26
+
27
+ def options_for_checks
28
+ {
29
+ types:,
30
+ inclusion:,
31
+ must:,
32
+ required:,
33
+ # internal:,
34
+ default:
35
+ }
36
+ end
37
+
38
+ def conflict_code
39
+ return :required_vs_default if required? && default_value_present?
40
+ return :array_vs_array if array? && types.include?(Array)
41
+ return :array_vs_inclusion if array? && inclusion_present?
42
+
43
+ nil
44
+ end
45
+
46
+ def inclusion_present?
47
+ inclusion.is_a?(Array) && inclusion.present?
48
+ end
49
+
50
+ def must_present?
51
+ must.present?
52
+ end
53
+
54
+ def array?
55
+ Servactory::Utils.boolean?(array)
56
+ end
57
+
58
+ def required?
59
+ Servactory::Utils.boolean?(required)
60
+ end
61
+
62
+ def optional?
63
+ !required?
64
+ end
65
+
66
+ def internal?
67
+ Servactory::Utils.boolean?(internal)
68
+ end
69
+
70
+ def default_value_present?
71
+ !default.nil?
72
+ end
73
+
74
+ def with_conflicts?
75
+ conflict_code.present?
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InputArguments
5
+ module Tools
6
+ class Check
7
+ def self.check!(...)
8
+ new(...).check!
9
+ end
10
+
11
+ def initialize(context, incoming_arguments, collection_of_input_arguments)
12
+ @context = context
13
+ @incoming_arguments = incoming_arguments
14
+ @collection_of_input_arguments = collection_of_input_arguments
15
+
16
+ @errors = []
17
+ end
18
+
19
+ def check!
20
+ @collection_of_input_arguments.each do |input|
21
+ process_input(input)
22
+ end
23
+
24
+ raise_errors
25
+ end
26
+
27
+ private
28
+
29
+ def process_input(input)
30
+ input.options_for_checks.each do |check_key, check_options|
31
+ process_option(check_key, check_options, input:)
32
+ end
33
+ end
34
+
35
+ def process_option(check_key, check_options, input:)
36
+ check_classes.each do |check_class|
37
+ errors_from_checks = process_check_class(check_class, input:, check_key:, check_options:)
38
+
39
+ @errors.push(*errors_from_checks)
40
+ end
41
+ end
42
+
43
+ def process_check_class(check_class, input:, check_key:, check_options:)
44
+ check_class.check(
45
+ context: @context,
46
+ input:,
47
+ value: @incoming_arguments.fetch(input.name, nil),
48
+ check_key:,
49
+ check_options:
50
+ )
51
+ end
52
+
53
+ ########################################################################
54
+
55
+ def check_classes
56
+ [
57
+ Servactory::InputArguments::Checks::Required,
58
+ Servactory::InputArguments::Checks::Type,
59
+ Servactory::InputArguments::Checks::Inclusion,
60
+ Servactory::InputArguments::Checks::Must
61
+ ]
62
+ end
63
+
64
+ ########################################################################
65
+
66
+ def raise_errors
67
+ return if @errors.empty?
68
+
69
+ raise Servactory.configuration.input_argument_error_class, @errors.first
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InputArguments
5
+ module Tools
6
+ class FindUnnecessary
7
+ def self.check!(...)
8
+ new(...).check!
9
+ end
10
+
11
+ def initialize(context, incoming_arguments, collection_of_input_arguments)
12
+ @context = context
13
+ @incoming_arguments = incoming_arguments
14
+ @collection_of_input_arguments = collection_of_input_arguments
15
+ end
16
+
17
+ def check!
18
+ return if unnecessary_attributes.empty?
19
+
20
+ raise Servactory.configuration.input_argument_error_class,
21
+ "[#{@context.class.name}] Unexpected attributes: `#{unnecessary_attributes.join(', ')}`"
22
+ end
23
+
24
+ private
25
+
26
+ def unnecessary_attributes
27
+ @unnecessary_attributes ||= @incoming_arguments.keys - @collection_of_input_arguments.names
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InputArguments
5
+ module Tools
6
+ class Prepare
7
+ def self.prepare(...)
8
+ new(...).prepare
9
+ end
10
+
11
+ def initialize(context, incoming_arguments, collection_of_input_arguments)
12
+ @context = context
13
+ @incoming_arguments = incoming_arguments
14
+ @collection_of_input_arguments = collection_of_input_arguments
15
+ end
16
+
17
+ def prepare
18
+ @inputs_variables = {}
19
+ @internal_variables = {}
20
+
21
+ @collection_of_input_arguments.each do |input|
22
+ process_input(input)
23
+ end
24
+
25
+ create_instance_variables
26
+ end
27
+
28
+ private
29
+
30
+ def process_input(input)
31
+ input_value = @incoming_arguments.fetch(input.name, nil)
32
+ input_value = input.default if input.optional? && input_value.blank?
33
+
34
+ @inputs_variables[input.name] = input_value
35
+
36
+ return unless input.internal?
37
+
38
+ @internal_variables[input.name] = input_value
39
+ end
40
+
41
+ def create_instance_variables
42
+ Servactory::Inputs.class_eval(class_inputs_template)
43
+
44
+ @context.assign_inputs(Servactory::Inputs.new(**@inputs_variables))
45
+
46
+ @context.class.class_eval(context_internal_variables_template)
47
+
48
+ @internal_variables.each do |input_name, input_value|
49
+ @context.instance_variable_set(:"@#{input_name}", input_value)
50
+ end
51
+ end
52
+
53
+ ########################################################################
54
+
55
+ # EXAMPLE:
56
+ #
57
+ # attr_reader(*[:attr_1]); def initialize(attr_1); @attr_1 = attr_1; end
58
+ #
59
+ # OR
60
+ #
61
+ # attr_reader(*[:attr_1, :attr_2, :attr_3])
62
+ #
63
+ # def initialize(attr_1:, attr_2:, attr_3:)
64
+ # @attr_1 = attr_1; @attr_2 = attr_2; @attr_3 = attr_3;
65
+ # end
66
+ #
67
+ def class_inputs_template
68
+ <<-RUBY.squish
69
+ attr_reader(*#{@inputs_variables.keys});
70
+
71
+ def initialize(#{@inputs_variables.keys.join(':, ')}:);
72
+ #{@inputs_variables.keys.map { |key| "@#{key} = #{key}" }.join('; ')};
73
+ end
74
+ RUBY
75
+ end
76
+
77
+ # EXAMPLE:
78
+ #
79
+ # private attr_reader(*[:attr_1]);
80
+ #
81
+ def context_internal_variables_template
82
+ <<-RUBY.squish
83
+ private attr_reader(*#{@internal_variables.keys});
84
+ RUBY
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InputArguments
5
+ module Tools
6
+ class Rules
7
+ def self.check!(...)
8
+ new(...).check!
9
+ end
10
+
11
+ def initialize(context, collection_of_input_arguments)
12
+ @context = context
13
+ @collection_of_input_arguments = collection_of_input_arguments
14
+ end
15
+
16
+ def check!
17
+ @collection_of_input_arguments.each do |input_argument|
18
+ check_for!(input_argument)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def check_for!(input_argument)
25
+ return unless input_argument.with_conflicts?
26
+
27
+ raise_error_for(input_argument)
28
+ end
29
+
30
+ def raise_error_for(input_argument)
31
+ raise Servactory.configuration.input_argument_error_class,
32
+ "[#{@context.class.name}] Conflict in `#{input_argument.name}` input " \
33
+ "options: `#{input_argument.conflict_code}`"
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InputArguments
5
+ class Workbench
6
+ def self.work_with(...)
7
+ new(...)
8
+ end
9
+
10
+ def initialize(collection_of_input_arguments)
11
+ @collection_of_input_arguments = collection_of_input_arguments
12
+ end
13
+
14
+ def assign(context:, arguments:)
15
+ @context = context
16
+ @incoming_arguments = arguments
17
+ end
18
+
19
+ def find_unnecessary!
20
+ Tools::FindUnnecessary.check!(context, @incoming_arguments, collection_of_input_arguments)
21
+ end
22
+
23
+ def check_rules!
24
+ Tools::Rules.check!(context, collection_of_input_arguments)
25
+ end
26
+
27
+ def prepare
28
+ Tools::Prepare.prepare(context, @incoming_arguments, collection_of_input_arguments)
29
+ end
30
+
31
+ def check!
32
+ Tools::Check.check!(context, @incoming_arguments, collection_of_input_arguments)
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :context, :collection_of_input_arguments
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ class Inputs # rubocop:disable Lint/EmptyClass
5
+ # NOTE: Look at the file `lib/servactory/input_arguments/tools/prepare.rb`
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InternalArguments
5
+ module Checks
6
+ class Base
7
+ protected
8
+
9
+ def raise_error_with(message, **arguments)
10
+ message = message.call(**arguments) if message.is_a?(Proc)
11
+
12
+ raise Servactory.configuration.internal_argument_error_class, message
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InternalArguments
5
+ module Checks
6
+ class Type < Base
7
+ DEFAULT_MESSAGE = lambda do |service_class_name:, internal_argument:, expected_type:, given_type:|
8
+ "The \"#{internal_argument.name}\" internal argument on \"#{service_class_name}\" must be of type " \
9
+ "\"#{expected_type}\" but was \"#{given_type}\""
10
+ end
11
+
12
+ private_constant :DEFAULT_MESSAGE
13
+
14
+ def self.check!(...)
15
+ new(...).check!
16
+ end
17
+
18
+ ##########################################################################
19
+
20
+ def initialize(context:, internal_argument:, value:)
21
+ super()
22
+
23
+ @context = context
24
+ @internal_argument = internal_argument
25
+ @value = value
26
+ end
27
+
28
+ def check!
29
+ return if prepared_types.any? { |type| @value.is_a?(type) }
30
+
31
+ raise_error_with(
32
+ DEFAULT_MESSAGE,
33
+ service_class_name: @context.class.name,
34
+ internal_argument: @internal_argument,
35
+ expected_type: prepared_types.join(", "),
36
+ given_type: @value.class.name
37
+ )
38
+ end
39
+
40
+ private
41
+
42
+ def prepared_types
43
+ @prepared_types ||=
44
+ Array(@internal_argument.types).map do |type|
45
+ if type.is_a?(String)
46
+ Object.const_get(type)
47
+ else
48
+ type
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InternalArguments
5
+ class Collection
6
+ # NOTE: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
7
+ extend Forwardable
8
+ def_delegators :@collection, :<<, :each, :map
9
+
10
+ def initialize(*)
11
+ @collection = []
12
+ end
13
+
14
+ def names
15
+ map(&:name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InternalArguments
5
+ module DSL
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ private
12
+
13
+ def internal(name, **options)
14
+ collection_of_internal_arguments << InternalArgument.new(name, **options)
15
+ end
16
+
17
+ def collection_of_internal_arguments
18
+ @collection_of_internal_arguments ||= Collection.new
19
+ end
20
+
21
+ def internal_arguments_workbench
22
+ @internal_arguments_workbench ||= Workbench.work_with(collection_of_internal_arguments)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InternalArguments
5
+ class InternalArgument
6
+ attr_reader :name,
7
+ :types,
8
+ :required
9
+
10
+ def initialize(name, type:, **options)
11
+ @name = name
12
+ @types = Array(type)
13
+
14
+ @required = options.fetch(:required, true)
15
+ end
16
+
17
+ def options_for_checks
18
+ {
19
+ types:,
20
+ required:
21
+ }
22
+ end
23
+
24
+ def required?
25
+ required
26
+ end
27
+
28
+ def optional?
29
+ !required
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InternalArguments
5
+ module Tools
6
+ class Prepare
7
+ def self.prepare(...)
8
+ new(...).prepare
9
+ end
10
+
11
+ def initialize(context, collection_of_internal_arguments)
12
+ @context = context
13
+ @collection_of_internal_arguments = collection_of_internal_arguments
14
+ end
15
+
16
+ def prepare
17
+ @collection_of_internal_arguments.each do |internal_argument|
18
+ create_instance_variable_for(internal_argument)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def create_instance_variable_for(internal_argument)
25
+ @context.instance_variable_set(:"@#{internal_argument.name}", nil)
26
+
27
+ @context.class.class_eval(context_internal_argument_template_for(internal_argument))
28
+ end
29
+
30
+ # EXAMPLE:
31
+ #
32
+ # define_method(:user=) do |value|;
33
+ # Servactory::InternalArguments::Checks::Type.check!( context: self, internal_argument:, value: );
34
+ #
35
+ # instance_variable_set(:@user, value);
36
+ # end;
37
+ #
38
+ # private attr_reader :user;
39
+ #
40
+ def context_internal_argument_template_for(internal_argument)
41
+ <<-RUBY.squish
42
+ define_method(:#{internal_argument.name}=) do |value|;
43
+ Servactory::InternalArguments::Checks::Type.check!(
44
+ context: self,
45
+ internal_argument:,
46
+ value:
47
+ );
48
+
49
+ instance_variable_set(:@#{internal_argument.name}, value);
50
+ end;
51
+
52
+ private attr_reader :#{internal_argument.name};
53
+ RUBY
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module InternalArguments
5
+ class Workbench
6
+ def self.work_with(...)
7
+ new(...)
8
+ end
9
+
10
+ def initialize(collection_of_internal_arguments)
11
+ @collection_of_internal_arguments = collection_of_internal_arguments
12
+ end
13
+
14
+ def assign(context:)
15
+ @context = context
16
+ end
17
+
18
+ def prepare
19
+ Tools::Prepare.prepare(context, collection_of_internal_arguments)
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :context, :collection_of_internal_arguments
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module OutputArguments
5
+ module Checks
6
+ class Base
7
+ protected
8
+
9
+ def raise_error_with(message, **arguments)
10
+ message = message.call(**arguments) if message.is_a?(Proc)
11
+
12
+ raise Servactory.configuration.output_argument_error_class, message
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module OutputArguments
5
+ module Checks
6
+ class Type < Base
7
+ DEFAULT_MESSAGE = lambda do |service_class_name:, output_argument:, expected_type:, given_type:|
8
+ "The \"#{output_argument.name}\" output argument on \"#{service_class_name}\" must be of type " \
9
+ "\"#{expected_type}\" but was \"#{given_type}\""
10
+ end
11
+
12
+ private_constant :DEFAULT_MESSAGE
13
+
14
+ def self.check!(...)
15
+ new(...).check!
16
+ end
17
+
18
+ ##########################################################################
19
+
20
+ def initialize(context:, output_argument:, value:)
21
+ super()
22
+
23
+ @context = context
24
+ @output_argument = output_argument
25
+ @value = value
26
+ end
27
+
28
+ def check!
29
+ return if prepared_types.any? { |type| @value.is_a?(type) }
30
+
31
+ raise_error_with(
32
+ DEFAULT_MESSAGE,
33
+ service_class_name: @context.class.name,
34
+ output_argument: @output_argument,
35
+ expected_type: prepared_types.join(", "),
36
+ given_type: @value.class.name
37
+ )
38
+ end
39
+
40
+ private
41
+
42
+ def prepared_types
43
+ @prepared_types ||=
44
+ Array(@output_argument.types).map do |type|
45
+ if type.is_a?(String)
46
+ Object.const_get(type)
47
+ else
48
+ type
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module OutputArguments
5
+ class Collection
6
+ # NOTE: http://words.steveklabnik.com/beware-subclassing-ruby-core-classes
7
+ extend Forwardable
8
+ def_delegators :@collection, :<<, :each, :map
9
+
10
+ def initialize(*)
11
+ @collection = []
12
+ end
13
+
14
+ def names
15
+ map(&:name)
16
+ end
17
+ end
18
+ end
19
+ end