module Puppet::Pops module Serialization class Builder def initialize(values) @values = values @resolved = true end def [](key) @values[key] end def []=(key, value) @values[key] = value @resolved = false if value.is_a?(Builder) end def resolve unless @resolved @resolved = true if @values.is_a?(Array) @values.each_with_index { |v, idx| @values[idx] = v.resolve if v.is_a?(Builder) } elsif @values.is_a?(Hash) @values.each_pair { |k, v| @values[k] = v.resolve if v.is_a?(Builder) } end end @values end end class ObjectHashBuilder < Builder def initialize(instance) super({}) @instance = instance end def resolve @instance._pcore_init_from_hash(super) @instance end end class ObjectArrayBuilder < Builder def initialize(instance) super({}) @instance = instance end def resolve @instance.send(:initialize, *super.values) @instance end end # Class that can process the `Data` produced by the {ToDataConverter} class and reassemble # the objects that were converted. # # @api public class FromDataConverter # Converts the given `Data` _value_ according to the given _options_ and returns the resulting `RichData`. # # @param value [Data] the value to convert # @param options {Symbol => } options hash # @option options [Loaders::Loader] :loader the loader to use. Can be `nil` in which case the default is # determined by the {Types::TypeParser}. # @option options [Boolean] :allow_unresolved `true` to allow that rich_data hashes are kept "as is" if the # designated '__ptype' cannot be resolved. Defaults to `false`. # @return [RichData] the processed result. # # @api public def self.convert(value, options = EMPTY_HASH) new(options).convert(value) end # Creates a new instance of the processor # # @param options {Symbol => Object} options hash # @option options [Loaders::Loader] :loader the loader to use. Can be `nil` in which case the default is # determined by the {Types::TypeParser}. # @option options [Boolean] :allow_unresolved `true` to allow that rich_data hashes are kept "as is" if the # designated '__ptype' cannot be resolved. Defaults to `false`. # # @api public def initialize(options = EMPTY_HASH) @allow_unresolved = options[:allow_unresolved] @allow_unresolved = false if @allow_unresolved.nil? @loader = options[:loader] @pcore_type_procs = { PCORE_TYPE_HASH => proc do |hash, _| value = hash[PCORE_VALUE_KEY] build({}) do top = value.size idx = 0 while idx < top key = without_value { convert(value[idx]) } idx += 1 with(key) { convert(value[idx]) } idx += 1 end end end, PCORE_TYPE_SENSITIVE => proc do |hash, _| build(Types::PSensitiveType::Sensitive.new(convert(hash[PCORE_VALUE_KEY]))) end, PCORE_TYPE_DEFAULT => proc do |_, _| build(:default) end, PCORE_TYPE_SYMBOL => proc do |hash, _| build(:"#{hash[PCORE_VALUE_KEY]}") end, PCORE_LOCAL_REF_SYMBOL => proc do |hash, _| build(JsonPath::Resolver.singleton.resolve(@root, hash[PCORE_VALUE_KEY])) end } @pcore_type_procs.default = proc do |hash, type_value| value = hash.include?(PCORE_VALUE_KEY) ? hash[PCORE_VALUE_KEY] : hash.reject { |key, _| PCORE_TYPE_KEY == key } if type_value.is_a?(Hash) type = without_value { convert(type_value) } if type.is_a?(Hash) raise SerializationError, _('Unable to deserialize type from %{type}') % { type: type } unless @allow_unresolved hash else pcore_type_hash_to_value(type, value) end else type = Types::TypeParser.singleton.parse(type_value, @loader) if type.is_a?(Types::PTypeReferenceType) unless @allow_unresolved raise SerializationError, _('No implementation mapping found for Puppet Type %{type_name}') % { type_name: type_value } end hash else # not a string pcore_type_hash_to_value(type, value) end end end end # Converts the given `Data` _value_ and returns the resulting `RichData` # # @param value [Data] the value to convert # @return [RichData] the processed result # # @api public def convert(value) if value.is_a?(Hash) pcore_type = value[PCORE_TYPE_KEY] if pcore_type && (pcore_type.is_a?(String) || pcore_type.is_a?(Hash)) @pcore_type_procs[pcore_type].call(value, pcore_type) else build({}) { value.each_pair { |key, elem| with(key) { convert(elem) }}} end elsif value.is_a?(Array) build([]) { value.each_with_index { |elem, idx| with(idx) { convert(elem)}}} else build(value) end end private def with(key) parent_key = @key @key = key yield @key = parent_key end def with_value(value) @root = value unless instance_variable_defined?(:@root) parent = @current @current = value yield @current = parent value end def without_value parent = @current @current = nil value = yield @current = parent value end def build(value, &block) vx = Builder.new(value) @current[@key] = vx unless @current.nil? with_value(vx, &block) if block_given? vx.resolve end def build_object(builder, &block) @current[@key] = builder unless @current.nil? with_value(builder, &block) if block_given? builder.resolve end def pcore_type_hash_to_value(pcore_type, value) if value.is_a?(Hash) # Complex object if value.empty? build(pcore_type.create) elsif pcore_type.implementation_class.respond_to?(:_pcore_init_from_hash) build_object(ObjectHashBuilder.new(pcore_type.allocate)) { value.each_pair { |key, elem| with(key) { convert(elem) } } } else build_object(ObjectArrayBuilder.new(pcore_type.allocate)) { value.each_pair { |key, elem| with(key) { convert(elem) } } } end elsif value.is_a?(String) build(pcore_type.create(value)) else raise SerializationError, _('Cannot create a %{type_name} from a %{arg_class}') % { :type_name => pcore_type.name, :arg_class => value.class.name } end end end end end