class Puppet::Pops::Serialization::ToDataConverter
Class that can process an arbitrary object into a value that is assignable to `Data`.
@api public
Public Class Methods
Convert the given value according to the given options and return the result of the conversion
@param value [Object] the value to convert @param options {Symbol => <Boolean,String>} options hash @option options [Boolean] :rich_data `true` if rich data is enabled @option options [Boolean] :local_reference use local references instead of duplicating complex entries @option options [Boolean] :type_by_reference `true` if Object types are converted to references rather than embedded. @option options [Boolean] :symbol_as_string `true` if Symbols should be converted to strings (with type loss) @option options [String] :message_prefix String to prepend to in warnings and errors @return [Data] the processed result. An object assignable to `Data`.
@api public
# File lib/puppet/pops/serialization/to_data_converter.rb 22 def self.convert(value, options = EMPTY_HASH) 23 new(options).convert(value) 24 end
Create a new instance of the processor
@param options {Symbol => Object} options hash @option options [Boolean] :rich_data `true` if rich data is enabled @option options [Boolean] :local_references use local references instead of duplicating complex entries @option options [Boolean] :type_by_reference `true` if Object types are converted to references rather than embedded. @option options [Boolean] :symbol_as_string `true` if Symbols should be converted to strings (with type loss) @option options [String] :message_prefix String to prepend to path in warnings and errors @option semantic [Object] :semantic object to pass to the issue reporter
# File lib/puppet/pops/serialization/to_data_converter.rb 35 def initialize(options = EMPTY_HASH) 36 @type_by_reference = options[:type_by_reference] 37 @type_by_reference = true if @type_by_reference.nil? 38 39 @local_reference = options[:local_reference] 40 @local_reference = true if @local_reference.nil? 41 42 @symbol_as_string = options[:symbol_as_string] 43 @symbol_as_string = false if @symbol_as_string.nil? 44 45 @rich_data = options[:rich_data] 46 @rich_data = false if @rich_data.nil? 47 48 @message_prefix = options[:message_prefix] 49 @semantic = options[:semantic] 50 end
Public Instance Methods
Convert the given value
@param value [Object] the value to convert @return [Data] the processed result. An object assignable to `Data`.
@api public
# File lib/puppet/pops/serialization/to_data_converter.rb 58 def convert(value) 59 @path = [] 60 @values = {} 61 to_data(value) 62 end
Private Instance Methods
# File lib/puppet/pops/serialization/to_data_converter.rb 215 def non_string_keyed_hash_to_data(hash) 216 if @rich_data 217 to_key_extended_hash(hash) 218 else 219 result = {} 220 hash.each_pair do |key, value| 221 if key.is_a?(Symbol) && @symbol_as_string 222 key = key.to_s 223 elsif !key.is_a?(String) 224 key = unknown_key_to_string_with_warning(key) 225 end 226 with(key) { result[key] = to_data(value) } 227 end 228 result 229 end 230 end
# File lib/puppet/pops/serialization/to_data_converter.rb 66 def path_to_s 67 s = String.new(@message_prefix || '') 68 s << JsonPath.to_json_path(@path)[1..-1] 69 s 70 end
# File lib/puppet/pops/serialization/to_data_converter.rb 289 def pcore_type_to_data(pcore_type) 290 type_name = pcore_type.name 291 if @type_by_reference || type_name.start_with?('Pcore::') 292 type_name 293 else 294 with(PCORE_TYPE_KEY) { to_data(pcore_type) } 295 end 296 end
If `:local_references` is enabled, then the `object_id` will be associated with the current path of the context the first time this method is called. The method then returns the result of yielding to the given block. Subsequent calls with a value that has the same `object_id` will instead return a reference based on the given path.
If `:local_references` is disabled, then this method performs a check for endless recursion before it yields to the given block. The result of yielding is returned.
@param value [Object] the value @yield The block that will produce the data for the value @return [Data] the result of yielding to the given block, or a hash denoting a reference
@api private
# File lib/puppet/pops/serialization/to_data_converter.rb 138 def process(value, &block) 139 if @local_reference 140 id = value.object_id 141 ref = @values[id] 142 if ref.nil? 143 @values[id] = @path.dup 144 yield 145 elsif ref.instance_of?(Hash) 146 ref 147 else 148 json_ref = JsonPath.to_json_path(ref) 149 if json_ref.nil? 150 # Complex key and hence no way to reference the prior value. The value must therefore be 151 # duplicated which in turn introduces a risk for endless recursion in case of self 152 # referencing structures 153 with_recursive_guard(value, &block) 154 else 155 @values[id] = { PCORE_TYPE_KEY => PCORE_LOCAL_REF_SYMBOL, PCORE_VALUE_KEY => json_ref } 156 end 157 end 158 else 159 with_recursive_guard(value, &block) 160 end 161 end
# File lib/puppet/pops/serialization/to_data_converter.rb 299 def serialization_issue(issue, options = EMPTY_HASH) 300 semantic = @semantic 301 if semantic.nil? 302 tos = Puppet::Pops::PuppetStack.top_of_stack 303 if tos.empty? 304 semantic = Puppet::Pops::SemanticError.new(issue, nil, EMPTY_HASH) 305 else 306 file, line = stacktrace 307 semantic = Puppet::Pops::SemanticError.new(issue, nil, {:file => file, :line => line}) 308 end 309 end 310 optionally_fail(issue, semantic, options) 311 end
# File lib/puppet/pops/serialization/to_data_converter.rb 72 def to_data(value) 73 if value.nil? || Types::PScalarDataType::DEFAULT.instance?(value) 74 if @rich_data && value.is_a?(String) && value.encoding == Encoding::ASCII_8BIT 75 # Transform the binary string to rich Binary 76 { 77 PCORE_TYPE_KEY => PCORE_TYPE_BINARY, 78 PCORE_VALUE_KEY => Puppet::Pops::Types::PBinaryType::Binary.from_binary_string(value).to_s 79 } 80 else 81 value 82 end 83 elsif :default == value 84 if @rich_data 85 { PCORE_TYPE_KEY => PCORE_TYPE_DEFAULT } 86 else 87 serialization_issue(Issues::SERIALIZATION_DEFAULT_CONVERTED_TO_STRING, :path => path_to_s) 88 'default' 89 end 90 elsif value.is_a?(Symbol) 91 if @symbol_as_string 92 value.to_s 93 elsif @rich_data 94 { PCORE_TYPE_KEY => PCORE_TYPE_SYMBOL, PCORE_VALUE_KEY => value.to_s } 95 else 96 unknown_to_string_with_warning(value) 97 end 98 elsif value.instance_of?(Array) 99 process(value) do 100 result = [] 101 value.each_with_index do |elem, index| 102 with(index) { result << to_data(elem) } 103 end 104 result 105 end 106 elsif value.instance_of?(Hash) 107 process(value) do 108 if value.keys.all? { |key| key.is_a?(String) && key != PCORE_TYPE_KEY } 109 result = {} 110 value.each_pair { |key, elem| with(key) { result[key] = to_data(elem) } } 111 result 112 else 113 non_string_keyed_hash_to_data(value) 114 end 115 end 116 elsif value.instance_of?(Types::PSensitiveType::Sensitive) 117 process(value) do 118 { PCORE_TYPE_KEY => PCORE_TYPE_SENSITIVE, PCORE_VALUE_KEY => to_data(value.unwrap) } 119 end 120 else 121 unknown_to_data(value) 122 end 123 end
A Key extended hash is a hash whose keys are not entirely strings. Such a hash cannot be safely represented as JSON or YAML
@param hash {Object => Object} the hash to process @return [String => Data] the representation of the extended hash
# File lib/puppet/pops/serialization/to_data_converter.rb 237 def to_key_extended_hash(hash) 238 key_value_pairs = [] 239 hash.each_pair do |key, value| 240 key = to_data(key) 241 key_value_pairs << key 242 key_value_pairs << with(key) { to_data(value) } 243 end 244 { PCORE_TYPE_KEY => PCORE_TYPE_HASH, PCORE_VALUE_KEY => key_value_pairs } 245 end
# File lib/puppet/pops/serialization/to_data_converter.rb 199 def unknown_key_to_string_with_warning(value) 200 str = unknown_to_string(value) 201 serialization_issue(Issues::SERIALIZATION_UNKNOWN_KEY_CONVERTED_TO_STRING, :path => path_to_s, :klass => value.class, :value => str) 202 str 203 end
# File lib/puppet/pops/serialization/to_data_converter.rb 195 def unknown_to_data(value) 196 @rich_data ? value_to_data_hash(value) : unknown_to_string_with_warning(value) 197 end
# File lib/puppet/pops/serialization/to_data_converter.rb 211 def unknown_to_string(value) 212 value.is_a?(Regexp) ? Puppet::Pops::Types::PRegexpType.regexp_to_s_with_delimiters(value) : value.to_s 213 end
# File lib/puppet/pops/serialization/to_data_converter.rb 205 def unknown_to_string_with_warning(value) 206 str = unknown_to_string(value) 207 serialization_issue(Issues::SERIALIZATION_UNKNOWN_CONVERTED_TO_STRING, :path => path_to_s, :klass => value.class, :value => str) 208 str 209 end
# File lib/puppet/pops/serialization/to_data_converter.rb 247 def value_to_data_hash(value) 248 pcore_type = value.is_a?(Types::PuppetObject) ? value._pcore_type : Types::TypeCalculator.singleton.infer(value) 249 if pcore_type.is_a?(Puppet::Pops::Types::PRuntimeType) 250 unknown_to_string_with_warning(value) 251 else 252 pcore_tv = pcore_type_to_data(pcore_type) 253 if pcore_type.roundtrip_with_string? 254 { 255 PCORE_TYPE_KEY => pcore_tv, 256 257 # Scalar values are stored using their default string representation 258 PCORE_VALUE_KEY => Types::StringConverter.singleton.convert(value) 259 } 260 elsif pcore_type.implementation_class.respond_to?(:_pcore_init_from_hash) 261 process(value) do 262 { 263 PCORE_TYPE_KEY => pcore_tv, 264 }.merge(to_data(value._pcore_init_hash)) 265 end 266 else 267 process(value) do 268 (names, _, required_count) = pcore_type.parameter_info(value.class) 269 args = names.map { |name| value.send(name) } 270 271 # Pop optional arguments that are default 272 while args.size > required_count 273 break unless pcore_type[names[args.size-1]].default_value?(args.last) 274 args.pop 275 end 276 result = { 277 PCORE_TYPE_KEY => pcore_tv 278 } 279 args.each_with_index do |val, idx| 280 key = names[idx] 281 with(key) { result[key] = to_data(val) } 282 end 283 result 284 end 285 end 286 end 287 end
Pushes `key` to the end of the path and yields to the given block. The `key` is popped when the yield returns. @param key [Object] the key to push on the current path @yield The block that will produce the returned value @return [Object] the result of yielding to the given block
@api private
# File lib/puppet/pops/serialization/to_data_converter.rb 170 def with(key) 171 @path.push(key) 172 value = yield 173 @path.pop 174 value 175 end
@param value [Object] the value to use when checking endless recursion @yield The block that will produce the data @return [Data] the result of yielding to the given block
# File lib/puppet/pops/serialization/to_data_converter.rb 180 def with_recursive_guard(value) 181 id = value.object_id 182 if @recursive_lock 183 if @recursive_lock.include?(id) 184 serialization_issue(Issues::SERIALIZATION_ENDLESS_RECURSION, :type_name => value.class.name) 185 end 186 @recursive_lock[id] = true 187 else 188 @recursive_lock = { id => true } 189 end 190 v = yield 191 @recursive_lock.delete(id) 192 v 193 end