servactory 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/config/locales/en.yml +31 -3
  3. data/config/locales/ru.yml +31 -3
  4. data/lib/servactory/context/callable.rb +35 -11
  5. data/lib/servactory/context/store.rb +58 -0
  6. data/lib/servactory/context/workspace/internals.rb +2 -2
  7. data/lib/servactory/context/workspace/outputs.rb +2 -2
  8. data/lib/servactory/context/workspace.rb +12 -25
  9. data/lib/servactory/errors/input_error.rb +4 -2
  10. data/lib/servactory/errors/internal_error.rb +4 -2
  11. data/lib/servactory/errors/output_error.rb +4 -2
  12. data/lib/servactory/exceptions/input.rb +4 -2
  13. data/lib/servactory/exceptions/internal.rb +4 -2
  14. data/lib/servactory/exceptions/output.rb +4 -2
  15. data/lib/servactory/inputs/input.rb +36 -3
  16. data/lib/servactory/internals/internal.rb +32 -3
  17. data/lib/servactory/maintenance/attributes/options/registrar.rb +15 -1
  18. data/lib/servactory/maintenance/attributes/translator/must.rb +4 -3
  19. data/lib/servactory/maintenance/attributes/translator/type.rb +2 -2
  20. data/lib/servactory/maintenance/attributes/validations/inclusion.rb +1 -1
  21. data/lib/servactory/maintenance/attributes/validations/must.rb +16 -9
  22. data/lib/servactory/outputs/output.rb +32 -3
  23. data/lib/servactory/result.rb +1 -1
  24. data/lib/servactory/test_kit/result.rb +3 -3
  25. data/lib/servactory/tool_kit/dynamic_options/format.rb +130 -0
  26. data/lib/servactory/tool_kit/dynamic_options/max.rb +68 -0
  27. data/lib/servactory/tool_kit/dynamic_options/min.rb +68 -0
  28. data/lib/servactory/tool_kit/dynamic_options/must.rb +130 -0
  29. data/lib/servactory/utils.rb +30 -4
  30. data/lib/servactory/version.rb +1 -1
  31. data/lib/servactory.rb +2 -0
  32. metadata +9 -4
@@ -29,7 +29,7 @@ module Servactory
29
29
 
30
30
  if collection_message.is_a?(Proc)
31
31
  collection_message.call(
32
- "#{attribute.system_name}_name": attribute.name,
32
+ **Servactory::Utils.fetch_hash_with_desired_attribute(attribute),
33
33
  expected_type: expected_type,
34
34
  given_type: given_type
35
35
  )
@@ -59,7 +59,7 @@ module Servactory
59
59
 
60
60
  if hash_message.is_a?(Proc)
