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(value, options = EMPTY_HASH) click to toggle source

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
new(options = EMPTY_HASH) click to toggle source

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(value) click to toggle source

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

non_string_keyed_hash_to_data(hash) click to toggle source
    # 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
path_to_s() click to toggle source
   # 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
pcore_type_to_data(pcore_type) click to toggle source
    # 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
process(value) { || ... } click to toggle source

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
serialization_issue(issue, options = EMPTY_HASH) click to toggle source
    # 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
to_data(value) click to toggle source
    # 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
to_key_extended_hash(hash) click to toggle source

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
unknown_key_to_string_with_warning(value) click to toggle source
    # 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
unknown_to_string(value) click to toggle source
    # 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
unknown_to_string_with_warning(value) click to toggle source
    # 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
value_to_data_hash(value) click to toggle source
    # 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
with(key) { || ... } click to toggle source

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
with_recursive_guard(value) { || ... } click to toggle source

@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