Class: YamlWrapper

Inherits:
Object show all
Defined in:
lib/ceedling/yaml_wrapper.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.psych_safe_load_uses_keywords?Boolean

safe_load's calling convention changed between Psych 3 (bundled with Ruby 3.0) and Psych 4+ (bundled with Ruby 3.1+, and installable on any Ruby >= 3.0):

Psych 4+:  safe_load(yaml, permitted_classes: [], aliases: false, ...)   # keyword args
Psych 3:   safe_load(yaml, permitted_classes = [], permitted_symbols = [], aliases = false)  # positional

Psych::VERSION is fixed for the life of the process, and #load/#load_string are called repeatedly over a Ceedling run, so the version check is memoized rather than repeated per call.

Returns:

  • (Boolean)


32
33
34
35
# File 'lib/ceedling/yaml_wrapper.rb', line 32

def self.psych_safe_load_uses_keywords?
  return @psych_safe_load_uses_keywords if defined?(@psych_safe_load_uses_keywords)
  @psych_safe_load_uses_keywords = (Gem::Version.new(Psych::VERSION) >= Gem::Version.new("4.0"))
end

Instance Method Details

#dump(filepath, structure) ⇒ Object



91
92
93
94
95
# File 'lib/ceedling/yaml_wrapper.rb', line 91

def dump(filepath, structure)
  File.open(filepath, 'w') do |output|
    YAML.dump(structure, output)
  end
end

#load(filepath) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/ceedling/yaml_wrapper.rb', line 37

def load(filepath)
  begin
    source = File.read(filepath)
  rescue Errno::ENOENT
    raise YamlLoadException.new(
      reason: :not_found, source: filepath, original_error: nil,
      message: "Could not find YAML file ⏩️ #{filepath}"
    )
  end

  load_string(source, source_label: filepath)
end

#load_string(source, source_label: '<inline YAML>') ⇒ Object



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
# File 'lib/ceedling/yaml_wrapper.rb', line 50

def load_string(source, source_label: '<inline YAML>')
  if self.class.psych_safe_load_uses_keywords?
    YAML.safe_load(source, permitted_classes: [Symbol], aliases: true, filename: source_label)
  else
    YAML.safe_load(source, [Symbol], [], true, source_label)
  end

# The YAML parsed fine syntactically, but it contains a tag/type (e.g. `!ruby/object:...`)
# that isn't on safe_load's permitted-class allowlist. This is Psych doing its safety job,
# not a content typo -- surfaced separately from :syntax so the user understands the fix is
# "remove this Ruby-object tag," not "fix a formatting mistake."
rescue Psych::DisallowedClass => e
  raise YamlLoadException.new(
    reason: :unsafe, source: source_label, original_error: e,
    message: "YAML content in #{source_label} uses a Ruby type not permitted by safe YAML loading ⏩️ #{e.message}"
  )

# Catches every other Psych::Exception subclass (Psych::SyntaxError, Psych::BadAlias,
# Psych::AnchorNotDefined, etc.) -- i.e. the YAML text itself is malformed: bad syntax,
# a dangling alias reference, and similar authoring mistakes the user needs to fix in
# the YAML source. Ordered after Psych::DisallowedClass, which is a subclass of
# Psych::Exception and needs its own more specific message.
rescue Psych::Exception => e
  raise YamlLoadException.new(
    reason: :syntax, source: source_label, original_error: e,
    message: "Malformed YAML content in #{source_label} ⏩️ #{e.message}"
  )

# Not a problem with the YAML content at all -- the installed Psych version rejected one
# of the arguments safe_load was called with. self.class.psych_safe_load_uses_keywords?
# already picks the right calling convention for the installed Psych, so this should be
# unreachable in practice; it exists as a last-resort safety net for an unanticipated
# Psych version/interface mismatch, reported distinctly so it isn't mistaken for a user
# YAML-authoring error.
rescue ArgumentError => e
  raise YamlLoadException.new(
    reason: :incompatible, source: source_label, original_error: e,
    message: "Installed Psych YAML library (#{Psych::VERSION}) does not support a safe-loading feature Ceedling requires ⏩️ #{e.message}"
  )
end