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.
- checksums.yaml +7 -0
- data/.rubocop.yml +29 -0
- data/.simplecov +6 -0
- data/Gemfile +2 -0
- data/LICENSE +201 -0
- data/NOTICE +13 -0
- data/README.md +124 -0
- data/Rakefile +27 -0
- data/cloud-templates.gemspec +27 -0
- data/examples/lib/user_directory/artifacts/catalogized.rb +11 -0
- data/examples/lib/user_directory/artifacts/group.rb +37 -0
- data/examples/lib/user_directory/artifacts/ided.rb +11 -0
- data/examples/lib/user_directory/artifacts/organization.rb +17 -0
- data/examples/lib/user_directory/artifacts/pathed.rb +22 -0
- data/examples/lib/user_directory/artifacts/person.rb +20 -0
- data/examples/lib/user_directory/artifacts/team.rb +31 -0
- data/examples/lib/user_directory/artifacts/unit.rb +24 -0
- data/examples/lib/user_directory/artifacts/user.rb +29 -0
- data/examples/lib/user_directory/render/etc/artifact_view.rb +15 -0
- data/examples/lib/user_directory/render/etc/composite_view.rb +26 -0
- data/examples/lib/user_directory/render/etc/group_view.rb +23 -0
- data/examples/lib/user_directory/render/etc/person_view.rb +19 -0
- data/examples/lib/user_directory/render/etc/registry.rb +33 -0
- data/examples/lib/user_directory/render/etc/user_view.rb +35 -0
- data/examples/lib/user_directory/render/etc.rb +3 -0
- data/examples/lib/user_directory/render/ldap/artifact_view.rb +27 -0
- data/examples/lib/user_directory/render/ldap/composite_view.rb +32 -0
- data/examples/lib/user_directory/render/ldap/group_view.rb +28 -0
- data/examples/lib/user_directory/render/ldap/organization_view.rb +26 -0
- data/examples/lib/user_directory/render/ldap/person_view.rb +39 -0
- data/examples/lib/user_directory/render/ldap/registry.rb +16 -0
- data/examples/lib/user_directory/render/ldap/unit_view.rb +26 -0
- data/examples/lib/user_directory/render/ldap/user_view.rb +39 -0
- data/examples/lib/user_directory/render/ldap.rb +3 -0
- data/examples/lib/user_directory/utils.rb +18 -0
- data/examples/lib/user_directory.rb +23 -0
- data/examples/lib_path.rb +2 -0
- data/examples/spec/spec_helper.rb +1 -0
- data/examples/spec/user_directory_spec.rb +568 -0
- data/lib/aws/templates/artifact.rb +140 -0
- data/lib/aws/templates/composite.rb +178 -0
- data/lib/aws/templates/exceptions.rb +221 -0
- data/lib/aws/templates/render/registry.rb +60 -0
- data/lib/aws/templates/render/utils/base_type_views.rb +131 -0
- data/lib/aws/templates/render/view.rb +127 -0
- data/lib/aws/templates/render.rb +72 -0
- data/lib/aws/templates/utils/artifact_storage.rb +141 -0
- data/lib/aws/templates/utils/contextualized/filters.rb +437 -0
- data/lib/aws/templates/utils/contextualized/hash.rb +13 -0
- data/lib/aws/templates/utils/contextualized/nil.rb +13 -0
- data/lib/aws/templates/utils/contextualized/proc.rb +13 -0
- data/lib/aws/templates/utils/contextualized.rb +113 -0
- data/lib/aws/templates/utils/default.rb +185 -0
- data/lib/aws/templates/utils/dependency/enumerable.rb +13 -0
- data/lib/aws/templates/utils/dependency/object.rb +46 -0
- data/lib/aws/templates/utils/dependency.rb +121 -0
- data/lib/aws/templates/utils/dependent.rb +28 -0
- data/lib/aws/templates/utils/inheritable.rb +52 -0
- data/lib/aws/templates/utils/late_bound.rb +89 -0
- data/lib/aws/templates/utils/memoized.rb +27 -0
- data/lib/aws/templates/utils/named.rb +19 -0
- data/lib/aws/templates/utils/options.rb +279 -0
- data/lib/aws/templates/utils/parametrized/constraints.rb +423 -0
- data/lib/aws/templates/utils/parametrized/getters.rb +293 -0
- data/lib/aws/templates/utils/parametrized/guarded.rb +32 -0
- data/lib/aws/templates/utils/parametrized/mapper.rb +73 -0
- data/lib/aws/templates/utils/parametrized/nested.rb +72 -0
- data/lib/aws/templates/utils/parametrized/transformations.rb +660 -0
- data/lib/aws/templates/utils/parametrized.rb +240 -0
- data/lib/aws/templates/utils.rb +219 -0
- data/lib/aws/templates.rb +16 -0
- data/spec/aws/templates/artifact_spec.rb +161 -0
- data/spec/aws/templates/composite_spec.rb +361 -0
- data/spec/aws/templates/render/utils/base_type_views_spec.rb +104 -0
- data/spec/aws/templates/render_spec.rb +62 -0
- data/spec/aws/templates/utils/as_named_spec.rb +31 -0
- data/spec/aws/templates/utils/contextualized/filters_spec.rb +108 -0
- data/spec/aws/templates/utils/contextualized_spec.rb +115 -0
- data/spec/aws/templates/utils/late_bound_spec.rb +52 -0
- data/spec/aws/templates/utils/options_spec.rb +67 -0
- data/spec/aws/templates/utils/parametrized/constraint_spec.rb +175 -0
- data/spec/aws/templates/utils/parametrized/getters_spec.rb +139 -0
- data/spec/aws/templates/utils/parametrized/transformation_spec.rb +314 -0
- data/spec/aws/templates/utils/parametrized_spec.rb +241 -0
- data/spec/spec_helper.rb +6 -0
- 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
|