Class: Mimi::Core::Manifest
- Inherits:
-
Object
- Object
- Mimi::Core::Manifest
- Defined in:
- lib/mimi/core/manifest.rb
Overview
Manifest represents a set of definitions of configurable parameters.
It is a way of formally declaring which configurable parameters are accepted by a Mimi module, application etc. A Manifest object is also used to validate passed set of raw values, apply rules and produce a set of parsed configurable parameter values.
Manifests are constructed from a Hash representation, following some structure. Configurable parameter definitions are specified in the manifest Hash as key-value pairs, where key is the name of the configurable parameter, and value is a Hash with parameter properties.
Example:
manifest = Mimi::Core::Manifest.new(
var1: {}, # minimalistic configurable parameter definition, all properties are default
var2: {}
)
The properties that can be defined for a configurable parameter are:
:desc
(String) -- a human readable description of the parameter (default: nil):type
(Symbol,Array) -- defines the type of the parameter and the type/format of accepted values (default: :string) :default
(Object) -- specified default value indicates that the parameter is optional:hidden
(true,false) -- if set to true, omits the parameter from the application's combined manifest:const
(true,false) -- if set to true, this configurable parameter cannot be changed and always equals to its default value which must be specified
Configurable parameter properties
:desc =>
Default: nil
Allows to specify a human readable description for a configurable parameter.
Example:
manifest = Mimi::Core::Manifest.new(
var1: {
desc: 'My configurable parameter 1'
}
}
:type => >
Default: :string
Defines the type of the parameter and accepted values. Recognised types are:
:string
-- accepts any value, presents it as aString
:integer
-- accepts anyInteger
value or a validString
representation of integer:decimal
-- acceptsBigDecimal
value or a validString
representation of a decimal number:boolean
-- acceptstrue
orfalse
or string literals'true'
,'false'
:json
-- accepts a string with valid JSON, presents it as a parsed object (literal, Array or Hash)Array<String>
-- defines enumeration of values, e.g.['debug', 'info', 'warn', 'error']
; only values enumerated in the list are accepted, presented asString
Example:
manifest = Mimi::Core::Manifest.new(
var1: {
type: :integer,
default: 1
},
var2: {
type: :decimal,
default: '0.01'
},
var3: {
type: ['debug', 'info', 'warn', 'error'],
default: 'info'
}
}
:default =>
Default: nil
...
:hidden =>
Default: false
...
:const =>
Default: false
...
Example: manifest_hash = { var1: { desc: 'My var 1', type: :string,
}
}
Constant Summary collapse
- ALLOWED_TYPES =
%w[string integer decimal boolean json].freeze
Class Method Summary collapse
-
.from_yaml(yaml) ⇒ Mimi::Core::Manifest
Constructs a Manifest object from a YAML representation.
-
.validate_manifest_hash(manifest_hash) ⇒ Object
Validates a Hash representation of the manifest.
-
.validate_manifest_key_properties(name, properties) ⇒ Object
Validates configurable parameter properties.
Instance Method Summary collapse
-
#apply(values) ⇒ Hash<Symbol,Object>
Accepts the values, performs the validation and applies the manifest, responding with a Hash of parameters and processed values.
-
#initialize(manifest_hash = {}) ⇒ Manifest
constructor
Constructs a new Manifest from its Hash representation.
-
#keys ⇒ Array<Symbol>
Returns a list of configurable parameter names.
-
#merge(another) ⇒ Mimi::Core::Manifest
Returns a copy of current Manifest merged with another Hash or Manifest.
-
#merge!(another) ⇒ Object
Merges current Manifest with another Hash or Manifest, modifies current Manifest in-place.
-
#required?(name) ⇒ true, false
Returns true if the configurable parameter is a required one.
-
#to_h ⇒ Hash
Returns a Hash representation of the Manifest.
-
#to_yaml ⇒ String
Returns a YAML representation of the manifest.
Constructor Details
#initialize(manifest_hash = {}) ⇒ Manifest
Constructs a new Manifest from its Hash representation
132 133 134 135 |
# File 'lib/mimi/core/manifest.rb', line 132 def initialize(manifest_hash = {}) self.class.validate_manifest_hash(manifest_hash) @manifest = manifest_hash_canonical(manifest_hash.deep_dup) end |
Class Method Details
.from_yaml(yaml) ⇒ Mimi::Core::Manifest
Constructs a Manifest object from a YAML representation
345 346 347 348 349 350 351 352 353 |
# File 'lib/mimi/core/manifest.rb', line 345 def self.from_yaml(yaml) manifest_hash = YAML.safe_load(yaml) raise 'Invalid manifest, JSON Object is expected' unless manifest_hash.is_a?(Hash) manifest_hash = manifest_hash.map do |k, v| v = (v || {}).symbolize_keys [k.to_sym, v] end.to_h new(manifest_hash) end |
.validate_manifest_hash(manifest_hash) ⇒ Object
Validates a Hash representation of the manifest
- all keys are symbols
- all configurable parameter properties are valid
294 295 296 297 298 299 300 301 302 |
# File 'lib/mimi/core/manifest.rb', line 294 def self.validate_manifest_hash(manifest_hash) invalid_keys = manifest_hash.keys.reject { |k| k.is_a?(Symbol) } unless invalid_keys.empty? raise ArgumentError, "Invalid manifest keys, Symbols are expected: #{invalid_keys.join(', ')}" end manifest_hash.each { |n, p| validate_manifest_key_properties(n, p) } end |
.validate_manifest_key_properties(name, properties) ⇒ Object
Validates configurable parameter properties
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 |
# File 'lib/mimi/core/manifest.rb', line 309 def self.validate_manifest_key_properties(name, properties) raise 'Hash as properties is expected' unless properties.is_a?(Hash) if properties[:desc] raise ArgumentError, 'String as :desc is expected' unless properties[:desc].is_a?(String) end if properties[:type] if properties[:type].is_a?(Array) if properties[:type].any? { |v| !v.is_a?(String) } raise ArgumentError, 'Array<String> is expected as enumeration :type' end elsif !ALLOWED_TYPES.include?(properties[:type].to_s) raise ArgumentError, "Unrecognised type '#{properties[:type]}'" end end if properties.keys.include?(:hidden) if !properties[:hidden].is_a?(TrueClass) && !properties[:hidden].is_a?(FalseClass) raise ArgumentError, 'Invalid type for :hidden, true or false is expected' end end if properties.keys.include?(:const) if !properties[:const].is_a?(TrueClass) && !properties[:const].is_a?(FalseClass) raise ArgumentError, 'Invalid type for :const, true or false is expected' end end if properties[:const] && !properties.keys.include?(:default) raise ArgumentError, ':default is required if :const is set' end rescue ArgumentError => e raise ArgumentError, "Invalid manifest: invalid properties for '#{name}': #{e}" end |
Instance Method Details
#apply(values) ⇒ Hash<Symbol,Object>
Accepts the values, performs the validation and applies the manifest, responding with a Hash of parameters and processed values.
Performs the type coercion of values to the specified configurable parameter type.
- type: :string, value: anything =>
String
- type: :integer, value:
1
or'1'
=>1
- type: :decimal, value:
1
,1.0 (BigDecimal)
,'1'
or'1.0'
=>1.0 (BigDecimal)
- type: :boolean, value:
true
or'true'
=>true
- type: :json, value:
{ 'id' => 123 }
or'{"id":123}'
=>{ 'id' => 123 }
- type:
['a', 'b', 'c']
, value:'a'
=>'a'
Example:
manifest = Mimi::Core::Manifest.new(
var1: {},
var2: :integer,
var3: :decimal,
var4: :boolean,
var5: :json,
var6: ['a', 'b', 'c']
)
manifest.apply(
var1: 'var1.value',
var2: '2',
var3: '3',
var4: 'false',
var5: '[{"name":"value"}]',
var6: 'c'
)
# =>
# {
# var1: 'var1.value', var2: 2, var3: 3.0, var4: false,
# var5: [{ 'name' => 'value '}], var6: 'c'
# }
If :default
is specified for the parameter and the value is not provided,
the default value is returned, converted to corresponding type, if it is not nil
manifest = Mimi::Core::Manifest.new(var1: { default: nil })
manifest.apply({}) # => { var1: nil }
Values for parameters not defined in the manifest are ignored:
manifest = Mimi::Core::Manifest.new(var1: {})
manifest.apply(var1: '123', var2: '456') # => { var1: '123' }
Configurable parameters defined as :const
cannot be changed by provided values:
manifest = Mimi::Core::Manifest.new(var1: { default: 1, const: true })
manifest.apply(var1: 2) # => { var1: 1 }
If a configurable parameter defined as required in the manifest (has no :default
)
and the provided values have no corresponding key, an ArgumentError is raised:
manifest = Mimi::Core::Manifest.new(var1: {})
manifest.apply({}) # => ArgumentError "Required value for 'var1' is missing"
If a value provided for the configurable parameter is incompatible (different type, wrong format etc), an ArgumentError is raised:
manifest = Mimi::Core::Manifest.new(var1: { type: :integer })
manifest.apply(var1: 'abc') # => ArgumentError "Invalid value provided for 'var1'"
During validation of provided values, all violations are detected and reported in a single ArgumentError:
manifest = Mimi::Core::Manifest.new(var1: { type: :integer }, var2: {})
manifest.apply(var1: 'abc') # =>
# ArgumentError "Invalid value provided for 'var1'. Required value for 'var2' is missing."
280 281 282 283 284 |
# File 'lib/mimi/core/manifest.rb', line 280 def apply(values) raise ArgumentError, 'Hash is expected as values' unless values.is_a?(Hash) validate_values(values) process_values(values) end |
#keys ⇒ Array<Symbol>
Returns a list of configurable parameter names
149 150 151 |
# File 'lib/mimi/core/manifest.rb', line 149 def keys @manifest.keys end |
#merge(another) ⇒ Mimi::Core::Manifest
Returns a copy of current Manifest merged with another Hash or Manifest
178 179 180 181 182 183 184 185 186 187 |
# File 'lib/mimi/core/manifest.rb', line 178 def merge(another) if !another.is_a?(Mimi::Core::Manifest) && !another.is_a?(Hash) raise ArgumentError 'Another Mimi::Core::Manifest or Hash is expected' end another_hash = another.is_a?(Hash) ? another.deep_dup : another.to_h.deep_dup new_manifest_hash = @manifest.deep_merge(another_hash) new_manifest_hash = manifest_hash_canonical(new_manifest_hash) self.class.validate_manifest_hash(new_manifest_hash) self.class.new(new_manifest_hash) end |
#merge!(another) ⇒ Object
Merges current Manifest with another Hash or Manifest, modifies current Manifest in-place
169 170 171 |
# File 'lib/mimi/core/manifest.rb', line 169 def merge!(another) @manifest = merge(another).to_h end |
#required?(name) ⇒ true, false
Returns true if the configurable parameter is a required one
158 159 160 161 162 163 |
# File 'lib/mimi/core/manifest.rb', line 158 def required?(name) raise ArgumentError, 'Symbol is expected as the parameter name' unless name.is_a?(Symbol) props = @manifest[name] return false unless props # parameter is not required if it is not declared !props.keys.include?(:default) end |
#to_h ⇒ Hash
Returns a Hash representation of the Manifest
141 142 143 |
# File 'lib/mimi/core/manifest.rb', line 141 def to_h @manifest end |
#to_yaml ⇒ String
Returns a YAML representation of the manifest
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/mimi/core/manifest.rb', line 359 def to_yaml out = [] to_h.each do |k, v| next if v[:hidden] out << "#{k}:" vy = v[:desc].nil? ? '# nil' : v[:desc].inspect # value to yaml out << " desc: #{vy}" if v.key?(:desc) && !v[:desc].empty? if v[:type].is_a?(Array) out << ' type:' v[:type].each { |t| out << " - #{t}" } elsif v[:type] != :string out << " type: #{v[:type]}" end out << ' const: true' if v[:const] vy = v[:default].nil? ? '# nil' : v[:default].inspect # value to yaml out << " default: #{vy}" if v.key?(:default) out << '' end out.join("\n") end |