summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGreg Houle <greg.houle@shopify.com>2018-04-23 13:53:30 -0400
committerGreg Houle <greg.houle@shopify.com>2018-07-13 14:17:46 -0400
commit4d4439d6d0adfcbd211ea295779315f1baa7dadd (patch)
tree469e905f601cb7c50de3a02dc915a5eed4dc1bda
parent1cc38947ac7dd7239a502f639ecbbec99c0732bb (diff)
downloadpsych-4d4439d6d0adfcbd211ea295779315f1baa7dadd.zip
unifying interface of Psych
-rw-r--r--lib/psych.rb117
-rw-r--r--test/psych/test_exception.rb26
-rw-r--r--test/psych/test_safe_load.rb69
3 files changed, 165 insertions, 47 deletions
diff --git a/lib/psych.rb b/lib/psych.rb
index d314ebf..6830a2f 100644
--- a/lib/psych.rb
+++ b/lib/psych.rb
@@ -230,8 +230,9 @@ require 'psych/class_loader'
module Psych
# The version of libyaml Psych is using
LIBYAML_VERSION = Psych.libyaml_version.join '.'
-
- FALLBACK = Struct.new :to_ruby # :nodoc:
+ # Deprecation guard
+ NOT_GIVEN = Object.new
+ private_constant :NOT_GIVEN
###
# Load +yaml+ in to a Ruby data structure. If multiple documents are
@@ -248,7 +249,7 @@ module Psych
# Psych.load("---\n - a\n - b") # => ['a', 'b']
#
# begin
- # Psych.load("--- `", "file.txt")
+ # Psych.load("--- `", filename: "file.txt")
# rescue Psych::SyntaxError => ex
# ex.file # => 'file.txt'
# ex.message # => "(file.txt): found character that cannot start any token"
@@ -260,8 +261,15 @@ module Psych
# Psych.load("---\n foo: bar") # => {"foo"=>"bar"}
# Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
#
- def self.load yaml, filename = nil, fallback: false, symbolize_names: false
- result = parse(yaml, filename, fallback: FALLBACK.new(fallback))
+ # Raises a TypeError when `yaml` parameter is NilClass
+ #
+ def self.load yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: false, symbolize_names: false
+ if legacy_filename != NOT_GIVEN
+ filename = legacy_filename
+ end
+
+ result = parse(yaml, filename: filename)
+ return fallback unless result
result = result.to_ruby if result
symbolize_names!(result) if symbolize_names
result
@@ -280,27 +288,27 @@ module Psych
# * Hash
#
# Recursive data structures are not allowed by default. Arbitrary classes
- # can be allowed by adding those classes to the +whitelist+. They are
+ # can be allowed by adding those classes to the +whitelist_classes+ keyword argument. They are
# additive. For example, to allow Date deserialization:
#
- # Psych.safe_load(yaml, [Date])
+ # Psych.safe_load(yaml, whitelist_classes: [Date])
#
# Now the Date class can be loaded in addition to the classes listed above.
#
- # Aliases can be explicitly allowed by changing the +aliases+ parameter.
+ # Aliases can be explicitly allowed by changing the +aliases+ keyword argument.
# For example:
#
# x = []
# x << x
# yaml = Psych.dump x
# Psych.safe_load yaml # => raises an exception
- # Psych.safe_load yaml, [], [], true # => loads the aliases
+ # Psych.safe_load yaml, aliases: true # => loads the aliases
#
# A Psych::DisallowedClass exception will be raised if the yaml contains a
# class that isn't in the whitelist.
#
# A Psych::BadAlias exception will be raised if the yaml contains aliases
- # but the +aliases+ parameter is set to false.
+ # but the +aliases+ keyword argument is set to false.
#
# +filename+ will be used in the exception message if any exception is raised
# while parsing.
@@ -311,18 +319,34 @@ module Psych
# Psych.safe_load("---\n foo: bar") # => {"foo"=>"bar"}
# Psych.safe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
#
- def self.safe_load yaml, whitelist_classes = [], whitelist_symbols = [], aliases = false, filename = nil, symbolize_names: false
- result = parse(yaml, filename)
- return unless result
+ def self.safe_load yaml, legacy_whitelist_classes = NOT_GIVEN, legacy_whitelist_symbols = NOT_GIVEN, legacy_aliases = NOT_GIVEN, legacy_filename = NOT_GIVEN, whitelist_classes: [], whitelist_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false
+ if legacy_whitelist_classes != NOT_GIVEN
+ whitelist_classes = legacy_whitelist_classes
+ end
+
+ if legacy_whitelist_symbols != NOT_GIVEN
+ whitelist_symbols = legacy_whitelist_symbols
+ end
+
+ if legacy_aliases != NOT_GIVEN
+ aliases = legacy_aliases
+ end
+
+ if legacy_filename != NOT_GIVEN
+ filename = legacy_filename
+ end
+
+ result = parse(yaml, filename: filename)
+ return fallback unless result
class_loader = ClassLoader::Restricted.new(whitelist_classes.map(&:to_s),
whitelist_symbols.map(&:to_s))
scanner = ScalarScanner.new class_loader
- if aliases
- visitor = Visitors::ToRuby.new scanner, class_loader
- else
- visitor = Visitors::NoAliasRuby.new scanner, class_loader
- end
+ visitor = if aliases
+ Visitors::ToRuby.new scanner, class_loader
+ else
+ Visitors::NoAliasRuby.new scanner, class_loader
+ end
result = visitor.accept result
symbolize_names!(result) if symbolize_names
result
@@ -340,28 +364,38 @@ module Psych
# Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Document:0x00>
#
# begin
- # Psych.parse("--- `", "file.txt")
+ # Psych.parse("--- `", filename: "file.txt")
# rescue Psych::SyntaxError => ex
# ex.file # => 'file.txt'
# ex.message # => "(file.txt): found character that cannot start any token"
# end
#
# See Psych::Nodes for more information about YAML AST.
- def self.parse yaml, filename = nil, fallback: false
- parse_stream(yaml, filename) do |node|
+ def self.parse yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: NOT_GIVEN
+ if legacy_filename != NOT_GIVEN
+ filename = legacy_filename
+ end
+
+ parse_stream(yaml, filename: filename) do |node|
return node
end
- fallback
+
+ if fallback != NOT_GIVEN
+ fallback
+ else
+ false
+ end
end
###
# Parse a file at +filename+. Returns the Psych::Nodes::Document.
#
# Raises a Psych::SyntaxError when a YAML syntax error is detected.
- def self.parse_file filename
- File.open filename, 'r:bom|utf-8' do |f|
- parse f, filename
+ def self.parse_file filename, fallback: false
+ result = File.open filename, 'r:bom|utf-8' do |f|
+ parse f, filename: filename
end
+ result || fallback
end
###
@@ -390,14 +424,20 @@ module Psych
# end
#
# begin
- # Psych.parse_stream("--- `", "file.txt")
+ # Psych.parse_stream("--- `", filename: "file.txt")
# rescue Psych::SyntaxError => ex
# ex.file # => 'file.txt'
# ex.message # => "(file.txt): found character that cannot start any token"
# end
#
+ # Raises a TypeError when NilClass is passed.
+ #
# See Psych::Nodes for more information about YAML AST.
- def self.parse_stream yaml, filename = nil, &block
+ def self.parse_stream yaml, legacy_filename = NOT_GIVEN, filename: nil, &block
+ if legacy_filename != NOT_GIVEN
+ filename = legacy_filename
+ end
+
if block_given?
parser = Psych::Parser.new(Handlers::DocumentStream.new(&block))
parser.parse yaml, filename
@@ -498,14 +538,21 @@ module Psych
# end
# list # => ['foo', 'bar']
#
- def self.load_stream yaml, filename = nil
- if block_given?
- parse_stream(yaml, filename) do |node|
- yield node.to_ruby
- end
- else
- parse_stream(yaml, filename).children.map { |child| child.to_ruby }
+ def self.load_stream yaml, legacy_filename = NOT_GIVEN, filename: nil, fallback: []
+ if legacy_filename != NOT_GIVEN
+ filename = legacy_filename
end
+
+ result = if block_given?
+ parse_stream(yaml, filename: filename) do |node|
+ yield node.to_ruby
+ end
+ else
+ parse_stream(yaml, filename: filename).children.map(&:to_ruby)
+ end
+
+ return fallback if result.is_a?(Array) && result.empty?
+ result
end
###
@@ -514,7 +561,7 @@ module Psych
# the specified +fallback+ return value, which defaults to +false+.
def self.load_file filename, fallback: false
File.open(filename, 'r:bom|utf-8') { |f|
- self.load f, filename, fallback: fallback
+ self.load f, filename: filename, fallback: fallback
}
end
diff --git a/test/psych/test_exception.rb b/test/psych/test_exception.rb
index 3040bfb..3c72f4a 100644
--- a/test/psych/test_exception.rb
+++ b/test/psych/test_exception.rb
@@ -30,9 +30,15 @@ module Psych
assert_nil ex.file
ex = assert_raises(Psych::SyntaxError) do
- Psych.load '--- `', 'meow'
+ Psych.load '--- `', filename: 'meow'
end
assert_equal 'meow', ex.file
+
+ # deprecated interface
+ ex = assert_raises(Psych::SyntaxError) do
+ Psych.load '--- `', 'deprecated'
+ end
+ assert_equal 'deprecated', ex.file
end
def test_psych_parse_stream_takes_file
@@ -43,7 +49,7 @@ module Psych
assert_match '(<unknown>)', ex.message
ex = assert_raises(Psych::SyntaxError) do
- Psych.parse_stream '--- `', 'omg!'
+ Psych.parse_stream '--- `', filename: 'omg!'
end
assert_equal 'omg!', ex.file
assert_match 'omg!', ex.message
@@ -57,9 +63,15 @@ module Psych
assert_match '(<unknown>)', ex.message
ex = assert_raises(Psych::SyntaxError) do
- Psych.load_stream '--- `', 'omg!'
+ Psych.load_stream '--- `', filename: 'omg!'
end
assert_equal 'omg!', ex.file
+
+ # deprecated interface
+ ex = assert_raises(Psych::SyntaxError) do
+ Psych.load_stream '--- `', 'deprecated'
+ end
+ assert_equal 'deprecated', ex.file
end
def test_parse_file_exception
@@ -94,9 +106,15 @@ module Psych
assert_nil ex.file
ex = assert_raises(Psych::SyntaxError) do
- Psych.parse '--- `', 'omg!'
+ Psych.parse '--- `', filename: 'omg!'
end
assert_match 'omg!', ex.message
+
+ # deprecated interface
+ ex = assert_raises(Psych::SyntaxError) do
+ Psych.parse '--- `', 'deprecated'
+ end
+ assert_match 'deprecated', ex.message
end
def test_attributes
diff --git a/test/psych/test_safe_load.rb b/test/psych/test_safe_load.rb
index cf8abeb..82a5f19 100644
--- a/test/psych/test_safe_load.rb
+++ b/test/psych/test_safe_load.rb
@@ -22,6 +22,8 @@ module Psych
def test_explicit_recursion
x = []
x << x
+ assert_equal(x, Psych.safe_load(Psych.dump(x), whitelist_classes: [], whitelist_symbols: [], aliases: true))
+ # deprecated interface
assert_equal(x, Psych.safe_load(Psych.dump(x), [], [], true))
end
@@ -30,6 +32,16 @@ module Psych
assert_raises(Psych::DisallowedClass) do
Psych.safe_load yml
end
+ assert_equal(
+ :foo,
+ Psych.safe_load(
+ yml,
+ whitelist_classes: [Symbol],
+ whitelist_symbols: [:foo]
+ )
+ )
+
+ # deprecated interface
assert_equal(:foo, Psych.safe_load(yml, [Symbol], [:foo]))
end
@@ -38,32 +50,71 @@ module Psych
assert_safe_cycle :foo
end
assert_raises(Psych::DisallowedClass) do
+ Psych.safe_load '--- !ruby/symbol foo', whitelist_classes: []
+ end
+
+ # deprecated interface
+ assert_raises(Psych::DisallowedClass) do
Psych.safe_load '--- !ruby/symbol foo', []
end
- assert_safe_cycle :foo, [Symbol]
- assert_safe_cycle :foo, %w{ Symbol }
+
+ assert_safe_cycle :foo, whitelist_classes: [Symbol]
+ assert_safe_cycle :foo, whitelist_classes: %w{ Symbol }
+ assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', whitelist_classes: [Symbol])
+
+ # deprecated interface
assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', [Symbol])
end
def test_foo
assert_raises(Psych::DisallowedClass) do
+ Psych.safe_load '--- !ruby/object:Foo {}', whitelist_classes: [Foo]
+ end
+
+ # deprecated interface
+ assert_raises(Psych::DisallowedClass) do
Psych.safe_load '--- !ruby/object:Foo {}', [Foo]
end
+
assert_raises(Psych::DisallowedClass) do
assert_safe_cycle Foo.new
end
+ assert_kind_of(Foo, Psych.safe_load(Psych.dump(Foo.new), whitelist_classes: [Foo]))
+
+ # deprecated interface
assert_kind_of(Foo, Psych.safe_load(Psych.dump(Foo.new), [Foo]))
end
X = Struct.new(:x)
def test_struct_depends_on_sym
- assert_safe_cycle(X.new, [X, Symbol])
+ assert_safe_cycle(X.new, whitelist_classes: [X, Symbol])
assert_raises(Psych::DisallowedClass) do
- cycle X.new, [X]
+ cycle X.new, whitelist_classes: [X]
end
end
def test_anon_struct
+ assert Psych.safe_load(<<-eoyml, whitelist_classes: [Struct, Symbol])
+--- !ruby/struct
+ foo: bar
+ eoyml
+
+ assert_raises(Psych::DisallowedClass) do
+ Psych.safe_load(<<-eoyml, whitelist_classes: [Struct])
+--- !ruby/struct
+ foo: bar
+ eoyml
+ end
+
+ assert_raises(Psych::DisallowedClass) do
+ Psych.safe_load(<<-eoyml, whitelist_classes: [Symbol])
+--- !ruby/struct
+ foo: bar
+ eoyml
+ end
+ end
+
+ def test_deprecated_anon_struct
assert Psych.safe_load(<<-eoyml, [Struct, Symbol])
--- !ruby/struct
foo: bar
@@ -98,12 +149,14 @@ module Psych
private
- def cycle object, whitelist = []
- Psych.safe_load(Psych.dump(object), whitelist)
+ def cycle object, whitelist_classes: []
+ Psych.safe_load(Psych.dump(object), whitelist_classes: whitelist_classes)
+ # deprecated interface test
+ Psych.safe_load(Psych.dump(object), whitelist_classes)
end
- def assert_safe_cycle object, whitelist = []
- other = cycle object, whitelist
+ def assert_safe_cycle object, whitelist_classes: []
+ other = cycle object, whitelist_classes: whitelist_classes
assert_equal object, other
end
end