cloud-templates 0.1.0

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