cloud-templates 0.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 (86) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +29 -0
  3. data/.simplecov +6 -0
  4. data/Gemfile +2 -0
  5. data/LICENSE +201 -0
  6. data/NOTICE +13 -0
  7. data/README.md +124 -0
  8. data/Rakefile +27 -0
  9. data/cloud-templates.gemspec +27 -0
  10. data/examples/lib/user_directory/artifacts/catalogized.rb +11 -0
  11. data/examples/lib/user_directory/artifacts/group.rb +37 -0
  12. data/examples/lib/user_directory/artifacts/ided.rb +11 -0
  13. data/examples/lib/user_directory/artifacts/organization.rb +17 -0
  14. data/examples/lib/user_directory/artifacts/pathed.rb +22 -0
  15. data/examples/lib/user_directory/artifacts/person.rb +20 -0
  16. data/examples/lib/user_directory/artifacts/team.rb +31 -0
  17. data/examples/lib/user_directory/artifacts/unit.rb +24 -0
  18. data/examples/lib/user_directory/artifacts/user.rb +29 -0
  19. data/examples/lib/user_directory/render/etc/artifact_view.rb +15 -0
  20. data/examples/lib/user_directory/render/etc/composite_view.rb +26 -0
  21. data/examples/lib/user_directory/render/etc/group_view.rb +23 -0
  22. data/examples/lib/user_directory/render/etc/person_view.rb +19 -0
  23. data/examples/lib/user_directory/render/etc/registry.rb +33 -0
  24. data/examples/lib/user_directory/render/etc/user_view.rb +35 -0
  25. data/examples/lib/user_directory/render/etc.rb +3 -0
  26. data/examples/lib/user_directory/render/ldap/artifact_view.rb +27 -0
  27. data/examples/lib/user_directory/render/ldap/composite_view.rb +32 -0
  28. data/examples/lib/user_directory/render/ldap/group_view.rb +28 -0
  29. data/examples/lib/user_directory/render/ldap/organization_view.rb +26 -0
  30. data/examples/lib/user_directory/render/ldap/person_view.rb +39 -0
  31. data/examples/lib/user_directory/render/ldap/registry.rb +16 -0
  32. data/examples/lib/user_directory/render/ldap/unit_view.rb +26 -0
  33. data/examples/lib/user_directory/render/ldap/user_view.rb +39 -0
  34. data/examples/lib/user_directory/render/ldap.rb +3 -0
  35. data/examples/lib/user_directory/utils.rb +18 -0
  36. data/examples/lib/user_directory.rb +23 -0
  37. data/examples/lib_path.rb +2 -0
  38. data/examples/spec/spec_helper.rb +1 -0
  39. data/examples/spec/user_directory_spec.rb +568 -0
  40. data/lib/aws/templates/artifact.rb +140 -0
  41. data/lib/aws/templates/composite.rb +178 -0
  42. data/lib/aws/templates/exceptions.rb +221 -0
  43. data/lib/aws/templates/render/registry.rb +60 -0
  44. data/lib/aws/templates/render/utils/base_type_views.rb +131 -0
  45. data/lib/aws/templates/render/view.rb +127 -0
  46. data/lib/aws/templates/render.rb +72 -0
  47. data/lib/aws/templates/utils/artifact_storage.rb +141 -0
  48. data/lib/aws/templates/utils/contextualized/filters.rb +437 -0
  49. data/lib/aws/templates/utils/contextualized/hash.rb +13 -0
  50. data/lib/aws/templates/utils/contextualized/nil.rb +13 -0
  51. data/lib/aws/templates/utils/contextualized/proc.rb +13 -0
  52. data/lib/aws/templates/utils/contextualized.rb +113 -0
  53. data/lib/aws/templates/utils/default.rb +185 -0
  54. data/lib/aws/templates/utils/dependency/enumerable.rb +13 -0
  55. data/lib/aws/templates/utils/dependency/object.rb +46 -0
  56. data/lib/aws/templates/utils/dependency.rb +121 -0
  57. data/lib/aws/templates/utils/dependent.rb +28 -0
  58. data/lib/aws/templates/utils/inheritable.rb +52 -0
  59. data/lib/aws/templates/utils/late_bound.rb +89 -0
  60. data/lib/aws/templates/utils/memoized.rb +27 -0
  61. data/lib/aws/templates/utils/named.rb +19 -0
  62. data/lib/aws/templates/utils/options.rb +279 -0
  63. data/lib/aws/templates/utils/parametrized/constraints.rb +423 -0
  64. data/lib/aws/templates/utils/parametrized/getters.rb +293 -0
  65. data/lib/aws/templates/utils/parametrized/guarded.rb +32 -0
  66. data/lib/aws/templates/utils/parametrized/mapper.rb +73 -0
  67. data/lib/aws/templates/utils/parametrized/nested.rb +72 -0
  68. data/lib/aws/templates/utils/parametrized/transformations.rb +660 -0
  69. data/lib/aws/templates/utils/parametrized.rb +240 -0
  70. data/lib/aws/templates/utils.rb +219 -0
  71. data/lib/aws/templates.rb +16 -0
  72. data/spec/aws/templates/artifact_spec.rb +161 -0
  73. data/spec/aws/templates/composite_spec.rb +361 -0
  74. data/spec/aws/templates/render/utils/base_type_views_spec.rb +104 -0
  75. data/spec/aws/templates/render_spec.rb +62 -0
  76. data/spec/aws/templates/utils/as_named_spec.rb +31 -0
  77. data/spec/aws/templates/utils/contextualized/filters_spec.rb +108 -0
  78. data/spec/aws/templates/utils/contextualized_spec.rb +115 -0
  79. data/spec/aws/templates/utils/late_bound_spec.rb +52 -0
  80. data/spec/aws/templates/utils/options_spec.rb +67 -0
  81. data/spec/aws/templates/utils/parametrized/constraint_spec.rb +175 -0
  82. data/spec/aws/templates/utils/parametrized/getters_spec.rb +139 -0
  83. data/spec/aws/templates/utils/parametrized/transformation_spec.rb +314 -0
  84. data/spec/aws/templates/utils/parametrized_spec.rb +241 -0
  85. data/spec/spec_helper.rb +6 -0
  86. metadata +244 -0
