lib/rasn1/model.rb in rasn1-0.8.0 vs lib/rasn1/model.rb in rasn1-0.9.0
- old
+ new
@@ -56,20 +56,24 @@
# 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
+ class Model # rubocop:disable Metrics/ClassLength
+ # @private
+ Elem = Struct.new(:name, :proc_or_class, :content)
+
class << self
# @return [Hash]
attr_reader :options
# Use another model in this model
# @param [String,Symbol] name
# @param [Class] model_klass
+ # @return [Elem]
def model(name, model_klass)
- @root = [name, model_klass]
+ @root = Elem.new(name, model_klass, nil)
end
# Update options of root element.
# May be used when subclassing.
# class Model1 < RASN1::Model
@@ -97,127 +101,143 @@
end
# @method sequence(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::Sequence#initialize
# @method set(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::Set#initialize
# @method choice(name, options)
# @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|
class_eval "def #{type}(name, options={})\n" \
" options.merge!(name: name)\n" \
" proc = proc do |opts|\n" \
" Types::#{type.capitalize}.new(options.merge(opts))\n" \
" end\n" \
- " @root = [name, proc]\n" \
- " @root << options[:content] unless options[:content].nil?\n" \
- " @root\n" \
+ " @root = Elem.new(name, proc, options[:content])\n" \
'end'
end
# @method sequence_of(name, type, options)
# @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)
# @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|
klass_name = "Types::#{type.capitalize}Of"
class_eval "def #{type}_of(name, type, options={})\n" \
" options.merge!(name: name)\n" \
" proc = proc do |opts|\n" \
" #{klass_name}.new(type, options.merge(opts))\n" \
" end\n" \
- " @root = [name, proc]\n" \
+ " @root = Elem.new(name, proc, nil)\n" \
'end'
end
# @method boolean(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::Boolean#initialize
# @method integer(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::Integer#initialize
# @method bit_string(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::BitString#initialize
# @method octet_string(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::OctetString#initialize
# @method null(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::Null#initialize
# @method enumerated(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::Enumerated#initialize
# @method utf8_string(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::Utf8String#initialize
# @method numeric_string(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::NumericString#initialize
# @method printable_string(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::PrintableString#initialize
# @method visible_string(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::VisibleString#initialize
# @method ia5_string(name, options)
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::IA5String#initialize
Types.primitives.each do |prim|
next if prim == Types::ObjectId
method_name = prim.type.gsub(/([a-z0-9])([A-Z])/, '\1_\2').downcase.gsub(/\s+/, '_')
class_eval "def #{method_name}(name, options={})\n" \
" options.merge!(name: name)\n" \
" proc = proc do |opts|\n" \
" #{prim}.new(options.merge(opts))\n" \
" end\n" \
- " @root = [name, proc]\n" \
+ " @root = Elem.new(name, proc, nil)\n" \
'end'
end
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @note This method is named +objectid+ and not +object_id+ to not override
# +Object#object_id+.
# @see Types::ObjectId#initialize
def objectid(name, options={})
options[:name] = name
proc = proc { |opts| Types::ObjectId.new(options.merge(opts)) }
- @root = [name, proc]
+ @root = Elem.new(name, proc, nil)
end
# @param [Symbol,String] name name of object in model
# @param [Hash] options
+ # @return [Elem]
# @see Types::Any#initialize
def any(name, options={})
options[:name] = name
proc = proc { |opts| Types::Any.new(options.merge(opts)) }
- @root = [name, proc]
+ @root = Elem.new(name, proc, nil)
end
# Give type name (aka class name)
# @return [String]
def type
@@ -231,21 +251,21 @@
# @param [Boolean] ber accept BER encoding or not
# @return [Model]
# @raise [ASN1Error] error on parsing
def parse(str, ber: false)
model = new
- model.parse! str, ber: ber
+ model.parse!(str, ber: ber)
model
end
end
# Create a new instance of a {Model}
# @param [Hash] args
def initialize(args={})
root = generate_root
- set_elements(*root)
- initialize_elements self, args
+ set_elements(root)
+ initialize_elements(self, args)
end
# Give access to element +name+ in model
# @param [String,Symbol] name
# @return [Types::Base]
@@ -261,14 +281,14 @@
raise Error, 'cannot set value for a Model' if @elements[name].is_a? Model
@elements[name].value = value
end
- # Get name frm root type
+ # Get name from root type
# @return [String,Symbol]
def name
- @root
+ @root_name
end
# Get elements names
# @return [Array<Symbol,String>]
def keys
@@ -279,21 +299,21 @@
# @return [Hash]
def to_h
private_to_h
end
- # @return [String]
- def to_der
- @elements[@root].to_der
- end
-
# Get root element from model
# @return [Types::Base,Model]
def root
- @elements[@root]
+ @elements[@root_name]
end
+ # @return [String]
+ def to_der
+ root.to_der
+ end
+
# Give type name (aka class name)
# @return [String]
def type
self.class.type
end
@@ -302,29 +322,41 @@
# @param [String] str
# @param [Boolean] ber accept BER encoding or not
# @return [Integer] number of parsed bytes
# @raise [ASN1Error] error on parsing
def parse!(str, ber: false)
- @elements[@root].parse!(str.dup.force_encoding('BINARY'), ber: ber)
+ root.parse!(str.dup.force_encoding('BINARY'), ber: ber)
end
# @overload value
# Get value of root element
# @return [Object,nil]
- # @overload value(name)
+ # @overload value(name, *args)
# Direct access to the value of +name+ (nested) element of model.
# @param [String,Symbol] name
+ # @param [Array<Integer,String,Symbol>] args more argument to access element. May be
+ # used to access content of a SequenceOf or a SetOf
# @return [Object,nil]
# @return [Object,nil]
+ # @example
+ # class MyModel1 < RASN1::Model
+ # sequence('seq', content: [boolean('boolean'), integer('int')])
+ # end
+ # class MyModel2 < RASN1::Model
+ # sequence('root', content: [sequence_of('list', MyModel1)])
+ # end
+ # model = MyModel2.new
+ # model.parse!(der)
+ # # access to 2nd MyModel1.int in list
+ # model.value('list', 1, 'int')
def value(name=nil, *args)
if name.nil?
- @elements[@root].value
+ root.value
else
elt = by_name(name)
unless args.empty?
- elt = elt.root if elt.is_a?(Model)
args.each do |arg|
elt = elt.root if elt.is_a?(Model)
elt = elt[arg]
end
end
@@ -334,20 +366,20 @@
end
# Delegate some methods to root element
# @param [Symbol] meth
def method_missing(meth, *args)
- if @elements[@root].respond_to? meth
- @elements[@root].send meth, *args
+ if root.respond_to? meth
+ root.send meth, *args
else
super
end
end
# @return [Boolean]
def respond_to_missing?(meth, *)
- @elements[@root].respond_to?(meth) || super
+ root.respond_to?(meth) || super
end
# @return [String]
def inspect(level=0)
' ' * level + "(#{type}) #{root.inspect(-level)}"
@@ -383,34 +415,37 @@
def composed?(elt)
[Types::Sequence, Types::Set].include? elt.class
end
+ # proc_or_class:
+ # * proc: a Types::Base subclass
+ # * class: a model
def get_type(proc_or_class, options={})
case proc_or_class
when Proc
proc_or_class.call(options)
when Class
proc_or_class.new
end
end
def generate_root
- root = self.class.class_eval { @root }
- @root = root[0]
+ class_element = self.class.class_eval { @root }
+ @root_name = class_element.name
@elements = {}
- @elements[@root] = get_type(root[1], self.class.options || {})
- root
+ @elements[@root_name] = get_type(class_element.proc_or_class, self.class.options || {})
+ class_element
end
- def set_elements(name, _elt, content=nil)
- return unless content.is_a? Array
+ def set_elements(element) # rubocop:disable Naming/AccessorMethodName
+ return unless element.content.is_a? Array
- @elements[name].value = content.map do |name2, proc_or_class, content2|
- subel = get_type(proc_or_class)
- @elements[name2] = subel
- set_elements(name2, proc_or_class, content2) if composed?(subel) && content2.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)
subel
end
end
def initialize_elements(obj, args)
@@ -421,56 +456,61 @@
when Hash
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
- composed = if obj[name].is_a? Model
- obj[name].root
- else
- obj[name]
- end
- if composed.of_type.is_a? Model
- value.each do |el|
- composed << initialize_elements(composed.of_type.class.new, el)
- end
- else
- value.each do |el|
- obj[name] << el
- end
- end
+ initialize_element_from_array(obj[name], value)
else
obj[name].value = value
end
end
end
+ def initialize_element_from_array(obj, value)
+ composed = obj.is_a?(Model) ? obj.root : obj
+ if composed.of_type.is_a?(Model)
+ value.each do |el|
+ composed << initialize_elements(composed.of_type.class.new, el)
+ end
+ else
+ value.each { |el| composed << el }
+ end
+ end
+
def private_to_h(element=nil)
- my_element = element
- my_element = root if my_element.nil?
+ my_element = element || root
my_element = my_element.root if my_element.is_a?(Model)
value = case my_element
when Types::SequenceOf
- if my_element.of_type < Model
- my_element.value.map { |el| el.to_h.values.first }
- else
- my_element.value.map { |el| private_to_h(el) }
- end
+ sequence_of_to_h(my_element)
when Types::Sequence
- seq = my_element.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)]
- end
- seq.compact!
- Hash[seq]
+ sequence_to_h(my_element)
else
my_element.value
end
if element.nil?
- { @root => value }
+ { @root_name => value }
else
value
end
+ end
+
+ def sequence_of_to_h(elt)
+ if elt.of_type < Model
+ elt.value.map { |el| el.to_h.values.first }
+ else
+ elt.value.map { |el| private_to_h(el) }
+ end
+ end
+
+ 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)]
+ end
+ ary.compact!
+ ary.to_h
end
end
end