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 [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
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
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(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
58 def convert(value)
59   @path = []
60   @values = {}
61   to_data(value)
62 end

Private Instance Methods

non_string_keyed_hash_to_data(hash) click to toggle source
    # 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
path_to_s() click to toggle source
   # 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
pcore_type_to_data(pcore_type) click to toggle source
    # 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
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
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
serialization_issue(issue, options = EMPTY_HASH) click to toggle source
    # 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
to_data(value) click to toggle source
    # 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
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
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
unknown_key_to_string_with_warning(value) click to toggle source
    # 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
unknown_to_data(value) click to toggle source
    # 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
unknown_to_string(value) click to toggle source
    # 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
unknown_to_string_with_warning(value) click to toggle source
    # 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
value_to_data_hash(value) click to toggle source
    # 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
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
170 def with(key)
171   @path.push(key)
172   value = yield
173   @path.pop
174   value
175 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
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