@@ -0,0 +1,279 @@
1
+ require 'set'
2
+ require 'aws/templates/exceptions'
3
+ require 'aws/templates/utils'
4
+ require 'aws/templates/utils/memoized'
5
+
6
+ module Aws
7
+ module Templates
8
+ module Utils
9
+ # rubocop:disable Metrics/ClassLength
10
+
11
+ ##
12
+ # Options hash-like class
13
+ #
14
+ # Implements the core mechanism of hash lookup, merge, and transformation.
15
+ #
16
+ # It supports nested hash lookup with index function and wildcard
17
+ # hash definitions on any level of nested hierarchy so you can define
18
+ # fallback values right in the input hash. The algorithm will try to
19
+ # select the best combination if it doesn't see the exact match
20
+ # during lookup.
21
+ class Options
22
+ include Memoized
23
+
24
+ ##
25
+ # Get a parameter from resulting hash or any nested part of it
26
+ #
27
+ # The method can access resulting hash as a tree performing
28
+ # traverse as needed. Also, it handles nil-pointer situations
29
+ # correctly so you will get no exception but just 'nil' even when
30
+ # the whole branch you're trying to access don't exist or contains
31
+ # non-hash value somewhere in the middle. Also, the method
32
+ # recognizes asterisk (*) hash records which is an analog of
33
+ # match-all or default values for some sub-branch.
34
+ #
35
+ # * +path+ - an array representing path of the value in the nested
36
+ # hash
37
+ #
38
+ # ==== Example
39
+ #
40
+ # opts = Options.new(
41
+ # 'a' => {
42
+ # 'b' => 'c',
43
+ # '*' => { '*' => 2 }
44
+ # },
45
+ # 'd' => 1
46
+ # )
47
+ # opts.to_hash # => { 'a' => { 'b' => 'c', '*' => { '*' => 2 } }, 'd' => 1 }
48
+ # opts['a'] # => Options.new('b' => 'c', '*' => { '*' => 2 })
49
+ # opts['a', 'b'] # => 'c'
50
+ # opts['d', 'e'] # => nil
51
+ # # multi-level wildcard match
52
+ # opts['a', 'z', 'r'] # => 2
53
+ def [](*path)
54
+ @structures.reverse_each.inject(nil) do |memo, container|
55
+ ret = begin
56
+ Utils.lookup(container, path.dup)
57
+ rescue OptionValueDeleted, OptionScalarOnTheWay
58
+ # we discovered that this layer either have value deleted or parent was overriden
59
+ # by a scalar. Either way we just return what we have in the memo
60
+ break memo
61
+ end
62
+
63
+ # if current container doesn't have this value - let's go to the next iteration
64
+ next memo if ret.nil?
65
+
66
+ # if found value is a scalar then either we return it as is or return memo
67
+ # if memo is not nil it means that we've found hierarchical objects before
68
+ break(memo.nil? ? ret : memo) unless Utils.recursive?(ret)
69
+
70
+ # value is not a scalar. it means we need to keep merging them
71
+ memo.nil? ? Options.new(ret) : memo.unshift_layer!(ret)
72
+ end
73
+ end
74
+
75
+ def dependency?
76
+ !dependencies.empty?
77
+ end
78
+
79
+ def dependencies
80
+ memoize(:dependencies) do
81
+ select_recursively(&:dependency?)
82
+ .inject(Set.new) { |acc, elem| acc.merge(elem.dependencies) }
83
+ end
84
+ end
85
+
86
+ def select_recursively(&blk)
87
+ Utils.select_recursively(self, &blk)
88
+ end
89
+
90
+ ##
91
+ # Set the parameter with the path to the value
92
+ #
93
+ # The method can access resulting hash as a tree performing
94
+ # traverse as needed. When stubled uponAlso, it handles non-existent
95
+ # keys correctly creating sub-branches as necessary. If a non-hash
96
+ # and non-nil value discovered in the middle of the path, an exception
97
+ # will be thrown. The method doesn't give any special meaning
98
+ # to wildcards keys so you can set wildcard parameters also.
99
+ #
100
+ # * +path+ - an array representing path of the value in the nested
101
+ # hash
102
+ # * +value+ - value to set the parameter to
103
+ #
104
+ # ==== Example
105
+ #
106
+ # opts = Options.new({})
107
+ # opts.to_hash # => {}
108
+ # opts['a', 'b'] = 'c'
109
+ # opts['a', '*', '*'] = 2
110
+ # opts['d'] = 1
111
+ # opts.to_hash # => { 'a' => { 'b' => 'c', '*' => { '*' => 2 } }, 'd' => 1 }
112
+ def []=(*path_and_value)
113
+ value = path_and_value.pop
114
+ path = path_and_value
115
+ dirty!.cow! # mooo
116
+ Utils.set_recursively(@structures.last, value, path)
117
+ end
118
+
119
+ ##
120
+ # Delete a branch
121
+ #
122
+ # Delete a branch in the options. Rather than deleting it from hash, the path is assigned
123
+ # with special marker that it was deleted. It helps avoid hash recalculation leading to
124
+ # memory thrashing simultaneously maintaining semantics close to Hash#delete
125
+ def delete(*path)
126
+ self[*path] = DELETED_MARKER
127
+ end
128
+
129
+ ##
130
+ # Transforms to hash object
131
+ #
132
+ # Produces a hash out of Options object merging COW layers iteratively and calculating them
133
+ # recursively.
134
+ def to_hash
135
+ memoize(:to_hash) do
136
+ _process_hashed(@structures.inject({}) { |acc, elem| Utils.merge(acc, elem) })
137
+ end
138
+ end
139
+
140
+ ##
141
+ # Create filter
142
+ #
143
+ # Gets hash representstion of the Options instance and transforms it to filter
144
+ def to_filter
145
+ to_hash.to_filter
146
+ end
147
+
148
+ ##
149
+ # Top-level keys
150
+ #
151
+ # Produces a list of top-level keys from all layers. Deleted branches are not included.
152
+ def keys
153
+ memoize(:keys) do
154
+ @structures
155
+ .each_with_object(Set.new) do |container, keyset|
156
+ container.keys.each do |k|
157
+ container[k] == DELETED_MARKER ? keyset.delete(k) : keyset.add(k)
158
+ end
159
+ end
160
+ .to_a
161
+ end
162
+ end
163
+
164
+ ##
165
+ # If top-level key exists
166
+ #
167
+ # Checks if top-level key exists. Deleted branches are excluded.
168
+ def include?(k)
169
+ found = @structures.reverse_each.find { |container| container.include?(k) }
170
+ !found.nil? && (found[k] != DELETED_MARKER)
171
+ end
172
+
173
+ ##
174
+ # Merge Options with object
175
+ #
176
+ # Create new Options object which is a merge of the target Options instance with an object.
177
+ # The object must be "recusrsive" meaning it should satisfy minimum contract for
178
+ # "recursive". See Utils::recursive? for details
179
+ def merge(other)
180
+ self.class.new(*@structures, other)
181
+ end
182
+
183
+ ##
184
+ # Merge Options with object in-place
185
+ #
186
+ # Put the passed object as the top layer of the current instance.
187
+ # The object must be "recursive" meaning it should satisfy minimum contract for
188
+ # "recursive". See Utils::recursive? for details
189
+ def merge!(other)
190
+ raise OptionShouldBeRecursive.new(other) unless Utils.recursive?(other)
191
+ @structures << other
192
+ dirty!
193
+ end
194
+
195
+ ##
196
+ # Filter options
197
+ #
198
+ # Filter options with provided Proc. The proc should accept one parameter satisfying
199
+ # "recursive" contract. See Utils.recursive
200
+ def filter
201
+ Options.new(yield self)
202
+ end
203
+
204
+ ##
205
+ # Initialize Options with list of recursive structures (See Options#recursive?)
206
+ def initialize(*structures)
207
+ @structures = structures.map do |container|
208
+ if Utils.recursive?(container)
209
+ container
210
+ elsif Utils.hashable?(container)
211
+ container.to_hash
212
+ else
213
+ raise OptionShouldBeRecursive.new(container)
214
+ end
215
+ end
216
+ end
217
+
218
+ ##
219
+ # Duplicate the options
220
+ #
221
+ # Duplicates the object itself and puts another layer of hash map. All original hash maps
222
+ # are not touched if the duplicate is modified.
223
+ def dup
224
+ Options.new(*@structures)
225
+ end
226
+
227
+ ##
228
+ # Squash all layers into one
229
+ #
230
+ # Options is designed with very specific goal to be memory-friendly and re-use merged
231
+ # objects as immutable layers. However, after some particular threshold of layer's stack
232
+ # size, performance of index operations can suffer significantly. To mitigate this user can
233
+ # use the method to squash all layers into one aggregated hash.
234
+ #
235
+ # The method performs in-place stack modification
236
+ def compact!
237
+ @structures = [to_hash]
238
+ self
239
+ end
240
+
241
+ ##
242
+ # Put the layer to the bottom of the stack
243
+ #
244
+ # However it doesn't resemble exact semantics, the method is similar to reverse_merge!
245
+ # from ActiveSupport. It puts the "recursive" object passed to the bottom of the layer
246
+ # stack of the Options instance effectively making it the least prioritized layer.
247
+ def unshift_layer!(layer)
248
+ raise OptionShouldBeRecursive.new(layer) unless Utils.recursive?(layer)
249
+ @structures.unshift(layer)
250
+ dirty!
251
+ end
252
+
253
+ def cow!
254
+ unless @is_cowed
255
+ @structures << {}
256
+ @is_cowed = true
257
+ end
258
+
259
+ self
260
+ end
261
+
262
+ private
263
+
264
+ # :nodoc: process hashable recursively removing all keys marked as deleted
265
+ def _process_hashed(hashed)
266
+ hashed.each_pair do |key, value|
267
+ if value == DELETED_MARKER
268
+ hashed.delete(key)
269
+ elsif Utils.hashable?(value)
270
+ _process_hashed(value.to_hash).freeze
271
+ end
272
+ end
273
+
274
+ hashed
275
+ end
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,423 @@
1
+ require 'aws/templates/exceptions'
2
+ require 'aws/templates/utils/parametrized'
3
+ require 'set'
4
+ require 'singleton'
5
+
6
+ module Aws
7
+ module Templates
8
+ module Utils
9
+ module Parametrized #:nodoc:
10
+ ##
11
+ # Constraint functor class
12
+ #
13
+ # A constraint is a Proc which accepts one parameter which is a value
14
+ # which needs to be checked and ir is expected to throw an exception
15
+ # if the value is not in compliance with the constraint
16
+ #
17
+ # The class implements functor pattern through to_proc method and
18
+ # closure. Essentially, all constraints can be used everywhere where
19
+ # a block is expected.
20
+ #
21
+ # It provides protected method check which should be overriden in
22
+ # all concrete constraint classes.
23
+ class Constraint
24
+ ##
25
+ # Check if passed value is not nil
26
+ #
27
+ # === Example
28
+ #
29
+ # class Piece
30
+ # include Aws::Templates::Utils::Parametrized
31
+ #
32
+ # parameter :param1, :constraint => not_nil
33
+ # end
34
+ #
35
+ # i = Piece.new(:param1 => 3)
36
+ # i.param1 # => 3
37
+ # i = Piece.new
38
+ # i.param1 # throws ParameterValueInvalid
39
+ class NotNil < Constraint
40
+ include Singleton
41
+
42
+ protected
43
+
44
+ def check(_, value, _)
45
+ raise('required but was not found in input hash') if value.nil?
46
+ end
47
+ end
48
+
49
+ ##
50
+ # Check if passed value is in the enumeration values.
51
+ #
52
+ # === Example
53
+ #
54
+ # class Piece
55
+ # include Aws::Templates::Utils::Parametrized
56
+ #
57
+ # parameter :param1, :constraint => enum([1,'2',3])
58
+ # end
59
+ #
60
+ # i = Piece.new(:param1 => 3)
61
+ # i.param1 # => 3
62
+ # i = Piece.new(:param1 => 4)
63
+ # i.param1 # throws ParameterValueInvalid
64
+ class Enum < Constraint
65
+ attr_reader :set
66
+
67
+ def initialize(list)
68
+ @set = Set.new(list)
69
+ end
70
+
71
+ protected
72
+
73
+ def check(_, value, _)
74
+ return if value.nil? || set.include?(value)
75
+
76
+ raise(
77
+ "Value #{value.inspect} is not in the set of allowed " \
78
+ "values #{set.inspect}"
79
+ )
80
+ end
81
+ end
82
+
83
+ ##
84
+ # Switch-like variant check
85
+ #
86
+ # Recursive check implementing switch-based semantics for defining
87
+ # checks need to be performed depending on parameter's value.
88
+ #
89
+ # === Example
90
+ #
91
+ # class Piece
92
+ # include Aws::Templates::Utils::Parametrized
93
+ # parameter :param2
94
+ # parameter :param1,
95
+ # :constraint => depends_on_value(
96
+ # 1 => lambda { |v| raise 'Too big' if param2 > 3 },
97
+ # 2 => lambda { |v| raise 'Too small' if param2 < 2 }
98
+ # )
99
+ # end
100
+ #
101
+ # i = Piece.new(:param1 => 1, :param2 => 1)
102
+ # i.param1 # => 1
103
+ # i = Piece.new(:param1 => 1, :param2 => 5)
104
+ # i.param1 # raise ParameterValueInvalid
105
+ # i = Piece.new(:param1 => 2, :param2 => 1)
106
+ # i.param1 # raise ParameterValueInvalid
107
+ # i = Piece.new(:param1 => 2, :param2 => 5)
108
+ # i.param1 # => 2
109
+ class DependsOnValue < Constraint
110
+ ##
111
+ # Selector hash
112
+ attr_reader :selector
113
+
114
+ def initialize(selector)
115
+ @selector = selector
116
+ end
117
+
118
+ protected
119
+
120
+ def check(parameter, value, instance)
121
+ return unless selector.key?(value)
122
+
123
+ instance.instance_exec(
124
+ parameter,
125
+ value,
126
+ &selector[value]
127
+ )
128
+ end
129
+ end
130
+
131
+ ##
132
+ # Check presence of parameters if the condition is met
133
+ #
134
+ # Requires presence of the methods passed as dependencies in the
135
+ # current scope with non-nil returning values. Default condition
136
+ # for the value is not to be nil. The condition can be either
137
+ # a block or a value.
138
+ #
139
+ # === Example
140
+ #
141
+ # class Piece
142
+ # include Aws::Templates::Utils::Parametrized
143
+ # parameter :param2
144
+ # parameter :param1, :constraint => requires(:param2)
145
+ # end
146
+ #
147
+ # i = Piece.new(:param2 => 1)
148
+ # i.param1 # => nil
149
+ # i = Piece.new(:param1 => 1)
150
+ # i.param1 # raise ParameterValueInvalid
151
+ # i = Piece.new(:param1 => 2, :param2 => 1)
152
+ # i.param1 # => 2
153
+ class Requires < Constraint
154
+ attr_reader :dependencies
155
+ attr_reader :condition
156
+
157
+ def initialize(dependencies)
158
+ @dependencies = dependencies
159
+ @condition = method(:not_nil?)
160
+ end
161
+
162
+ def if(*params, &blk)
163
+ if params.empty?
164
+ @condition = blk
165
+ else
166
+ test = params.first
167
+ @condition = ->(v) { v == test }
168
+ end
169
+
170
+ self
171
+ end
172
+
173
+ protected
174
+
175
+ def not_nil?(value)
176
+ !value.nil?
177
+ end
178
+
179
+ def check(parameter, value, instance)
180
+ return unless instance.instance_exec(value, &condition)
181
+
182
+ dependencies.each do |pname|
183
+ next unless instance.send(pname).nil?
184
+
185
+ raise(
186
+ "#{pname} is required when #{parameter.name} value " \
187
+ "is set to #{value.inspect}"
188
+ )
189
+ end
190
+ end
191
+ end
192
+
193
+ ##
194
+ # Check if value satisfies the condition
195
+ #
196
+ # Checks if value satisfies the condition defined in the block
197
+ # which should return true if the condition is met and false if it's
198
+ # not. If value fails the check, an exception will be thrown
199
+ # with attached condition description. The description is a part
200
+ # of constraint definition.
201
+ #
202
+ # The block is evaluated in the functor's invocation context.
203
+ #
204
+ # === Example
205
+ #
206
+ # class Piece
207
+ # include Aws::Templates::Utils::Parametrized
208
+ # parameter :param1,
209
+ # :constraint => satisfies('Mediocre value') { |v| v < 100 }
210
+ # end
211
+ #
212
+ # i = Piece.new(:param2 => 1)
213
+ # i.param1 # => 1
214
+ # i = Piece.new(:param1 => 101)
215
+ # i.param1 # raise ParameterValueInvalid
216
+ class SatisfiesCondition < Constraint
217
+ attr_reader :condition
218
+ attr_reader :description
219
+
220
+ def initialize(description, &cond_block)
221
+ @condition = cond_block
222
+ @description = description
223
+ end
224
+
225
+ protected
226
+
227
+ def check(parameter, value, instance)
228
+ return if value.nil? || instance.instance_exec(value, &condition)
229
+
230
+ raise(
231
+ "#{value.inspect} doesn't satisfy the condition " \
232
+ "#{description} for parameter #{parameter.name}"
233
+ )
234
+ end
235
+ end
236
+
237
+ ##
238
+ # Check if value matches the regular expression
239
+ #
240
+ # Checks if value matches the regular expression. If value doesn't match, an exception
241
+ # will be thrown with attached description of regular expression and value converted to
242
+ # string.
243
+ #
244
+ # === Example
245
+ #
246
+ # class Piece
247
+ # include Aws::Templates::Utils::Parametrized
248
+ # parameter :param1, constraint: matches('A+')
249
+ # end
250
+ #
251
+ # i = Piece.new(:param1 => 'Ask')
252
+ # i.param1 # => 'Ask'
253
+ # i = Piece.new(:param1 => 'Bar')
254
+ # i.param1 # raise ParameterValueInvalid
255
+ class Matches < Constraint
256
+ attr_reader :expression
257
+
258
+ def initialize(rex)
259
+ @expression = Regexp.new(rex)
260
+ end
261
+
262
+ protected
263
+
264
+ def check(parameter, value, _)
265
+ return if value.nil? || (expression =~ value.to_s)
266
+ raise "#{value} doesn't match #{expression} for parameter #{parameter.name}"
267
+ end
268
+ end
269
+
270
+ ##
271
+ # Aggregate constraint
272
+ #
273
+ # It is used to perform checks against a list of constraints-functors
274
+ # or lambdas.
275
+ #
276
+ # === Example
277
+ #
278
+ # class Piece
279
+ # include Aws::Templates::Utils::Parametrized
280
+ # parameter :param1,
281
+ # :constraint => all_of(
282
+ # not_nil,
283
+ # satisfies("Should be moderate") { |v| v < 100 }
284
+ # )
285
+ # end
286
+ #
287
+ # i = Piece.new(:param1 => nil)
288
+ # i.param1 # raise ParameterValueInvalid
289
+ # i = Piece.new(:param1 => 200)
290
+ # i.param1 # raise ParameterValueInvalid with description
291
+ # i = Piece.new(:param1 => 50)
292
+ # i.param1 # => 50
293
+ class AllOf < Constraint
294
+ attr_reader :constraints
295
+
296
+ def initialize(constraints)
297
+ @constraints = constraints
298
+ end
299
+
300
+ protected
301
+
302
+ def check(parameter, value, instance)
303
+ constraints.each do |c|
304
+ instance.instance_exec(parameter, value, &c)
305
+ end
306
+ end
307
+ end
308
+
309
+ ##
310
+ # Creates closure with checker invocation
311
+ #
312
+ # It's an interface method required for Constraint to expose
313
+ # functor properties. It encloses invocation of Constraint check_wrapper
314
+ # method into a closure. The closure itself is executed in the context
315
+ # of Parametrized instance which provides proper set "self" variable.
316
+ #
317
+ # The closure itself accepts 2 parameters:
318
+ # * +parameter+ - the Parameter object which the constraint is evaluated
319
+ # against
320
+ # * +value+ - parameter value to be checked
321
+ # ...where instance is assumed from self
322
+ def to_proc
323
+ constraint = self
324
+
325
+ lambda do |parameter, value|
326
+ constraint.check_wrapper(parameter, value, self)
327
+ end
328
+ end
329
+
330
+ ##
331
+ # Wraps constraint-dependent method
332
+ #
333
+ # It wraps constraint-dependent "check" method into a rescue block
334
+ # to standardize exception type and information provided by failed
335
+ # constraint validation
336
+ # * +parameter+ - the Parameter object which the constraint is evaluated
337
+ # against
338
+ # * +value+ - parameter value to be checked
339
+ # * +instance+ - the instance value is checked for
340
+ def check_wrapper(parameter, value, instance)
341
+ check(parameter, value, instance)
342
+ rescue StandardError
343
+ raise ParameterValueInvalid.new(parameter, instance, value)
344
+ end
345
+
346
+ protected
347
+
348
+ ##
349
+ # Constraint-dependent check
350
+ #
351
+ # * +parameter+ - the Parameter object which the constraint is evaluated
352
+ # against
353
+ # * +value+ - parameter value to be checked
354
+ # * +instance+ - the instance value is checked for
355
+ def check(parameter, value, instance); end
356
+ end
357
+
358
+ ##
359
+ # Syntax sugar for constraints definition
360
+ #
361
+ # It injects the methods as class-scope methods into mixing classes.
362
+ # The methods are factories to create particular type of constraint
363
+ class_scope do
364
+ ##
365
+ # Parameter shouldn't be nil
366
+ #
367
+ # alias for NotNil class
368
+ def not_nil
369
+ Constraint::NotNil.instance
370
+ end
371
+
372
+ ##
373
+ # Parameter value should be in enumeration
374
+ #
375
+ # alias for Enum class
376
+ def enum(*items)
377
+ Constraint::Enum.new(items.flatten)
378
+ end
379
+
380
+ ##
381
+ # Parameter value should satisfy all specified constraints
382
+ #
383
+ # alias for AllOf class
384
+ def all_of(*constraints)
385
+ Constraint::AllOf.new(constraints)
386
+ end
387
+
388
+ ##
389
+ # Requires presence of the parameters if condition is satisfied
390
+ #
391
+ # alias for Requires class
392
+ def requires(*dependencies)
393
+ Constraint::Requires.new(dependencies)
394
+ end
395
+
396
+ ##
397
+ # Constraint depends on value
398
+ #
399
+ # alias for DependsOnValue class
400
+ def depends_on_value(selector)
401
+ Constraint::DependsOnValue.new(selector)
402
+ end
403
+
404
+ ##
405
+ # Constraint should satisfy the condition
406
+ #
407
+ # alias for SatisfiesCondition class
408
+ def satisfies(description, &cond_block)
409
+ Constraint::SatisfiesCondition.new(description, &cond_block)
410
+ end
411
+
412
+ ##
413
+ # Value should match the regular experession
414
+ #
415
+ # alias for Matches
416
+ def matches(rex)
417
+ Constraint::Matches.new(rex)
418
+ end
419
+ end
420
+ end
421
+ end
422
+ end
423
+ end