lib/rasn1/model.rb in rasn1-0.11.0 vs lib/rasn1/model.rb in rasn1-0.12.0
- old
+ new
@@ -55,20 +55,46 @@
# or like this:
# record2 = Record2.new(rented: true, a_record: { id: 65537, room: 43 })
#
# == Delegation
# {Model} may delegate some methods to its root element. Thus, if root element
- # is, for example, a {TypeInts::Choice}, model may delegate +#chosen+ and +#chosen_value+.
+ # is, for example, a {Types::Choice}, model may delegate +#chosen+ and +#chosen_value+.
#
# All methods defined by root may be delegated by model, unless model also defines
# this method.
# @author Sylvain Daubert
- class Model
+ # @author adfoster-r7 ModelValidationError, track source location for dynamic class methods
+ class Model # rubocop:disable Metrics/ClassLength
# @private
- Elem = Struct.new(:name, :proc_or_class, :content)
+ Elem = Struct.new(:name, :proc_or_class, :content) do
+ def initialize(name, proc_or_class, content)
+ if content.is_a?(Array)
+ duplicate_names = find_all_duplicate_names(content.map(&:name) + [name])
+ raise ModelValidationError, "Duplicate name #{duplicate_names.first} found" if duplicate_names.any?
+ end
+ super
+ end
+
+ private
+
+ # @param [Array<String>] names
+ # @return [Array<String>] The duplicate names found in the array
+ def find_all_duplicate_names(names)
+ names.group_by { |name| name }
+ .select { |_name, values| values.length > 1 }
+ .keys
+ end
+ end
+
# @private
+ WrapElem = Struct.new(:element, :options) do
+ def name
+ "#{element.name}_wrapper"
+ end
+ end
+
module Accel
# @return [Hash]
attr_reader :options
# Use another model in this model
@@ -77,10 +103,19 @@
# @return [Elem]
def model(name, model_klass)
@root = Elem.new(name, model_klass, nil)
end
+ # Use a {Wrapper} around a {Types::Base} or a {Model} object
+ # @param [Types::Base,Model] element
+ # @param [Hash] options
+ # @return [WrapElem]
+ # @since 0.12
+ def wrapper(element, options={})
+ @root = WrapElem.new(element, options)
+ end
+
# Update options of root element.
# May be used when subclassing.
# class Model1 < RASN1::Model
# sequence :seq, implicit: 0,
# content: [bool(:bool), integer(:int)]
@@ -90,12 +125,17 @@
# class Model2 < Model1
# root_options implicit: 1
# end
# @param [Hash] options
# @return [void]
+ # @since 0.12.0 may change name through +:name+
def root_options(options)
@options = options
+ return unless options.key?(:name)
+
+ @root = @root.dup
+ @root.name = options[:name]
end
# On inheritance, create +@root+ class variable
# @param [Class] klass
# @return [void]
@@ -103,37 +143,42 @@
super
root = @root
klass.class_eval { @root = root }
end
+ # @since 0.11.0
+ # @since 0.12.0 track source location on error (adfoster-r7)
def define_type_accel_base(accel_name, klass)
- singleton_class.class_eval(
- "def #{accel_name}(name, options={})\n" \
- " options[:name] = name\n" \
- " proc = proc do |opts|\n" \
- " #{klass}.new(options.merge(opts))\n" \
- " end\n" \
- " @root = Elem.new(name, proc, options[:content])\n" \
- 'end'
- )
+ singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
+ def #{accel_name}(name, options={}) # def sequence(name, type, options)
+ options[:name] = name
+ proc = proc do |opts|
+ #{klass}.new(options.merge(opts)) # Sequence.new(options.merge(opts))
+ end
+ @root = Elem.new(name, proc, options[:content])
+ end
+ EVAL
end
+ # @since 0.11.0
+ # @since 0.12.0 track source location on error (adfoster-r7)
def define_type_accel_of(accel_name, klass)
- singleton_class.class_eval(
- "def #{accel_name}_of(name, type, options={})\n" \
- " options[:name] = name\n" \
- " proc = proc do |opts|\n" \
- " #{klass}.new(type, options.merge(opts))\n" \
- " end\n" \
- " @root = Elem.new(name, proc, nil)\n" \
- 'end'
- )
+ singleton_class.class_eval <<-EVAL, __FILE__, __LINE__ + 1
+ def #{accel_name}_of(name, type, options={}) # def sequence_of(name, type, options)
+ options[:name] = name
+ proc = proc do |opts|
+ #{klass}.new(type, options.merge(opts)) # SequenceOf.new(type, options.merge(opts))
+ end
+ @root = Elem.new(name, proc, nil)
+ end
+ EVAL
end
# Define an accelarator to access a type in a model definition
# @param [String] accel_name
# @param [Class] klass
+ # @since 0.11.0
def define_type_accel(accel_name, klass)
if klass < Types::SequenceOf
define_type_accel_of(accel_name, klass)
else
define_type_accel_base(accel_name, klass)
@@ -182,96 +227,112 @@
end
end
extend Accel
- # @method sequence(name, options)
+ # @!method sequence(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::Sequence#initialize
- # @method set(name, options)
+ # @!method set(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::Set#initialize
- # @method choice(name, options)
+ # @!method choice(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::Choice#initialize
%w[sequence set choice].each do |type|
self.define_type_accel_base(type, Types.const_get(type.capitalize))
end
- # @method sequence_of(name, type, options)
+ # @!method sequence_of(name, type, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Model, Types::Base] type type for SEQUENCE OF
# @param [Hash] options
# @return [Elem]
# @see Types::SequenceOf#initialize
- # @method set_of(name, type, options)
+ # @!method set_of(name, type, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Model, Types::Base] type type for SET OF
# @param [Hash] options
# @return [Elem]
# @see Types::SetOf#initialize
%w[sequence set].each do |type|
define_type_accel_of(type, Types.const_get("#{type.capitalize}Of"))
end
- # @method boolean(name, options)
+ # @!method boolean(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::Boolean#initialize
- # @method integer(name, options)
+ # @!method integer(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::Integer#initialize
- # @method bit_string(name, options)
+ # @!method bit_string(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::BitString#initialize
- # @method octet_string(name, options)
+ # @!method octet_string(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::OctetString#initialize
- # @method null(name, options)
+ # @!method null(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::Null#initialize
- # @method enumerated(name, options)
+ # @!method enumerated(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::Enumerated#initialize
- # @method utf8_string(name, options)
+ # @!method utf8_string(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::Utf8String#initialize
- # @method numeric_string(name, options)
+ # @!method numeric_string(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::NumericString#initialize
- # @method printable_string(name, options)
+ # @!method printable_string(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::PrintableString#initialize
- # @method visible_string(name, options)
+ # @!method visible_string(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::VisibleString#initialize
- # @method ia5_string(name, options)
+ # @!method ia5_string(name, options)
+ # @!scope class
# @param [Symbol,String] name name of object in model
# @param [Hash] options
# @return [Elem]
# @see Types::IA5String#initialize
Types.primitives.each do |prim|
@@ -283,22 +344,22 @@
# Create a new instance of a {Model}
# @param [Hash] args
def initialize(args={})
root = generate_root
- set_elements(root)
+ generate_elements(root)
initialize_elements(self, args)
end
# Give access to element +name+ in model
# @param [String,Symbol] name
# @return [Types::Base]
def [](name)
@elements[name]
end
- # Set value of element +name+. Element should be a {Base}.
+ # Set value of element +name+. Element should be a {Types::Base}.
# @param [String,Symbol] name
# @param [Object] value
# @return [Object] value
def []=(name, value)
raise Error, 'cannot set value for a Model' if @elements[name].is_a? Model
@@ -458,28 +519,47 @@
@elements = {}
@elements[@root_name] = get_type(class_element.proc_or_class, self.class.options || {})
class_element
end
- def set_elements(element) # rubocop:disable Naming/AccessorMethodName
+ def generate_elements(element)
+ if element.is_a?(WrapElem)
+ generate_wrapper(element)
+ return
+ end
return unless element.content.is_a? Array
@elements[name].value = element.content.map do |another_element|
- subel = get_type(another_element.proc_or_class)
- @elements[another_element.name] = subel
- set_elements(another_element) if composed?(subel) && another_element.content.is_a?(Array)
+ add_subelement(another_element)
+ end
+ end
+
+ def generate_wrapper(wrap_elem)
+ inner_elem = wrap_elem.element
+ subel = add_subelement(inner_elem)
+ Wrapper.new(subel, wrap_elem.options)
+ end
+
+ def add_subelement(subelement)
+ case subelement
+ when Elem
+ subel = get_type(subelement.proc_or_class)
+ @elements[subelement.name] = subel
+ generate_elements(subelement) if composed?(subel) && subelement.content.is_a?(Array)
subel
+ when WrapElem
+ generate_wrapper(subelement)
end
end
def initialize_elements(obj, args)
args.each do |name, value|
next unless obj[name]
case value
when Hash
- raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless obj[name].is_a? Model
+ raise ArgumentError, "element #{name}: may only pass a Hash for Model elements" unless obj[name].is_a?(Model)
initialize_elements obj[name], value
when Array
initialize_element_from_array(obj[name], value)
else
@@ -497,18 +577,25 @@
else
value.each { |el| composed << el }
end
end
- def private_to_h(element=nil)
+ def private_to_h(element=nil) # rubocop:disable Metrics/CyclomaticComplexity
my_element = element || root
my_element = my_element.root if my_element.is_a?(Model)
value = case my_element
when Types::SequenceOf
sequence_of_to_h(my_element)
when Types::Sequence
sequence_to_h(my_element)
+ # @author adfoster-r7
+ when Types::Choice
+ raise ChoiceError.new(my_element) if my_element.chosen.nil?
+
+ private_to_h(my_element.value[my_element.chosen])
+ when Wrapper
+ wrapper_to_h(my_element)
else
my_element.value
end
if element.nil?
{ @root_name => value }
@@ -527,13 +614,31 @@
def sequence_to_h(seq)
ary = seq.value.map do |el|
next if el.optional? && el.value.nil?
- name = el.is_a?(Model) ? @elements.key(el) : el.name
- [name, private_to_h(el)]
+ case el
+ when Model
+ hsh = el.to_h
+ hsh = hsh[hsh.keys.first]
+ [@elements.key(el), hsh]
+ when Wrapper
+ [@elements.key(el.element), wrapper_to_h(el)]
+ else
+ [el.name, private_to_h(el)]
+ end
end
ary.compact!
ary.to_h
+ end
+
+ def wrapper_to_h(wrap)
+ case wrap.element
+ when Model
+ hsh = wrap.element.to_h
+ hsh[hsh.keys.first]
+ else
+ private_to_h(wrap.element)
+ end
end
end
end