cloud-templates 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,140 @@
|
|
1
|
+
require 'aws/templates/utils/parametrized'
|
2
|
+
require 'aws/templates/utils/parametrized/getters'
|
3
|
+
require 'aws/templates/utils/parametrized/constraints'
|
4
|
+
require 'aws/templates/utils/default'
|
5
|
+
require 'aws/templates/utils/options'
|
6
|
+
require 'aws/templates/utils/dependent'
|
7
|
+
|
8
|
+
module Aws
|
9
|
+
module Templates
|
10
|
+
##
|
11
|
+
# Basic artifact
|
12
|
+
#
|
13
|
+
# "Artifact" in the terminology of the framework is an entity which
|
14
|
+
# can represent any parametrizable object type from any problem domain
|
15
|
+
# possible. For instance, for CloudFormation, artifacts are resources
|
16
|
+
# in a CFN template, the template itself
|
17
|
+
#
|
18
|
+
# If your target domain is infrastructure orchestration, artifact is
|
19
|
+
# usually a single entity such as S3 volume, ASG, DynamoDB table, etc.
|
20
|
+
# However, the notion of artifact is not particularly fixed since the
|
21
|
+
# framework can be used for different purposes including documents
|
22
|
+
# generation and general templating tasks. Basically, artifact is a
|
23
|
+
# single parametrizable entity or a step in input hash
|
24
|
+
# transformations.
|
25
|
+
#
|
26
|
+
# Artifact classes work together with meta-programming mechanisms in
|
27
|
+
# Ruby language enabling artifacts inheritance in the natural way
|
28
|
+
# using simple Ruby classes. The feature is useful when you have a
|
29
|
+
# group of artifacts which share the same basic parameters but differ
|
30
|
+
# in details.
|
31
|
+
#
|
32
|
+
# The central part in the framework is played by processed hash. All
|
33
|
+
# mechanisms are based on simple ad-hoc merging rules which are
|
34
|
+
# described at merge method but basis can be described as following:
|
35
|
+
# each superclass initializer accepts children class processed hash
|
36
|
+
# as input hash and the hash is processed recursively through
|
37
|
+
# class hierarchy. Old values are newer removed by default so the
|
38
|
+
# whole process is no more than continuous hash expansion.
|
39
|
+
#
|
40
|
+
# class Piece < Artifact
|
41
|
+
# default { { output: options[:param] } }
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# class ConcretePiece < Piece
|
45
|
+
# default param: 'Came from child'
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# Piece.new(a: 1, b: 2).options.to_hash # => { a: 1, b: 2, output: nil }
|
49
|
+
# ConcretePiece.new(a: 1, b: 2).options.to_hash
|
50
|
+
# # => { a: 1, b: 2, output: 'Came from child', param: 'Came from child' }
|
51
|
+
#
|
52
|
+
# Also, as one of the peculiarities of the framework, you can override
|
53
|
+
# any auto-generated parameter with input hash if they have the same
|
54
|
+
# name/path.
|
55
|
+
class Artifact
|
56
|
+
include Utils::Default
|
57
|
+
include Utils::Parametrized
|
58
|
+
include Utils::Dependent
|
59
|
+
|
60
|
+
attr_accessor :options
|
61
|
+
|
62
|
+
def self.getter
|
63
|
+
as_is
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.to_s
|
67
|
+
return super unless name.nil?
|
68
|
+
"<Subclass of (#{superclass}) with features #{features}>"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.features
|
72
|
+
@features ||= ancestors.take_while { |mod| mod != superclass }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Create new child class with mixins
|
76
|
+
#
|
77
|
+
# The class method is useful when you want to mix-in some behavior adjustments
|
78
|
+
# without creating a new named class. For instance when you want to mix-in
|
79
|
+
# some defaults into class and instantiate a few instances out of that.
|
80
|
+
def self.featuring(*modules)
|
81
|
+
return self if modules.empty?
|
82
|
+
|
83
|
+
modules.inject(Class.new(self)) do |klass, mod|
|
84
|
+
klass.send(:include, mod)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Artifact's label
|
90
|
+
#
|
91
|
+
# All artifacts have labels assigned to them to simplify reverse
|
92
|
+
# look-up while linking dependencies. Interpretation of this field is purely
|
93
|
+
# application-specific.
|
94
|
+
default label: proc { object_id }
|
95
|
+
|
96
|
+
parameter :label, description: 'Artifact\'s label', constraint: not_nil
|
97
|
+
|
98
|
+
##
|
99
|
+
# Artifact's root
|
100
|
+
#
|
101
|
+
# A root is an object which bundles artifacts into common rendering group helping to find
|
102
|
+
# disconnected pieces of dependency graph. If two artifacts have different roots they
|
103
|
+
# definitelly belong to different graphs.
|
104
|
+
default root: proc { object_id }
|
105
|
+
|
106
|
+
def root
|
107
|
+
options[:root]
|
108
|
+
end
|
109
|
+
|
110
|
+
##
|
111
|
+
# Artifact's parent
|
112
|
+
#
|
113
|
+
# Artifacts can be organized into a hierarchy of composition. This field points back to the
|
114
|
+
# artifact's parent.
|
115
|
+
parameter :parent, description: 'Artifact parent'
|
116
|
+
|
117
|
+
##
|
118
|
+
# Create a new artifact
|
119
|
+
#
|
120
|
+
# Artifact constructor. If you want to override it you might probably
|
121
|
+
# want to look into default first. All default values specified in the
|
122
|
+
# class definition and all its ancestors are processed and merged with
|
123
|
+
# options so it contains fully-processed hash.
|
124
|
+
#
|
125
|
+
# The algorithm of the processing is the following:
|
126
|
+
# * merge defaults hash with passed options; options take precedence
|
127
|
+
# * merge the hash with default calculations return results; calculations output
|
128
|
+
# takes preference
|
129
|
+
# * pass resulting hash to superclass initializer
|
130
|
+
# * merge resulting hash with options; options take preference
|
131
|
+
#
|
132
|
+
# * +params+ - input parameters hash to be used during following
|
133
|
+
# hash transformations and expansions.
|
134
|
+
def initialize(params)
|
135
|
+
@options = Utils::Options.new(params)
|
136
|
+
process_options(params)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'aws/templates/artifact'
|
2
|
+
require 'aws/templates/utils/artifact_storage'
|
3
|
+
require 'aws/templates/utils/contextualized'
|
4
|
+
require 'aws/templates/utils/dependency'
|
5
|
+
|
6
|
+
module Aws
|
7
|
+
module Templates
|
8
|
+
##
|
9
|
+
# Composite
|
10
|
+
#
|
11
|
+
# Composite is an artifact which contain other artifacts and provide
|
12
|
+
# DSL syntax sugar to define it
|
13
|
+
#
|
14
|
+
# A composite can represent complex entities as CloudFormation
|
15
|
+
# stacks which can contain settings and infrastructure artifacts inside it.
|
16
|
+
# However, it's still a single entity which may be versioned as a
|
17
|
+
# whole and can represent the current state of deployed application.
|
18
|
+
#
|
19
|
+
# Composite is still an artifact and has all methods inherited so
|
20
|
+
# besides grouping different artifacts alltogether you can process
|
21
|
+
# input parameters too.
|
22
|
+
#
|
23
|
+
# Composite is a recursive structure as a result of being an artifact
|
24
|
+
# so you can construct arbitrary deep hierarchies of objects. Also it
|
25
|
+
# supports inheritance as artifact does. So every component defined in
|
26
|
+
# the parent class will be initialized properly in all children too.
|
27
|
+
class Composite < Artifact
|
28
|
+
include Aws::Templates::Utils::Contextualized
|
29
|
+
|
30
|
+
# propagate root to the components and set itself as the parent
|
31
|
+
contextualize filter(:add, :root) & (filter(:override) { { parent: self } })
|
32
|
+
|
33
|
+
##
|
34
|
+
# Dictionary of artifacts and their labels the composite is
|
35
|
+
# consisting of
|
36
|
+
#
|
37
|
+
# Accessor returning dictionary of artifacts currently residing in
|
38
|
+
# composite instance with labels as keys
|
39
|
+
def artifacts
|
40
|
+
@artifacts ||= Utils::ArtifactStorage.new
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Shortcut for accessing artifacts by their labels
|
45
|
+
#
|
46
|
+
# The method returns either stored artifact object or throws a
|
47
|
+
# descriptive exception.
|
48
|
+
def [](artifact_label)
|
49
|
+
unless artifacts.key?(artifact_label)
|
50
|
+
raise "There is no artifact #{artifact_label}" \
|
51
|
+
" in composite #{label}"
|
52
|
+
end
|
53
|
+
|
54
|
+
artifacts[artifact_label].as_a_dependency.to_self
|
55
|
+
end
|
56
|
+
|
57
|
+
def []=(artifact_label, artifact_object)
|
58
|
+
if artifacts.key?(artifact_label)
|
59
|
+
if artifacts[artifact_label] != artifact_object.object
|
60
|
+
raise "Artifact #{artifact_label} is already present " \
|
61
|
+
"in composite #{label}"
|
62
|
+
end
|
63
|
+
else
|
64
|
+
artifacts[artifact_label] = artifact_object.object
|
65
|
+
end
|
66
|
+
|
67
|
+
artifact_object.as_a_dependency.to_self
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Artifacts definition block for DSL
|
72
|
+
#
|
73
|
+
# An element of the framework DSL. Allows you to define
|
74
|
+
# composite's artifacts declaratively with using standard language
|
75
|
+
# features.
|
76
|
+
def self.components(*args, &blk)
|
77
|
+
define_method(:create_components) do
|
78
|
+
super()
|
79
|
+
instance_exec(*args, &blk)
|
80
|
+
end
|
81
|
+
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Add components into the composite's instance
|
87
|
+
#
|
88
|
+
# Analog of class-level "components" method to add components after
|
89
|
+
# artifact creation when using class-level definitions are not
|
90
|
+
# appropriate
|
91
|
+
def components(&blk)
|
92
|
+
instance_exec(&blk)
|
93
|
+
self
|
94
|
+
end
|
95
|
+
|
96
|
+
##
|
97
|
+
# Syntax sugar to create composite classes on spot
|
98
|
+
#
|
99
|
+
# Create a new child class of the current class and executes a
|
100
|
+
# block in the context of the class object optionally passing a list
|
101
|
+
# of arguments to it.
|
102
|
+
def self.for(*args, &blk)
|
103
|
+
klass = Class.new(self)
|
104
|
+
klass.instance_eval(*args, &blk) unless blk.nil?
|
105
|
+
klass
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Artifact definition constructor in DSL
|
110
|
+
#
|
111
|
+
# Defines a single artifact in composite's definition block. This
|
112
|
+
# method was designed to be used inside of composite block but you
|
113
|
+
# can use it elsewhere else applied on a class instance.
|
114
|
+
#
|
115
|
+
# * +type+ - artifact type (class)
|
116
|
+
# * +params+ - optional map of artifact options
|
117
|
+
# * +blk+ - a block which will be passed to artifacts constructor;
|
118
|
+
# applications may vary but particular one is adding
|
119
|
+
# artifacts into composite during instantiation
|
120
|
+
def artifact(type, params = nil, &blk)
|
121
|
+
artifact_object = create_artifact_object(type, params, &blk)
|
122
|
+
self[artifact_object.label] = artifact_object
|
123
|
+
artifact_object.as_a_dependency.to_self
|
124
|
+
end
|
125
|
+
|
126
|
+
##
|
127
|
+
# Put labels on the artifact
|
128
|
+
#
|
129
|
+
# Put the artifact into the artifact storage under arbitrary aliases.
|
130
|
+
#
|
131
|
+
# * +artifact_object+ - artifact object to put
|
132
|
+
# * +labels+ - labels to assign to the artifact
|
133
|
+
def label_as(artifact_object, *labels)
|
134
|
+
labels.flatten.each do |artifact_label|
|
135
|
+
self[artifact_label] = artifact_object
|
136
|
+
end
|
137
|
+
|
138
|
+
artifact_object
|
139
|
+
end
|
140
|
+
|
141
|
+
##
|
142
|
+
# Provisions parameters and initializes nested artifacts
|
143
|
+
def initialize(*params, &blk)
|
144
|
+
super(*params)
|
145
|
+
create_components
|
146
|
+
instance_exec(&blk) if blk
|
147
|
+
end
|
148
|
+
|
149
|
+
##
|
150
|
+
# Find artifacts by criteria
|
151
|
+
#
|
152
|
+
# The method allows flexible introspection of the artifacts
|
153
|
+
# enclosed into the composite's storage. The method is just a proxy
|
154
|
+
# for the storage method with the same name
|
155
|
+
#
|
156
|
+
# * +search_params+ - map of search parameters:
|
157
|
+
# ** +recursive+ - if true, search will be performed recusrsively
|
158
|
+
# in nested composites
|
159
|
+
# ** +label+ - search for artifacts which have the label
|
160
|
+
# ** +parameters+ - search for artifacts which have specified
|
161
|
+
# parameters values; it's a multi-level map so
|
162
|
+
# you can check for nested values also
|
163
|
+
def search(search_params = {})
|
164
|
+
artifacts.search(search_params)
|
165
|
+
end
|
166
|
+
|
167
|
+
protected
|
168
|
+
|
169
|
+
def create_components; end
|
170
|
+
|
171
|
+
private
|
172
|
+
|
173
|
+
def create_artifact_object(type, params, &blk)
|
174
|
+
type.new(options.filter(&contextualize(params.to_filter)), &blk)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
module Aws
|
2
|
+
module Templates
|
3
|
+
##
|
4
|
+
# Parameter definition exception
|
5
|
+
#
|
6
|
+
# Meta-programming exception related to Parametrized DSL
|
7
|
+
class ParametrizedDSLError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
# Parameter already exists
|
12
|
+
#
|
13
|
+
# If you're trying to define a parameter in a parametrized artifact
|
14
|
+
# and this parameter either already defined for the class or defined
|
15
|
+
# in an ancestor.
|
16
|
+
class ParameterAlreadyExist < ParametrizedDSLError
|
17
|
+
# Parameter object of the conflicting parameter
|
18
|
+
attr_reader :parameter
|
19
|
+
|
20
|
+
def initialize(target_parameter)
|
21
|
+
@parameter = target_parameter
|
22
|
+
super(
|
23
|
+
"Parameter #{target_parameter.name} already in " \
|
24
|
+
"#{target_parameter.klass}."
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# Invalid parameter specification hash
|
31
|
+
#
|
32
|
+
# If unknown option is passed in a parameter description block
|
33
|
+
class ParameterSpecificationIsInvalid < ParametrizedDSLError
|
34
|
+
# Parameter object faulty options were specified for
|
35
|
+
attr_reader :parameter
|
36
|
+
|
37
|
+
# Options unknown to Parametrized
|
38
|
+
attr_reader :options
|
39
|
+
|
40
|
+
def initialize(target_parameter, opts)
|
41
|
+
@parameter = target_parameter
|
42
|
+
@options = opts
|
43
|
+
|
44
|
+
super(
|
45
|
+
'Unsupported options are in specification for ' \
|
46
|
+
"parameter #{target_parameter.name} in class " \
|
47
|
+
"#{target_parameter.klass} : #{opts}"
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# A regular method and a parameter have the same name in a class
|
54
|
+
#
|
55
|
+
# A parameter was specified with the same name as exsiting method
|
56
|
+
# in the class or in an ancestor of the class.
|
57
|
+
class ParameterMethodNameConflict < ParametrizedDSLError
|
58
|
+
# Method object of the method specified
|
59
|
+
attr_reader :method_object
|
60
|
+
|
61
|
+
def initialize(target_method)
|
62
|
+
@method_object = target_method
|
63
|
+
|
64
|
+
super(
|
65
|
+
"Parameter name #{target_method.name} clashes with a method name in " \
|
66
|
+
"#{target_method.owner.name}"
|
67
|
+
)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
##
|
72
|
+
# View was not found for the object
|
73
|
+
#
|
74
|
+
# View map was checked and there is no appropriate view class
|
75
|
+
# for the object class found in the registry.
|
76
|
+
class ViewNotFound < RuntimeError
|
77
|
+
# Instance of the object class render lookup was performed for
|
78
|
+
attr_reader :instance
|
79
|
+
|
80
|
+
def message
|
81
|
+
"Can't find any view for #{instance} of class #{instance.class}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize(target_instance)
|
85
|
+
super()
|
86
|
+
@instance = target_instance
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Parameter exception
|
92
|
+
#
|
93
|
+
# Happens during runtime if an error happens during parameter
|
94
|
+
# evaluation
|
95
|
+
class ParameterException < RuntimeError
|
96
|
+
# Parameter object
|
97
|
+
attr_reader :parameter
|
98
|
+
|
99
|
+
def message
|
100
|
+
cause.nil? ? super : "#{super} : #{cause.message}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def initialize(target_parameter, custom_message)
|
104
|
+
@parameter = target_parameter
|
105
|
+
super(custom_message)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
##
|
110
|
+
# If something happens during parameter calculation
|
111
|
+
class NestedParameterException < ParameterException
|
112
|
+
def initialize(target_parameter)
|
113
|
+
super(
|
114
|
+
target_parameter,
|
115
|
+
'Exception was thrown by nested parameter while calculating ' \
|
116
|
+
"#{target_parameter.name} (#{target_parameter.description})"
|
117
|
+
)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# A value failed constraints
|
123
|
+
class ParameterValueInvalid < ParameterException
|
124
|
+
attr_reader :value
|
125
|
+
attr_reader :object
|
126
|
+
|
127
|
+
def initialize(target_parameter, target_object, target_value)
|
128
|
+
@value = target_value
|
129
|
+
@object = target_object
|
130
|
+
super(
|
131
|
+
target_parameter,
|
132
|
+
message_text(target_parameter, target_object, target_value)
|
133
|
+
)
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def message_text(target_parameter, target_object, target_value)
|
139
|
+
message = "Value '(#{target_value.inspect})' violates constraints specified for " \
|
140
|
+
"#{target_parameter.name} (#{target_parameter.description}) in " \
|
141
|
+
"#{target_parameter.klass}"
|
142
|
+
|
143
|
+
unless target_object.class == target_parameter.klass
|
144
|
+
message += " and inherited by #{target_object.class}"
|
145
|
+
end
|
146
|
+
|
147
|
+
message
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Getter is not specified
|
153
|
+
#
|
154
|
+
# Getter wasn't specified neither for the individual parameter nor for the mixing instance nor
|
155
|
+
# for its class.
|
156
|
+
class ParameterGetterIsNotDefined < ParameterException
|
157
|
+
def initialize(target_parameter)
|
158
|
+
super(
|
159
|
+
target_parameter,
|
160
|
+
"Can't find getter for #{target_parameter.name} (#{target_parameter.description}): " \
|
161
|
+
'a getter should be attached either to the parameter or the instance ' \
|
162
|
+
'or the instance class'
|
163
|
+
)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
##
|
168
|
+
# Options exception
|
169
|
+
#
|
170
|
+
# The parent of all exceptions Options method can throw
|
171
|
+
class OptionError < ArgumentError
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Recursive value is expected
|
176
|
+
#
|
177
|
+
# Value passed doesn't not support "recursive" contract. See Utils.recursive?
|
178
|
+
class OptionShouldBeRecursive < OptionError
|
179
|
+
attr_reader :value
|
180
|
+
|
181
|
+
def initialize(value)
|
182
|
+
@value = value
|
183
|
+
super("Value #{value} is not a recursive data structure")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
##
|
188
|
+
# Deleted branch detected
|
189
|
+
#
|
190
|
+
# While traversing Options layers for a value, deleted branch marker was discovered.
|
191
|
+
class OptionValueDeleted < OptionError
|
192
|
+
attr_reader :path
|
193
|
+
|
194
|
+
def initialize(path)
|
195
|
+
@path = path
|
196
|
+
super(
|
197
|
+
"Deleted value was detected while traversing path. The path left untraversed: #{path}"
|
198
|
+
)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
##
|
203
|
+
# Scalar is met while traversing Options path
|
204
|
+
#
|
205
|
+
# Path is not empty yet but we can't traverse deeper because the current value is a scalar
|
206
|
+
class OptionScalarOnTheWay < OptionError
|
207
|
+
attr_reader :value
|
208
|
+
attr_reader :path
|
209
|
+
|
210
|
+
def initialize(value, path)
|
211
|
+
@value = value
|
212
|
+
@path = path
|
213
|
+
|
214
|
+
super(
|
215
|
+
"Value #{value} is not a recursive data structure and we have still #{path} keys " \
|
216
|
+
'to look-up'
|
217
|
+
)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'aws/templates/exceptions'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module Templates
|
5
|
+
module Render
|
6
|
+
##
|
7
|
+
# View registry
|
8
|
+
#
|
9
|
+
# View registries encapsulate differerent ways of transforming
|
10
|
+
# your artifacts into a domain-specific output.
|
11
|
+
# In nutshell, they are registries of View classes which are able
|
12
|
+
# to lookup proper View for object instance passed to it.
|
13
|
+
class Registry
|
14
|
+
# View registry accessor
|
15
|
+
def registry
|
16
|
+
@registry ||= {}
|
17
|
+
end
|
18
|
+
|
19
|
+
##
|
20
|
+
# Register pair artifact-view
|
21
|
+
#
|
22
|
+
# Invoked from inside of a View class at definition of the link
|
23
|
+
# between the view class and an artifact
|
24
|
+
# * +artifact+ - artifact class the view claims to be able to render
|
25
|
+
# * +render+ - view class
|
26
|
+
def register(artifact, view)
|
27
|
+
registry[artifact] = view
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Can object be rendered
|
32
|
+
#
|
33
|
+
# Returns true if the object passed can be rendered by one of the views in the registry
|
34
|
+
def can_render?(instance)
|
35
|
+
instance.class.ancestors.any? { |ancestor| registry.include?(ancestor) }
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Lookup a view for the artifact
|
40
|
+
#
|
41
|
+
# Searches registry for artifact's class and all its ancestors
|
42
|
+
# in the registry and returns the closest matching view
|
43
|
+
# * +instance+ - artifact instance to render
|
44
|
+
# * +params+ - assigned parameters; it can be arbitrary value;
|
45
|
+
# it is propagated to selected render
|
46
|
+
def view_for(instance, params = nil)
|
47
|
+
return instance if instance.respond_to?(:to_rendered)
|
48
|
+
|
49
|
+
mod = instance.class.ancestors.find do |ancestor|
|
50
|
+
registry.include?(ancestor)
|
51
|
+
end
|
52
|
+
|
53
|
+
raise ViewNotFound.new(instance) unless mod
|
54
|
+
|
55
|
+
registry[mod].new(instance, params)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|