File: json_path.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (127 lines) | stat: -rw-r--r-- 3,593 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
require_relative '../../../puppet/concurrent/thread_local_singleton'

module Puppet::Pops
module Serialization
module JsonPath
  # Creates a _json_path_ reference from the given `path` argument
  #
  # @path path [Array<Integer,String>] An array of integers and strings
  # @return [String] the created json_path
  #
  # @api private
  def self.to_json_path(path)
    p = '$'
    path.each do |seg|
      if seg.nil?
        p << '[null]'
      elsif Types::PScalarDataType::DEFAULT.instance?(seg)
        p << '[' << Types::StringConverter.singleton.convert(seg, '%p') << ']'
      else
        # Unable to construct json path from complex segments
        return nil
      end
    end
    p
  end

  # Resolver for JSON path that uses the Puppet parser to create the AST. The path must start
  # with '$' which denotes the value that is passed into the parser. This parser can easily
  # be extended with more elaborate resolution mechanisms involving document sets.
  #
  # The parser is limited to constructs generated by the {JsonPath#to_json_path}
  # method.
  #
  # @api private
  class Resolver
    extend Puppet::Concurrent::ThreadLocalSingleton

    def initialize
      @parser = Parser::Parser.new
      @visitor = Visitor.new(nil, 'resolve', 2, 2)
    end

    # Resolve the given _path_ in the given _context_.
    # @param context [Object] the context used for resolution
    # @param path [String] the json path
    # @return [Object] the resolved value
    #
    def resolve(context, path)
      factory = @parser.parse_string(path)
      v = resolve_any(factory.model.body, context, path)
      v.is_a?(Builder) ? v.resolve : v
    end

    def resolve_any(ast, context, path)
      @visitor.visit_this_2(self, ast, context, path)
    end

    def resolve_AccessExpression(ast, context, path)
      bad_json_path(path) unless ast.keys.size == 1
      receiver = resolve_any(ast.left_expr, context, path)
      key = resolve_any(ast.keys[0], context, path)
      if receiver.is_a?(Types::PuppetObject)
        PCORE_TYPE_KEY == key ? receiver._pcore_type : receiver.send(key)
      else
        receiver[key]
      end
    end

    def resolve_NamedAccessExpression(ast, context, path)
      receiver = resolve_any(ast.left_expr, context, path)
      key = resolve_any(ast.right_expr, context, path)
      if receiver.is_a?(Types::PuppetObject)
        PCORE_TYPE_KEY == key ? receiver._pcore_type : receiver.send(key)
      else
        receiver[key]
      end
    end

    def resolve_QualifiedName(ast, _, _)
      v = ast.value
      'null' == v ? nil : v
    end

    def resolve_QualifiedReference(ast, _, _)
      v = ast.cased_value
      'null'.casecmp(v) == 0 ? nil : v
    end

    def resolve_ReservedWord(ast, _, _)
      ast.word
    end

    def resolve_LiteralUndef(_, _, _)
      'undef'
    end

    def resolve_LiteralDefault(_, _, _)
      'default'
    end

    def resolve_VariableExpression(ast, context, path)
      # A single '$' means root, i.e. the context.
      bad_json_path(path) unless EMPTY_STRING == resolve_any(ast.expr, context, path)
      context
    end

    def resolve_CallMethodExpression(ast, context, path)
      bad_json_path(path) unless ast.arguments.empty?
      resolve_any(ast.functor_expr, context, path)
    end

    def resolve_LiteralValue(ast, _, _)
      ast.value
    end

    def resolve_Object(ast, _, path)
      bad_json_path(path)
    end

    def bad_json_path(path)
      raise SerializationError, _('Unable to parse jsonpath "%{path}"') % { :path => path }
    end
    private :bad_json_path
  end
end
end
end