Allow options on Mash.load (#474)
`Mash.load` uses the Ruby standard library to load Yaml-serialized files into a Mash. The original implementation used `YAML.load` for this purpose. However, that method is inherently unsafe so we switched to using `YAML.safe_load`. Safely loading Yaml files has many different domain-specific configuration flags that we did not, by default, expose. This change introduces the ability to configure the safe loading of Yaml files so that all types of Yaml can be loaded when necessary using the flags from the standard library. This implementation preserves the backwards-compatibility with the prior implementation so that it should not require updates from users of the current `Mash.load` behavior. For those who this change affects, we included upgrading documentation to ease the transition.
This commit is contained in:
parent
dc64b1024c
commit
30ab2a3cb0
|
@ -1,6 +1,6 @@
|
|||
# This configuration was generated by
|
||||
# `rubocop --auto-gen-config`
|
||||
# on 2018-09-30 14:46:03 -0500 using RuboCop version 0.52.1.
|
||||
# on 2019-03-22 11:21:24 -0400 using RuboCop version 0.52.1.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
|
@ -19,7 +19,7 @@ Metrics/ClassLength:
|
|||
Metrics/CyclomaticComplexity:
|
||||
Max: 11
|
||||
|
||||
# Offense count: 17
|
||||
# Offense count: 19
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/MethodLength:
|
||||
Max: 28
|
||||
|
@ -28,6 +28,6 @@ Metrics/MethodLength:
|
|||
Metrics/PerceivedComplexity:
|
||||
Max: 10
|
||||
|
||||
# Offense count: 37
|
||||
# Offense count: 39
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
|
|
@ -13,6 +13,7 @@ scheme are considered to be bugs.
|
|||
### Added
|
||||
|
||||
* [#323](https://github.com/intridea/hashie/pull/323): Added `Hashie::Extensions::Mash::DefineAccessors` - [@marshall-lee](https://github.com/marshall-lee).
|
||||
* [#474](https://github.com/intridea/hashie/pull/474): Expose `YAML#safe_load` options in `Mash#load` - [@riouruma](https://github.com/riouruma), [@dblock](https://github.com/dblock).
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
10
README.md
10
README.md
|
@ -566,7 +566,7 @@ Twitter.extend mash.to_module # NOTE: if you want another name than settings, ca
|
|||
Twitter.settings.api_key # => 'abcd'
|
||||
```
|
||||
|
||||
You can use another parser (by default: YamlErbParser):
|
||||
You can use another parser (by default: [YamlErbParser](lib/hashie/extensions/parsers/yaml_erb_parser.rb)):
|
||||
|
||||
```
|
||||
#/etc/data/user.csv
|
||||
|
@ -582,6 +582,14 @@ mash = Mash.load('data/user.csv', parser: MyCustomCsvParser)
|
|||
mash[1] #=> { name: 'John', lastname: 'Doe' }
|
||||
```
|
||||
|
||||
The `Mash#load` method calls `YAML.safe_load(path, [], [], true)`.
|
||||
|
||||
Specify `whitelist_symbols`, `whitelist_classes` and `aliases` options as needed.
|
||||
|
||||
```ruby
|
||||
Mash.load('data/user.csv', whitelist_classes: [Symbol], whitelist_symbols: [], aliases: false)
|
||||
```
|
||||
|
||||
### Mash Extension: KeepOriginalKeys
|
||||
|
||||
This extension can be mixed into a Mash to keep the form of any keys passed directly into the Mash. By default, Mash converts keys to strings to give indifferent access. This extension still allows indifferent access, but keeps the form of the keys to eliminate confusion when you're not expecting the keys to change.
|
||||
|
|
35
UPGRADING.md
35
UPGRADING.md
|
@ -1,6 +1,41 @@
|
|||
Upgrading Hashie
|
||||
================
|
||||
|
||||
### Upgrading to 3.7.0
|
||||
|
||||
#### Mash#load takes options
|
||||
|
||||
The `Hashie::Mash#load` method now accepts options, changing the interface of `Parser#initialize`. If you have a custom parser, you must update its `initialize` method.
|
||||
|
||||
For example, `Hashie::Extensions::Parsers::YamlErbParser` now accepts `whitelist_classes`, `whitelist_symbols` and `aliases` options.
|
||||
|
||||
Before:
|
||||
|
||||
```ruby
|
||||
class Hashie::Extensions::Parsers::YamlErbParser
|
||||
def initialize(file_path)
|
||||
@file_path = file_path
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
After:
|
||||
|
||||
```ruby
|
||||
class Hashie::Extensions::Parsers::YamlErbParser
|
||||
def initialize(file_path, options = {})
|
||||
@file_path = file_path
|
||||
@options = options
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Options can now be passed into `Mash#load`.
|
||||
|
||||
```ruby
|
||||
Mash.load(filename, whitelist_classes: [])
|
||||
```
|
||||
|
||||
### Upgrading to 3.5.2
|
||||
|
||||
#### Disable logging in Mash subclasses
|
||||
|
|
|
@ -6,19 +6,23 @@ module Hashie
|
|||
module Extensions
|
||||
module Parsers
|
||||
class YamlErbParser
|
||||
def initialize(file_path)
|
||||
def initialize(file_path, options = {})
|
||||
@content = File.read(file_path)
|
||||
@file_path = file_path.is_a?(Pathname) ? file_path.to_s : file_path
|
||||
@options = options
|
||||
end
|
||||
|
||||
def perform
|
||||
template = ERB.new(@content)
|
||||
template.filename = @file_path
|
||||
YAML.safe_load template.result, [], [], true
|
||||
whitelist_classes = @options.fetch(:whitelist_classes) { [] }
|
||||
whitelist_symbols = @options.fetch(:whitelist_symbols) { [] }
|
||||
aliases = @options.fetch(:aliases) { true }
|
||||
YAML.safe_load template.result, whitelist_classes, whitelist_symbols, aliases
|
||||
end
|
||||
|
||||
def self.perform(file_path)
|
||||
new(file_path).perform
|
||||
def self.perform(file_path, options = {})
|
||||
new(file_path, options).perform
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -107,7 +107,7 @@ module Hashie
|
|||
raise ArgumentError, "The following file doesn't exist: #{path}" unless File.file?(path)
|
||||
|
||||
parser = options.fetch(:parser) { Hashie::Extensions::Parsers::YamlErbParser }
|
||||
@_mashes[path] = new(parser.perform(path)).freeze
|
||||
@_mashes[path] = new(parser.perform(path, options.except(:parser))).freeze
|
||||
end
|
||||
|
||||
def to_module(mash_method_name = :settings)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module Hashie
|
||||
VERSION = '3.6.1'.freeze
|
||||
VERSION = '3.7.0'.freeze
|
||||
end
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
:user_icon:
|
||||
:width: 200
|
||||
:height: 200
|
|
@ -645,7 +645,7 @@ describe Hashie::Mash do
|
|||
context 'if the file exists' do
|
||||
before do
|
||||
expect(File).to receive(:file?).with(path).and_return(true)
|
||||
expect(parser).to receive(:perform).with(path).and_return(config)
|
||||
expect(parser).to receive(:perform).with(path, {}).and_return(config)
|
||||
end
|
||||
|
||||
it { is_expected.to be_a(Hashie::Mash) }
|
||||
|
@ -677,7 +677,7 @@ describe Hashie::Mash do
|
|||
|
||||
before do
|
||||
expect(File).to receive(:file?).with(path).and_return(true)
|
||||
expect(parser).to receive(:perform).with(path).and_return(config)
|
||||
expect(parser).to receive(:perform).with(path, {}).and_return(config)
|
||||
end
|
||||
|
||||
it 'return a Mash from a file' do
|
||||
|
@ -693,8 +693,8 @@ describe Hashie::Mash do
|
|||
before do
|
||||
expect(File).to receive(:file?).with(path).and_return(true)
|
||||
expect(File).to receive(:file?).with("#{path}+1").and_return(true)
|
||||
expect(parser).to receive(:perform).once.with(path).and_return(config)
|
||||
expect(parser).to receive(:perform).once.with("#{path}+1").and_return(config)
|
||||
expect(parser).to receive(:perform).once.with(path, {}).and_return(config)
|
||||
expect(parser).to receive(:perform).once.with("#{path}+1", {}).and_return(config)
|
||||
end
|
||||
|
||||
it 'cache the loaded yml file', :test_cache do
|
||||
|
@ -710,9 +710,45 @@ describe Hashie::Mash do
|
|||
context 'when the file has aliases in it' do
|
||||
it 'can use the aliases and does not raise an error' do
|
||||
mash = Hashie::Mash.load('spec/fixtures/yaml_with_aliases.yml')
|
||||
|
||||
expect(mash.company_a.accounts.admin.password).to eq('secret')
|
||||
end
|
||||
it 'can override the value of aliases' do
|
||||
expect do
|
||||
Hashie::Mash.load('spec/fixtures/yaml_with_aliases.yml', aliases: false)
|
||||
end.to raise_error Psych::BadAlias, /base_accounts/
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the file has symbols' do
|
||||
it 'can override the value of whitelist_classes' do
|
||||
mash = Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml', whitelist_classes: [Symbol])
|
||||
expect(mash.user_icon.width).to eq(200)
|
||||
end
|
||||
it 'uses defaults for whitelist_classes' do
|
||||
expect do
|
||||
Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml')
|
||||
end.to raise_error Psych::DisallowedClass, /Symbol/
|
||||
end
|
||||
it 'can override the value of whitelist_symbols' do
|
||||
mash = Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml',
|
||||
whitelist_classes: [Symbol],
|
||||
whitelist_symbols: %i[
|
||||
user_icon
|
||||
width
|
||||
height
|
||||
])
|
||||
expect(mash.user_icon.width).to eq(200)
|
||||
end
|
||||
it 'raises an error on insufficient whitelist_symbols' do
|
||||
expect do
|
||||
Hashie::Mash.load('spec/fixtures/yaml_with_symbols.yml',
|
||||
whitelist_classes: [Symbol],
|
||||
whitelist_symbols: %i[
|
||||
user_icon
|
||||
width
|
||||
])
|
||||
end.to raise_error Psych::DisallowedClass, /Symbol/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue