Class: Terret::Composition::Visitor

Inherits:
Psych::Visitors::ToRuby
  • Object
show all
Defined in:
lib/terret/composition.rb

Overview

YAML.safe_load DROPS a local tag silently: permitted_classes gates Ruby-object tags like !ruby/object:Foo, not application tags like !env, so a document loaded that way comes back with the tag gone and the bare scalar in its place — !env OPENROUTER_API_KEY would resolve to the STRING "OPENROUTER_API_KEY" and boot a service with a literal nonsense key. So resolution is explicit: parse to the node tree, then walk it and resolve our three tags by tag, refusing every other one. The class loader stays restricted throughout, so this is safe_load's safety with our tags intercepted before Psych can drop them. YAML.load appears nowhere.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(label) ⇒ Visitor

Returns a new instance of Visitor.



201
202
203
204
205
# File 'lib/terret/composition.rb', line 201

def initialize(label)
  loader = Psych::ClassLoader::Restricted.new([], [])
  super(Psych::ScalarScanner.new(loader), loader, symbolize_names: true)
  @label = label
end

Class Method Details

.load(text, label:) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/terret/composition.rb', line 180

def self.load(text, label:)
  stream = Psych.parse_stream(text)
  docs = stream.children
  if docs.length > 1
    raise Error, "#{label}: #{docs.length} YAML documents; a Terret config is one " \
                 "document, and the rest would be dropped without saying so"
  end
  return nil if docs.empty?

  value = new(label).accept(docs.first)
  Composition.assert_acyclic!(value, label)
  value
rescue Error
  raise
rescue StandardError => e
  # Psych's own exceptions, and the ArgumentError/FrozenError/NoMethodError
  # its schema handlers raise on input they cannot honour. Whatever it is,
  # the operator needs the file name more than the class.
  raise Error, "#{label}: #{e.class}: #{e.message}"
end

Instance Method Details

#visit_Psych_Nodes_Mapping(node) ⇒ Object

A collection carrying one of our tags is refused rather than silently dropped, which is what the parent visitor does with one.



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/terret/composition.rb', line 229

def visit_Psych_Nodes_Mapping(node)
  refuse_collection_tag!(node, CORE_MAPPING_TAGS)
  mapping = super
  # Psych merges `<<` when it points at a mapping or at a list of them.
  # Anything else it leaves as a literal "<<" key — a String among
  # symbols, and a merge the author believed had happened. Quoting the
  # key does not opt out: Psych reads `"<<"` as a merge too, so the
  # refusal names the spelling that does.
  if mapping.is_a?(Hash) && mapping.key?("<<")
    raise Error, "#{@label}: a << merge key must point at a mapping or a list of them; " \
                 "anything else is not a merge, and would land as a literal \"<<\" key. " \
                 "For a key that is genuinely the characters <<, write !!str \"<<\":."
  end

  mapping
end

#visit_Psych_Nodes_Scalar(node) ⇒ Object

Raises:



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/terret/composition.rb', line 207

def visit_Psych_Nodes_Scalar(node)
  tag = terret_tag(node, CORE_SCALAR_TAGS)
  return register(node, Tagged.new(tag: tag, argument: node.value)) if tag

  value = begin
    super
  rescue Psych::DisallowedClass => e
    raise Error, needs_quoting(node, e)
  end

  # Unlike a Date, a sexagesimal (10:30 -> 37800, 1:2:3 -> 3723) is a plain
  # Integer the restricted loader builds without complaint, so it slips past
  # the DisallowedClass guard above. A colon-bearing plain scalar that came
  # back a number is almost never the base-60 number YAML made of it —
  # refuse it and say to quote it, the same fix the Date guard gives.
  raise Error, sexagesimal_needs_quoting(node) if value.is_a?(Numeric) && node.value.include?(":")

  value
end

#visit_Psych_Nodes_Sequence(node) ⇒ Object



246
247
248
249
# File 'lib/terret/composition.rb', line 246

def visit_Psych_Nodes_Sequence(node)
  refuse_collection_tag!(node, CORE_SEQUENCE_TAGS)
  super
end