servactory 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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