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 [Boolean] :force_symbol `false` if Symbols should not be converted (rich_data and symbol_as_string must be false) @option options [Boolean] :silence_warnings `false` if warnings should be silenced @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 23 def self.convert(value, options = EMPTY_HASH) 24 new(options).convert(value) 25 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 36 def initialize(options = EMPTY_HASH) 37 @type_by_reference = options[:type_by_reference] 38 @type_by_reference = true if @type_by_reference.nil? 39 40 @local_reference = options[:local_reference] 41 @local_reference = true if @local_reference.nil? 42 43 @symbol_as_string = options[:symbol_as_string] 44 @symbol_as_string = false if @symbol_as_string.nil? 45 46 @force_symbol = options[:force_symbol] 47 @force_symbol = false if @force_symbol.nil? 48 49 @silence_warnings = options[:silence_warnings] 50 @silence_warnings = false if @silence_warnings.nil? 51 52 @rich_data = options[:rich_data] 53 @rich_data = false if @rich_data.nil? 54 55 @message_prefix = options[:message_prefix] 56 @semantic = options[:semantic] 57 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 65 def convert(value) 66 @path = [] 67 @values = {} 68 to_data(value) 69 end
Private Instance Methods
# File lib/puppet/pops/serialization/to_data_converter.rb 226 def non_string_keyed_hash_to_data(hash) 227 if @rich_data 228 to_key_extended_hash(hash) 229 else 230 result = {} 231 hash.each_pair do |key, value| 232 if key.is_a?(Symbol) && @symbol_as_string 233 key = key.to_s 234 elsif !key.is_a?(String) 235 key = unknown_key_to_string_with_warning(key) 236 end 237 with(key) { result[key] = to_data(value) } 238 end 239 result 240 end 241 end
# File lib/puppet/pops/serialization/to_data_converter.rb 73 def path_to_s 74 s = @message_prefix || '' 75 s << JsonPath.to_json_path(@path)[1..-1] 76 s 77 end
# File lib/puppet/pops/serialization/to_data_converter.rb 300 def pcore_type_to_data(pcore_type) 301 type_name = pcore_type.name 302 if @type_by_reference || type_name.start_with?('Pcore::') 303 type_name 304 else 305 with(PCORE_TYPE_KEY) { to_data(pcore_type) } 306 end 307 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 153 def process(value, &block) 154 if @local_reference 155 id = value.object_id 156 ref = @values[id] 157 if ref.nil? 158 @values[id] = @path.dup 159 yield 160 elsif ref.instance_of?(Hash) 161 ref 162 else 163 json_ref = JsonPath.to_json_path(ref) 164 if json_ref.nil? 165 # Complex key and hence no way to reference the prior value. The value must therefore be 166 # duplicated which in turn introduces a risk for endless recursion in case of self 167 # referencing structures 168 with_recursive_guard(value, &block) 169 else 170 @values[id] = { PCORE_TYPE_KEY => PCORE_LOCAL_REF_SYMBOL, PCORE_VALUE_KEY => json_ref } 171 end 172 end 173 else 174 with_recursive_guard(value, &block) 175 end 176 end
# File lib/puppet/pops/serialization/to_data_converter.rb 310 def serialization_issue(issue, options = EMPTY_HASH) 311 semantic = @semantic 312 if semantic.nil? 313 tos = Puppet::Pops::PuppetStack.top_of_stack 314 if tos.empty? 315 semantic = Puppet::Pops::SemanticError.new(issue, nil, EMPTY_HASH) 316 else 317 file, line = stacktrace 318 semantic = Puppet::Pops::SemanticError.new(issue, nil, {:file => file, :line => line}) 319 end 320 end 321 optionally_fail(issue, semantic, options) 322 end
# File lib/puppet/pops/serialization/to_data_converter.rb 79 def to_data(value) 80 if value.nil? || Types::PScalarDataType::DEFAULT.instance?(value) 81 if @rich_data && value.is_a?(String) && value.encoding == Encoding::ASCII_8BIT 82 # Transform the binary string to rich Binary 83 { 84 PCORE_TYPE_KEY => PCORE_TYPE_BINARY, 85 PCORE_VALUE_KEY => Puppet::Pops::Types::PBinaryType::Binary.from_binary_string(value).to_s 86 } 87 else 88 value 89 end 90 elsif :default == value 91 if @rich_data 92 { PCORE_TYPE_KEY => PCORE_TYPE_DEFAULT } 93 else 94 serialization_issue(Issues::SERIALIZATION_DEFAULT_CONVERTED_TO_STRING, :path => path_to_s) 95 'default' 96 end 97 elsif value.is_a?(Symbol) 98 if @symbol_as_string 99 value.to_s 100 elsif @rich_data 101 { PCORE_TYPE_KEY => PCORE_TYPE_SYMBOL, PCORE_VALUE_KEY => value.to_s } 102 else 103 if @force_symbol 104 value 105 else 106 @silence_warnings ? unknown_to_string(value) : unknown_to_string_with_warning(value) 107 end 108 end 109 elsif value.instance_of?(Array) 110 process(value) do 111 result = [] 112 value.each_with_index do |elem, index| 113 with(index) { result << to_data(elem) } 114 end 115 result 116 end 117 elsif value.instance_of?(Hash) 118 process(value) do 119 if value.keys.all? { |key| key.is_a?(String) && key != PCORE_TYPE_KEY } 120 result = {} 121 value.each_pair { |key, elem| with(key) { result[key] = to_data(elem) } } 122 result 123 else 124 non_string_keyed_hash_to_data(value) 125 end 126 end 127 elsif value.instance_of?(Types::PSensitiveType::Sensitive) 128 process(value) do 129 { PCORE_TYPE_KEY => PCORE_TYPE_SENSITIVE, PCORE_VALUE_KEY => to_data(value.unwrap) } 130 end 131 else 132 if @rich_data 133 value_to_data_hash(value) 134 else 135 @silence_warnings ? unknown_to_string(value) : unknown_to_string_with_warning(value) 136 end 137 end 138 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 248 def to_key_extended_hash(hash) 249 key_value_pairs = [] 250 hash.each_pair do |key, value| 251 key = to_data(key) 252 key_value_pairs << key 253 key_value_pairs << with(key) { to_data(value) } 254 end 255 { PCORE_TYPE_KEY => PCORE_TYPE_HASH, PCORE_VALUE_KEY => key_value_pairs } 256 end
# File lib/puppet/pops/serialization/to_data_converter.rb 210 def unknown_key_to_string_with_warning(value) 211 str = unknown_to_string(value) 212 serialization_issue(Issues::SERIALIZATION_UNKNOWN_KEY_CONVERTED_TO_STRING, :path => path_to_s, :klass => value.class, :value => str) 213 str 214 end
# File lib/puppet/pops/serialization/to_data_converter.rb 222 def unknown_to_string(value) 223 value.is_a?(Regexp) ? Puppet::Pops::Types::PRegexpType.regexp_to_s_with_delimiters(value) : value.to_s 224 end
# File lib/puppet/pops/serialization/to_data_converter.rb 216 def unknown_to_string_with_warning(value) 217 str = unknown_to_string(value) 218 serialization_issue(Issues::SERIALIZATION_UNKNOWN_CONVERTED_TO_STRING, :path => path_to_s, :klass => value.class, :value => str) 219 str 220 end
# File lib/puppet/pops/serialization/to_data_converter.rb 258 def value_to_data_hash(value) 259 pcore_type = value.is_a?(Types::PuppetObject) ? value._pcore_type : Types::TypeCalculator.singleton.infer(value) 260 if pcore_type.is_a?(Puppet::Pops::Types::PRuntimeType) 261 unknown_to_string_with_warning(value) 262 else 263 pcore_tv = pcore_type_to_data(pcore_type) 264 if pcore_type.roundtrip_with_string? 265 { 266 PCORE_TYPE_KEY => pcore_tv, 267 268 # Scalar values are stored using their default string representation 269 PCORE_VALUE_KEY => Types::StringConverter.singleton.convert(value) 270 } 271 elsif pcore_type.implementation_class.respond_to?(:_pcore_init_from_hash) 272 process(value) do 273 { 274 PCORE_TYPE_KEY => pcore_tv, 275 }.merge(to_data(value._pcore_init_hash)) 276 end 277 else 278 process(value) do 279 (names, _, required_count) = pcore_type.parameter_info(value.class) 280 args = names.map { |name| value.send(name) } 281 282 # Pop optional arguments that are default 283 while args.size > required_count 284 break unless pcore_type[names[args.size-1]].default_value?(args.last) 285 args.pop 286 end 287 result = { 288 PCORE_TYPE_KEY => pcore_tv 289 } 290 args.each_with_index do |val, idx| 291 key = names[idx] 292 with(key) { result[key] = to_data(val) } 293 end 294 result 295 end 296 end 297 end 298 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 185 def with(key) 186 @path.push(key) 187 value = yield 188 @path.pop 189 value 190 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 195 def with_recursive_guard(value) 196 id = value.object_id 197 if @recursive_lock 198 if @recursive_lock.include?(id) 199 serialization_issue(Issues::SERIALIZATION_ENDLESS_RECURSION, :type_name => value.class.name) 200 end 201 @recursive_lock[id] = true 202 else 203 @recursive_lock = { id => true } 204 end 205 v = yield 206 @recursive_lock.delete(id) 207 v 208 end