61
61
  hash_message.call(
62
- "#{attribute.system_name}_name": attribute.name,
62
+ **Servactory::Utils.fetch_hash_with_desired_attribute(attribute),
63
63
  key_name: key_name,
64
64
  expected_type: expected_type,
65
65
  given_type: given_type
@@ -52,7 +52,7 @@ module Servactory
52
52
  add_error(
53
53
  message: message.presence || Servactory::Maintenance::Attributes::Translator::Inclusion.default_message,
54
54
  service_class_name: @context.class.name,
55
- "#{@attribute.system_name}": @attribute,
55
+ **Servactory::Utils.fetch_hash_with_desired_attribute(@attribute),
56
56
  value: @value
57
57
  )
58
58
  end
@@ -28,11 +28,11 @@ module Servactory
28
28
 
29
29
  def check
30
30
  @check_options.each do |code, options|
31
- message = call_or_fetch_message_from(code, options)
31
+ message, reason = call_or_fetch_message_from(code, options)
32
32
 
33
33
  next if message.blank?
34
34
 
35
- add_error_with(message, code)
35
+ add_error_with(message, code, reason)
36
36
  end
37
37
 
38
38
  errors
@@ -40,12 +40,18 @@ module Servactory
40
40
 
41
41
  private
42
42
 
43
- def call_or_fetch_message_from(code, options)
43
+ def call_or_fetch_message_from(code, options) # rubocop:disable Metrics/MethodLength
44
44
  check, message = options.values_at(:is, :message)
45
45
 
46
- return if check.call(value: @value)
46
+ check_result, check_result_code =
47
+ check.call(value: @value, **Servactory::Utils.fetch_hash_with_desired_attribute(@attribute))
47
48
 
48
- message.presence || Servactory::Maintenance::Attributes::Translator::Must.default_message
49
+ return if check_result
50
+
51
+ [
52
+ message.presence || Servactory::Maintenance::Attributes::Translator::Must.default_message,
53
+ check_result_code
54
+ ]
49
55
  rescue StandardError => e
50
56
  add_syntax_error_with(
51
57
  Servactory::Maintenance::Attributes::Translator::Must.syntax_error_message,
@@ -56,13 +62,14 @@ module Servactory
56
62
 
57
63
  ########################################################################
58
64
 
59
- def add_error_with(message, code)
65
+ def add_error_with(message, code, reason)
60
66
  add_error(
61
67
  message: message,
62
68
  service_class_name: @context.class.name,
63
- "#{@attribute.system_name}": @attribute,
69
+ **Servactory::Utils.fetch_hash_with_desired_attribute(@attribute),
64
70
  value: @value,
65
- code: code
71
+ code: code,
72
+ reason: reason
66
73
  )
67
74
  end
68
75
 
@@ -70,7 +77,7 @@ module Servactory
70
77
  add_error(
71
78
  message: message,
72
79
  service_class_name: @context.class.name,
73
- "#{@attribute.system_name}": @attribute,
80
+ **Servactory::Utils.fetch_hash_with_desired_attribute(@attribute),
74
81
  value: @value,
75
82
  code: code,
76
83
  exception_message: exception_message
@@ -3,6 +3,25 @@
3
3
  module Servactory
4
4
  module Outputs
5
5
  class Output
6
+ class Work
7
+ attr_reader :name,
8
+ :types,
9
+ :inclusion
10
+
11
+ def initialize(output)
12
+ @name = output.name
13
+ @types = output.types
14
+ @inclusion = output.inclusion.slice(:in) if output.inclusion_present?
15
+
16
+ define_singleton_method(:system_name) { output.system_name }
17
+ define_singleton_method(:i18n_name) { output.i18n_name }
18
+ # The methods below are required to support the internal work.
19
+ define_singleton_method(:input?) { false }
20
+ define_singleton_method(:internal?) { false }
21
+ define_singleton_method(:output?) { true }
22
+ end
23
+ end
24
+
6
25
  attr_reader :name,
7
26
  :collection_of_options
8
27
 
@@ -35,7 +54,10 @@ module Servactory
35
54
  end
36
55
 
37
56
  def register_options(helpers:, options:) # rubocop:disable Metrics/MethodLength
57
+ advanced_helpers = options.except(*Servactory::Maintenance::Attributes::Options::Registrar::RESERVED_OPTIONS)
58
+
38
59
  options = apply_helpers_for_options(helpers: helpers, options: options) if helpers.present?
60
+ options = apply_helpers_for_options(helpers: advanced_helpers, options: options) if advanced_helpers.present?
39
61
 
40
62
  options_registrar = Servactory::Maintenance::Attributes::Options::Registrar.register(
41
63
  attribute: self,
@@ -54,15 +76,22 @@ module Servactory
54
76
  @collection_of_options = options_registrar.collection
55
77
  end
56
78
 
57
- def apply_helpers_for_options(helpers:, options:)
79
+ def apply_helpers_for_options(helpers:, options:) # rubocop:disable Metrics/MethodLength
58
80
  prepared_options = {}
59
81
 
60
- helpers.each do |helper|
82
+ helpers.each do |(helper, values)|
61
83
  found_helper = @option_helpers.find_by(name: helper)
62
84
 
63
85
  next if found_helper.blank?
64
86
 
65
- prepared_options.merge!(found_helper.equivalent)
87
+ prepared_option =
88
+ if found_helper.equivalent.is_a?(Proc)
89
+ values.is_a?(Hash) ? found_helper.equivalent.call(**values) : found_helper.equivalent.call(values)
90
+ else
91
+ found_helper.equivalent
92
+ end
93
+
94
+ prepared_options.deep_merge!(prepared_option)
66
95
  end
67
96
 
68
97
  options.merge(prepared_options)
@@ -88,7 +88,7 @@ module Servactory
88
88
  end
89
89
 
90
90
  def outputs
91
- @outputs ||= Outputs.new(@context.send(:servactory_service_storage).fetch(:outputs))
91
+ @outputs ||= Outputs.new(@context.send(:servactory_service_store).outputs)
92
92
  end
93
93
 
94
94
  ########################################################################
@@ -13,7 +13,7 @@ module Servactory
13
13
 
14
14
  def initialize(attributes = {})
15
15
  attributes.each_pair do |name, value|
16
- servactory_service_storage[:outputs].merge!({ name => value })
16
+ servactory_service_store.assign_output(name, value)
17
17
  end
18
18
  end
19
19
 
@@ -21,8 +21,8 @@ module Servactory
21
21
 
22
22
  private
23
23
 
24
- def servactory_service_storage
25
- @servactory_service_storage ||= { outputs: {} }
24
+ def servactory_service_store
25
+ @servactory_service_store ||= Servactory::Context::Store.new(self)
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module ToolKit
5
+ module DynamicOptions
6
+ class Format < Must
7
+ DEFAULT_FORMATS = {
8
+ boolean: {
9
+ pattern: /^(true|false|0|1)$/i,
10
+ validator: ->(value:) { %w[true 1].include?(value&.downcase) }
11
+ },
12
+ email: {
13
+ pattern: URI::MailTo::EMAIL_REGEXP,
14
+ validator: ->(value:) { value.present? }
15
+ },
16
+ date: {
17
+ pattern: nil,
18
+ validator: lambda do |value:|
19
+ Date.parse(value) and return true
20
+ rescue Date::Error
21
+ false
22
+ end
23
+ },
24
+ datetime: {
25
+ pattern: nil,
26
+ validator: lambda do |value:|
27
+ DateTime.parse(value) and return true
28
+ rescue Date::Error
29
+ false
30
+ end
31
+ },
32
+ password: {
33
+ # NOTE: Pattern 4 » https://dev.to/rasaf_ibrahim/write-regex-password-validation-like-a-pro-5175
34
+ # Password must contain one digit from 1 to 9, one lowercase letter, one
35
+ # uppercase letter, and one underscore, and it must be 8-16 characters long.
36
+ # Usage of any other special character and usage of space is optional.
37
+ pattern: /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,16}$/,
38
+ validator: ->(value:) { value.present? }
39
+ },
40
+ time: {
41
+ pattern: nil,
42
+ validator: lambda do |value:|
43
+ Time.parse(value) and return true
44
+ rescue ArgumentError
45
+ false
46
+ end
47
+ }
48
+ }.freeze
49
+ private_constant :DEFAULT_FORMATS
50
+
51
+ def self.use(option_name = :format, formats: {})
52
+ instance = new(option_name)
53
+ instance.assign(formats)
54
+ instance.must(:be_in_format)
55
+ end
56
+
57
+ def assign(formats = {})
58
+ @formats = formats.is_a?(Hash) ? DEFAULT_FORMATS.merge(formats) : DEFAULT_FORMATS
59
+ end
60
+
61
+ def condition_for_input_with(...)
62
+ common_condition_with(...)
63
+ end
64
+
65
+ def condition_for_internal_with(...)
66
+ common_condition_with(...)
67
+ end
68
+
69
+ def condition_for_output_with(...)
70
+ common_condition_with(...)
71
+ end
72
+
73
+ def common_condition_with(value:, option:, **)
74
+ option_value = option.value&.to_sym
75
+
76
+ return [false, :unknown] unless @formats.key?(option_value)
77
+
78
+ format_options = @formats.fetch(option_value)
79
+
80
+ format_pattern = option.properties.fetch(:pattern, format_options.fetch(:pattern))
81
+
82
+ return [false, :wrong_pattern] if format_pattern.present? && !value.match?(Regexp.compile(format_pattern))
83
+
84
+ option.properties.fetch(:validator, format_options.fetch(:validator)).call(value: value)
85
+ end
86
+
87
+ ########################################################################
88
+
89
+ def message_for_input_with(service_class_name:, input:, value:, option_value:, reason:, **)
90
+ i18n_key = "servactory.inputs.validations.must.dynamic_options.format"
91
+ i18n_key += reason.present? ? ".#{reason}" : ".default"
92
+
93
+ I18n.t(
94
+ i18n_key,
95
+ service_class_name: service_class_name,
96
+ input_name: input.name,
97
+ value: value,
98
+ format_name: option_value.present? ? option_value : option_value.inspect
99
+ )
100
+ end
101
+
102
+ def message_for_internal_with(service_class_name:, internal:, value:, option_value:, reason:, **)
103
+ i18n_key = "servactory.internals.validations.must.dynamic_options.format"
104
+ i18n_key += reason.present? ? ".#{reason}" : ".default"
105
+
106
+ I18n.t(
107
+ i18n_key,
108
+ service_class_name: service_class_name,
109
+ internal_name: internal.name,
110
+ value: value,
111
+ format_name: option_value.present? ? option_value : option_value.inspect
112
+ )
113
+ end
114
+
115
+ def message_for_output_with(service_class_name:, output:, value:, option_value:, reason:, **)
116
+ i18n_key = "servactory.outputs.validations.must.dynamic_options.format"
117
+ i18n_key += reason.present? ? ".#{reason}" : ".default"
118
+
119
+ I18n.t(
120
+ i18n_key,
121
+ service_class_name: service_class_name,
122
+ output_name: output.name,
123
+ value: value,
124
+ format_name: option_value.present? ? option_value : option_value.inspect
125
+ )
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module ToolKit
5
+ module DynamicOptions
6
+ class Max < Must
7
+ def self.use(option_name = :max)
8
+ new(option_name).must(:be_less_than_or_equal_to)
9
+ end
10
+
11
+ def condition_for_input_with(...)
12
+ common_condition_with(...)
13
+ end
14
+
15
+ def condition_for_internal_with(...)
16
+ common_condition_with(...)
17
+ end
18
+
19
+ def condition_for_output_with(...)
20
+ common_condition_with(...)
21
+ end
22
+
23
+ def common_condition_with(value:, option:, **)
24
+ case value
25
+ when Integer
26
+ value <= option.value
27
+ else
28
+ return false unless value.respond_to?(:size)
29
+
30
+ value.size <= option.value
31
+ end
32
+ end
33
+
34
+ ########################################################################
35
+
36
+ def message_for_input_with(service_class_name:, input:, value:, option_value:, **)
37
+ I18n.t(
38
+ "servactory.inputs.validations.must.dynamic_options.max.default",
39
+ service_class_name: service_class_name,
40
+ input_name: input.name,
41
+ value: value,
42
+ option_value: option_value
43
+ )
44
+ end
45
+
46
+ def message_for_internal_with(service_class_name:, internal:, value:, option_value:, **)
47
+ I18n.t(
48
+ "servactory.internals.validations.must.dynamic_options.max.default",
49
+ service_class_name: service_class_name,
50
+ internal_name: internal.name,
51
+ value: value,
52
+ option_value: option_value
53
+ )
54
+ end
55
+
56
+ def message_for_output_with(service_class_name:, output:, value:, option_value:, **)
57
+ I18n.t(
58
+ "servactory.outputs.validations.must.dynamic_options.max.default",
59
+ service_class_name: service_class_name,
60
+ output_name: output.name,
61
+ value: value,
62
+ option_value: option_value
63
+ )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module ToolKit
5
+ module DynamicOptions
6
+ class Min < Must
7
+ def self.use(option_name = :min)
8
+ new(option_name).must(:be_greater_than_or_equal_to)
9
+ end
10
+
11
+ def condition_for_input_with(...)
12
+ common_condition_with(...)
13
+ end
14
+
15
+ def condition_for_internal_with(...)
16
+ common_condition_with(...)
17
+ end
18
+
19
+ def condition_for_output_with(...)
20
+ common_condition_with(...)
21
+ end
22
+
23
+ def common_condition_with(value:, option:, **)
24
+ case value
25
+ when Integer
26
+ value >= option.value
27
+ else
28
+ return false unless value.respond_to?(:size)
29
+
30
+ value.size >= option.value
31
+ end
32
+ end
33
+
34
+ ########################################################################
35
+
36
+ def message_for_input_with(service_class_name:, input:, value:, option_value:, **)
37
+ I18n.t(
38
+ "servactory.inputs.validations.must.dynamic_options.min.default",
39
+ service_class_name: service_class_name,
40
+ input_name: input.name,
41
+ value: value,
42
+ option_value: option_value
43
+ )
44
+ end
45
+
46
+ def message_for_internal_with(service_class_name:, internal:, value:, option_value:, **)
47
+ I18n.t(
48
+ "servactory.internals.validations.must.dynamic_options.min.default",
49
+ service_class_name: service_class_name,
50
+ internal_name: internal.name,
51
+ value: value,
52
+ option_value: option_value
53
+ )
54
+ end
55
+
56
+ def message_for_output_with(service_class_name:, output:, value:, option_value:, **)
57
+ I18n.t(
58
+ "servactory.outputs.validations.must.dynamic_options.min.default",
59
+ service_class_name: service_class_name,
60
+ output_name: output.name,
61
+ value: value,
62
+ option_value: option_value
63
+ )
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Servactory
4
+ module ToolKit
5
+ module DynamicOptions
6
+ class Must
7
+ class WorkOption
8
+ attr_reader :value,
9
+ :message,
10
+ :properties
11
+
12
+ def initialize(data, body_key:, body_fallback:)
13
+ @value =
14
+ if data.is_a?(Hash) && data.key?(body_key)
15
+ data.delete(body_key)
16
+ else
17
+ data.present? ? data : body_fallback
18
+ end
19
+
20
+ @message = (data.is_a?(Hash) && data.key?(:message) ? data.delete(:message) : nil)
21
+ @properties = data.is_a?(Hash) ? data : {}
22
+ end
23
+ end
24
+
25
+ def initialize(option_name, body_key = :is, body_fallback = nil)
26
+ @option_name = option_name
27
+ @body_key = body_key
28
+ @body_fallback = body_fallback
29
+ end
30
+
31
+ def must(name)
32
+ Servactory::Maintenance::Attributes::OptionHelper.new(
33
+ name: @option_name,
34
+ equivalent: equivalent_with(name)
35
+ )
36
+ end
37
+
38
+ def equivalent_with(name)
39
+ lambda do |data|
40
+ option = WorkOption.new(data, body_key: @body_key, body_fallback: @body_fallback)
41
+
42
+ {
43
+ must: {
44
+ name => must_content_with(option)
45
+ }
46
+ }
47
+ end
48
+ end
49
+
50
+ def must_content_with(option)
51
+ {
52
+ is: must_content_value_with(option),
53
+ message: must_content_message_with(option)
54
+ }
55
+ end
56
+
57
+ ########################################################################
58
+
59
+ def must_content_value_with(option)
60
+ lambda do |value:, input: nil, internal: nil, output: nil|
61
+ if input.present? && input.input?
62
+ condition_for_input_with(input: input, value: value, option: option)
63
+ elsif internal.present? && internal.internal?
64
+ condition_for_internal_with(internal: internal, value: value, option: option)
65
+ elsif output.present? && output.output?
66
+ condition_for_output_with(output: output, value: value, option: option)
67
+ end
68
+ end
69
+ end
70
+
71
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
72
+ def must_content_message_with(option)
73
+ is_option_message_present = option.message.present?
74
+ is_option_message_proc = option.message.is_a?(Proc) if is_option_message_present
75
+
76
+ lambda do |input: nil, internal: nil, output: nil, **attributes|
77
+ default_attributes = { **attributes, option_value: option.value }
78
+
79
+ if Servactory::Utils.really_input?(input)
80
+ if is_option_message_present
81
+ is_option_message_proc ? option.message.call(**default_attributes.merge(input: input)) : option.message
82
+ else
83
+ message_for_input_with(**default_attributes.merge(input: input))
84
+ end
85
+ elsif Servactory::Utils.really_internal?(internal)
86
+ if is_option_message_present
87
+ is_option_message_proc ? option.message.call(**default_attributes.merge(internal: internal)) : option.message # rubocop:disable Layout/LineLength
88
+ else
89
+ message_for_internal_with(**default_attributes.merge(internal: internal))
90
+ end
91
+ elsif Servactory::Utils.really_output?(output)
92
+ if is_option_message_present
93
+ is_option_message_proc ? option.message.call(**default_attributes.merge(output: output)) : option.message # rubocop:disable Layout/LineLength
94
+ else
95
+ message_for_output_with(**default_attributes.merge(output: output))
96
+ end
97
+ end
98
+ end
99
+ end
100
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
101
+
102
+ ########################################################################
103
+
104
+ def condition_for_input_with(**)
105
+ raise "Need to implement `condition_for_input_with(**attributes)` method"
106
+ end
107
+
108
+ def condition_for_internal_with(**)
109
+ raise "Need to implement `condition_for_internal_with(**attributes)` method"
110
+ end
111
+
112
+ def condition_for_output_with(**)
113
+ raise "Need to implement `condition_for_output_with(**attributes)` method"
114
+ end
115
+
116
+ def message_for_input_with(**)
117
+ raise "Need to implement `message_for_input_with(**attributes)` method"
118
+ end
119
+
120
+ def message_for_internal_with(**)
121
+ raise "Need to implement `message_for_internal_with(**attributes)` method"
122
+ end
123
+
124
+ def message_for_output_with(**)
125
+ raise "Need to implement `message_for_output_with(**attributes)` method"
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -4,14 +4,40 @@ module Servactory
4
4
  module Utils
5
5
  module_function
6
6
 
7
- def define_attribute_with(input:, internal:, output:)
8
- return input if input.present?
9
- return internal if internal.present?
10
- return output if output.present?
7
+ def fetch_hash_with_desired_attribute(attribute)
8
+ return { input: attribute.class::Work.new(attribute) } if really_input?(attribute)
9
+ return { internal: attribute.class::Work.new(attribute) } if really_internal?(attribute)
10
+ return { output: attribute.class::Work.new(attribute) } if really_output?(attribute)
11
+
12
+ raise ArgumentError, "Failed to define attribute"
13
+ end
14
+
15
+ def define_attribute_with(input: nil, internal: nil, output: nil)
16
+ return input if really_input?(input)
17
+ return internal if really_internal?(internal)
18
+ return output if really_output?(output)
11
19
 
12
20
  raise ArgumentError, "missing keyword: :input, :internal or :output"
13
21
  end
14
22
 
23
+ def really_input?(attribute = nil)
24
+ return true if attribute.present? && attribute.input?
25
+
26
+ false
27
+ end
28
+
29
+ def really_internal?(attribute = nil)
30
+ return true if attribute.present? && attribute.internal?
31
+
32
+ false
33
+ end
34
+
35
+ def really_output?(attribute = nil)
36
+ return true if attribute.present? && attribute.output?
37
+
38
+ false
39
+ end
40
+
15
41
  FALSE_VALUES = [
16
42
  false,
17
43
  nil, "",
@@ -3,7 +3,7 @@
3
3
  module Servactory
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 3
6
+ MINOR = 4
7
7
  PATCH = 0
8
8
  PRE = nil
9
9
 
data/lib/servactory.rb CHANGED
@@ -4,6 +4,8 @@ require "zeitwerk"
4
4
 
5
5
  require "active_support/all"
6
6
 
7
+ require "uri"
8
+
7
9
  loader = Zeitwerk::Loader.for_gem
8
10
  loader.inflector.inflect(
9
11
  "dsl" => "DSL"