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,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
|