vcr 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.travis.yml +2 -1
- data/CHANGELOG.md +58 -1
- data/Gemfile +0 -6
- data/README.md +12 -3
- data/Rakefile +2 -2
- data/features/.nav +2 -0
- data/features/cassettes/allow_unused_http_interactions.feature +86 -0
- data/features/cassettes/naming.feature +3 -2
- data/features/cassettes/persistence.feature +63 -0
- data/features/configuration/debug_logging.feature +3 -3
- data/features/configuration/ignore_request.feature +1 -1
- data/features/hooks/after_http_request.feature +1 -1
- data/features/step_definitions/cli_steps.rb +33 -0
- data/features/support/env.rb +0 -1
- data/lib/vcr.rb +13 -0
- data/lib/vcr/cassette.rb +57 -44
- data/lib/vcr/cassette/{reader.rb → erb_renderer.rb} +9 -11
- data/lib/vcr/cassette/http_interaction_list.rb +18 -0
- data/lib/vcr/cassette/persisters.rb +42 -0
- data/lib/vcr/cassette/persisters/file_system.rb +60 -0
- data/lib/vcr/cassette/serializers.rb +1 -1
- data/lib/vcr/cassette/serializers/json.rb +1 -1
- data/lib/vcr/cassette/serializers/psych.rb +1 -1
- data/lib/vcr/cassette/serializers/syck.rb +1 -1
- data/lib/vcr/cassette/serializers/yaml.rb +1 -1
- data/lib/vcr/configuration.rb +49 -25
- data/lib/vcr/errors.rb +6 -0
- data/lib/vcr/library_hooks/excon.rb +1 -1
- data/lib/vcr/library_hooks/fakeweb.rb +16 -3
- data/lib/vcr/library_hooks/faraday.rb +13 -0
- data/lib/vcr/library_hooks/typhoeus.rb +5 -1
- data/lib/vcr/library_hooks/webmock.rb +90 -35
- data/lib/vcr/middleware/faraday.rb +12 -4
- data/lib/vcr/request_handler.rb +10 -2
- data/lib/vcr/structs.rb +31 -7
- data/lib/vcr/version.rb +1 -1
- data/script/ci.sh +14 -0
- data/spec/spec_helper.rb +8 -1
- data/spec/support/shared_example_groups/hook_into_http_library.rb +31 -1
- data/spec/vcr/cassette/{reader_spec.rb → erb_renderer_spec.rb} +15 -21
- data/spec/vcr/cassette/http_interaction_list_spec.rb +15 -0
- data/spec/vcr/cassette/persisters/file_system_spec.rb +64 -0
- data/spec/vcr/cassette/persisters_spec.rb +39 -0
- data/spec/vcr/cassette/serializers_spec.rb +4 -3
- data/spec/vcr/cassette_spec.rb +65 -41
- data/spec/vcr/configuration_spec.rb +33 -2
- data/spec/vcr/library_hooks/fakeweb_spec.rb +11 -0
- data/spec/vcr/library_hooks/faraday_spec.rb +24 -0
- data/spec/vcr/library_hooks/webmock_spec.rb +82 -2
- data/spec/vcr/middleware/faraday_spec.rb +32 -0
- data/spec/vcr/structs_spec.rb +60 -20
- data/spec/vcr/util/hooks_spec.rb +7 -0
- data/spec/vcr_spec.rb +7 -0
- data/vcr.gemspec +2 -2
- metadata +31 -26
- data/Guardfile +0 -9
- data/script/FullBuildRakeFile +0 -56
- data/script/full_build +0 -1
- data/script/spec +0 -1
data/lib/vcr.rb
CHANGED
@@ -3,6 +3,7 @@ require 'vcr/util/variable_args_block_caller'
|
|
3
3
|
|
4
4
|
require 'vcr/cassette'
|
5
5
|
require 'vcr/cassette/serializers'
|
6
|
+
require 'vcr/cassette/persisters'
|
6
7
|
require 'vcr/configuration'
|
7
8
|
require 'vcr/deprecations'
|
8
9
|
require 'vcr/errors'
|
@@ -80,12 +81,19 @@ module VCR
|
|
80
81
|
# @option options :allow_playback_repeats [Boolean] Whether or not to
|
81
82
|
# allow a single HTTP interaction to be played back multiple times.
|
82
83
|
# Defaults to false.
|
84
|
+
# @options options :allow_unused_http_interactions [Boolean] If set to
|
85
|
+
# false, an error will be raised if a cassette is ejected before all
|
86
|
+
# previously recorded HTTP interactions have been used.
|
87
|
+
# Defaults to true.
|
83
88
|
# @option options :exclusive [Boolean] Whether or not to use only this
|
84
89
|
# cassette and to completely ignore any cassettes in the cassettes stack.
|
85
90
|
# Defaults to false.
|
86
91
|
# @option options :serialize_with [Symbol] Which serializer to use.
|
87
92
|
# Valid values are :yaml, :syck, :psych, :json or any registered
|
88
93
|
# custom serializer. Defaults to :yaml.
|
94
|
+
# @option options :persist_with [Symbol] Which cassette persister to
|
95
|
+
# use. Defaults to :file_system. You can also register and use a
|
96
|
+
# custom persister.
|
89
97
|
# @option options :preserve_exact_body_bytes [Boolean] Whether or not
|
90
98
|
# to base64 encode the bytes of the requests and responses for this cassette
|
91
99
|
# when serializing it. See also `VCR::Configuration#preserve_exact_body_bytes`.
|
@@ -292,6 +300,11 @@ module VCR
|
|
292
300
|
@cassette_serializers ||= Cassette::Serializers.new
|
293
301
|
end
|
294
302
|
|
303
|
+
# @private
|
304
|
+
def cassette_persisters
|
305
|
+
@cassette_persisters ||= Cassette::Persisters.new
|
306
|
+
end
|
307
|
+
|
295
308
|
# @private
|
296
309
|
def record_http_interaction(interaction)
|
297
310
|
return unless cassette = current_cassette
|
data/lib/vcr/cassette.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'erb'
|
3
|
-
|
4
1
|
require 'vcr/cassette/http_interaction_list'
|
5
|
-
require 'vcr/cassette/
|
2
|
+
require 'vcr/cassette/erb_renderer'
|
6
3
|
require 'vcr/cassette/serializers'
|
7
4
|
|
8
5
|
module VCR
|
@@ -46,35 +43,14 @@ module VCR
|
|
46
43
|
# @param (see VCR#insert_cassette)
|
47
44
|
# @see VCR#insert_cassette
|
48
45
|
def initialize(name, options = {})
|
49
|
-
|
50
|
-
|
51
|
-
:record, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
|
52
|
-
:update_content_length_header, :allow_playback_repeats, :exclusive,
|
53
|
-
:serialize_with, :preserve_exact_body_bytes, :decode_compressed_response
|
54
|
-
]
|
55
|
-
|
56
|
-
if invalid_options.size > 0
|
57
|
-
raise ArgumentError.new("You passed the following invalid options to VCR::Cassette.new: #{invalid_options.inspect}.")
|
58
|
-
end
|
59
|
-
|
60
|
-
@name = name
|
61
|
-
@record_mode = options[:record]
|
62
|
-
@erb = options[:erb]
|
63
|
-
@match_requests_on = options[:match_requests_on]
|
64
|
-
@re_record_interval = options[:re_record_interval]
|
65
|
-
@tags = Array(options.fetch(:tags) { options[:tag] })
|
66
|
-
@tags << :update_content_length_header if options[:update_content_length_header]
|
67
|
-
@tags << :preserve_exact_body_bytes if options[:preserve_exact_body_bytes]
|
68
|
-
@tags << :decode_compressed_response if options[:decode_compressed_response]
|
69
|
-
@allow_playback_repeats = options[:allow_playback_repeats]
|
70
|
-
@exclusive = options[:exclusive]
|
71
|
-
@serializer = VCR.cassette_serializers[options[:serialize_with]]
|
72
|
-
@record_mode = :all if should_re_record?
|
73
|
-
@parent_list = @exclusive ? HTTPInteractionList::NullList : VCR.http_interactions
|
46
|
+
@name = name
|
47
|
+
@options = VCR.configuration.default_cassette_options.merge(options)
|
74
48
|
|
49
|
+
assert_valid_options!
|
50
|
+
extract_options
|
75
51
|
raise_error_unless_valid_record_mode
|
76
52
|
|
77
|
-
log "Initialized with options: #{options.inspect}"
|
53
|
+
log "Initialized with options: #{@options.inspect}"
|
78
54
|
end
|
79
55
|
|
80
56
|
# Ejects the current cassette. The cassette will no longer be used.
|
@@ -82,6 +58,7 @@ module VCR
|
|
82
58
|
# disk.
|
83
59
|
def eject
|
84
60
|
write_recorded_interactions_to_disk
|
61
|
+
http_interactions.assert_no_unused_interactions! unless @allow_unused_http_interactions
|
85
62
|
end
|
86
63
|
|
87
64
|
# @private
|
@@ -106,17 +83,21 @@ module VCR
|
|
106
83
|
end
|
107
84
|
|
108
85
|
# @return [String] The file for this cassette.
|
86
|
+
# @raise [NotImplementedError] if the configured cassette persister
|
87
|
+
# does not support resolving file paths.
|
109
88
|
# @note VCR will take care of sanitizing the cassette name to make it a valid file name.
|
110
89
|
def file
|
111
|
-
|
112
|
-
|
90
|
+
unless @persister.respond_to?(:absolute_path_to_file)
|
91
|
+
raise NotImplementedError, "The configured cassette persister does not support resolving file paths"
|
92
|
+
end
|
93
|
+
@persister.absolute_path_to_file(storage_key)
|
113
94
|
end
|
114
95
|
|
115
96
|
# @return [Boolean] Whether or not the cassette is recording.
|
116
97
|
def recording?
|
117
98
|
case record_mode
|
118
99
|
when :none; false
|
119
|
-
when :once;
|
100
|
+
when :once; raw_cassette_bytes.to_s.empty?
|
120
101
|
else true
|
121
102
|
end
|
122
103
|
end
|
@@ -131,8 +112,44 @@ module VCR
|
|
131
112
|
|
132
113
|
private
|
133
114
|
|
115
|
+
def assert_valid_options!
|
116
|
+
invalid_options = @options.keys - [
|
117
|
+
:record, :erb, :match_requests_on, :re_record_interval, :tag, :tags,
|
118
|
+
:update_content_length_header, :allow_playback_repeats, :allow_unused_http_interactions,
|
119
|
+
:exclusive, :serialize_with, :preserve_exact_body_bytes, :decode_compressed_response,
|
120
|
+
:persist_with
|
121
|
+
]
|
122
|
+
|
123
|
+
if invalid_options.size > 0
|
124
|
+
raise ArgumentError.new("You passed the following invalid options to VCR::Cassette.new: #{invalid_options.inspect}.")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def extract_options
|
129
|
+
[:erb, :match_requests_on, :re_record_interval,
|
130
|
+
:allow_playback_repeats, :allow_unused_http_interactions, :exclusive].each do |name|
|
131
|
+
instance_variable_set("@#{name}", @options[name])
|
132
|
+
end
|
133
|
+
|
134
|
+
assign_tags
|
135
|
+
|
136
|
+
@record_mode = @options[:record]
|
137
|
+
@serializer = VCR.cassette_serializers[@options[:serialize_with]]
|
138
|
+
@persister = VCR.cassette_persisters[@options[:persist_with]]
|
139
|
+
@record_mode = :all if should_re_record?
|
140
|
+
@parent_list = @exclusive ? HTTPInteractionList::NullList : VCR.http_interactions
|
141
|
+
end
|
142
|
+
|
143
|
+
def assign_tags
|
144
|
+
@tags = Array(@options.fetch(:tags) { @options[:tag] })
|
145
|
+
|
146
|
+
[:update_content_length_header, :preserve_exact_body_bytes, :decode_compressed_response].each do |tag|
|
147
|
+
@tags << tag if @options[tag]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
134
151
|
def previously_recorded_interactions
|
135
|
-
@previously_recorded_interactions ||= if
|
152
|
+
@previously_recorded_interactions ||= if !raw_cassette_bytes.to_s.empty?
|
136
153
|
deserialized_hash['http_interactions'].map { |h| HTTPInteraction.from_hash(h) }.tap do |interactions|
|
137
154
|
invoke_hook(:before_playback, interactions)
|
138
155
|
|
@@ -145,8 +162,8 @@ module VCR
|
|
145
162
|
end
|
146
163
|
end
|
147
164
|
|
148
|
-
def
|
149
|
-
name.
|
165
|
+
def storage_key
|
166
|
+
@storage_key ||= [name, @serializer.file_extension].join('.')
|
150
167
|
end
|
151
168
|
|
152
169
|
def raise_error_unless_valid_record_mode
|
@@ -159,7 +176,6 @@ module VCR
|
|
159
176
|
return false unless @re_record_interval
|
160
177
|
previously_recorded_at = earliest_interaction_recorded_at
|
161
178
|
return false unless previously_recorded_at
|
162
|
-
return false unless File.exist?(file)
|
163
179
|
|
164
180
|
now = Time.now
|
165
181
|
|
@@ -189,8 +205,8 @@ module VCR
|
|
189
205
|
record_mode == :all
|
190
206
|
end
|
191
207
|
|
192
|
-
def
|
193
|
-
VCR::Cassette::
|
208
|
+
def raw_cassette_bytes
|
209
|
+
@raw_cassette_bytes ||= VCR::Cassette::ERBRenderer.new(@persister[storage_key], erb, name).render
|
194
210
|
end
|
195
211
|
|
196
212
|
def merged_interactions
|
@@ -213,14 +229,11 @@ module VCR
|
|
213
229
|
end
|
214
230
|
|
215
231
|
def write_recorded_interactions_to_disk
|
216
|
-
return unless VCR.configuration.cassette_library_dir
|
217
232
|
return if new_recorded_interactions.none?
|
218
233
|
hash = serializable_hash
|
219
234
|
return if hash["http_interactions"].none?
|
220
235
|
|
221
|
-
|
222
|
-
FileUtils.mkdir_p directory unless File.exist?(directory)
|
223
|
-
File.open(file, 'w') { |f| f.write @serializer.serialize(hash) }
|
236
|
+
@persister[storage_key] = @serializer.serialize(hash)
|
224
237
|
end
|
225
238
|
|
226
239
|
def invoke_hook(type, interactions)
|
@@ -232,7 +245,7 @@ module VCR
|
|
232
245
|
end
|
233
246
|
|
234
247
|
def deserialized_hash
|
235
|
-
@deserialized_hash ||= @serializer.deserialize(
|
248
|
+
@deserialized_hash ||= @serializer.deserialize(raw_cassette_bytes).tap do |hash|
|
236
249
|
unless hash.is_a?(Hash) && hash['http_interactions'].is_a?(Array)
|
237
250
|
raise Errors::InvalidCassetteFormatError.new \
|
238
251
|
"#{file} does not appear to be a valid VCR 2.0 cassette. " +
|
@@ -1,13 +1,15 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
1
3
|
module VCR
|
2
4
|
class Cassette
|
3
5
|
# @private
|
4
|
-
class
|
5
|
-
def initialize(
|
6
|
-
@
|
6
|
+
class ERBRenderer
|
7
|
+
def initialize(raw_template, erb, cassette_name=nil)
|
8
|
+
@raw_template, @erb, @cassette_name = raw_template, erb, cassette_name
|
7
9
|
end
|
8
10
|
|
9
|
-
def
|
10
|
-
return
|
11
|
+
def render
|
12
|
+
return @raw_template if @raw_template.nil? || !use_erb?
|
11
13
|
binding = binding_for_variables if erb_variables
|
12
14
|
template.result(binding)
|
13
15
|
rescue NameError => e
|
@@ -20,7 +22,7 @@ module VCR
|
|
20
22
|
example_hash = (erb_variables || {}).merge(e.name => 'some value')
|
21
23
|
|
22
24
|
raise Errors::MissingERBVariableError.new(
|
23
|
-
"The ERB in the #{@
|
25
|
+
"The ERB in the #{@cassette_name} cassette file references undefined variable #{e.name}. " +
|
24
26
|
"Pass it to the cassette using :erb => #{ example_hash.inspect }."
|
25
27
|
)
|
26
28
|
end
|
@@ -33,12 +35,8 @@ module VCR
|
|
33
35
|
@erb if @erb.is_a?(Hash)
|
34
36
|
end
|
35
37
|
|
36
|
-
def file_content
|
37
|
-
@file_content ||= File.read(@file_name)
|
38
|
-
end
|
39
|
-
|
40
38
|
def template
|
41
|
-
@template ||= ERB.new(
|
39
|
+
@template ||= ERB.new(@raw_template)
|
42
40
|
end
|
43
41
|
|
44
42
|
@@struct_cache = Hash.new do |hash, attributes|
|
@@ -54,8 +54,26 @@ module VCR
|
|
54
54
|
@interactions.size
|
55
55
|
end
|
56
56
|
|
57
|
+
# Checks if there are no unused interactions left.
|
58
|
+
#
|
59
|
+
# @raise [VCR::Errors::UnusedHTTPInteractionError] if not all interactions were played back.
|
60
|
+
def assert_no_unused_interactions!
|
61
|
+
return unless has_unused_interactions?
|
62
|
+
|
63
|
+
descriptions = @interactions.map do |i|
|
64
|
+
" - #{request_summary(i.request)} => #{response_summary(i.response)}"
|
65
|
+
end.join("\n")
|
66
|
+
|
67
|
+
raise Errors::UnusedHTTPInteractionError, "There are unused HTTP interactions left in the cassette:\n#{descriptions}"
|
68
|
+
end
|
69
|
+
|
57
70
|
private
|
58
71
|
|
72
|
+
# @return [Boolean] Whether or not there are unused interactions left in the list.
|
73
|
+
def has_unused_interactions?
|
74
|
+
@interactions.size > 0
|
75
|
+
end
|
76
|
+
|
59
77
|
def request_summary(request)
|
60
78
|
super(request, @request_matchers)
|
61
79
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module VCR
|
2
|
+
class Cassette
|
3
|
+
# Keeps track of the cassette persisters in a hash-like object.
|
4
|
+
class Persisters
|
5
|
+
autoload :FileSystem, 'vcr/cassette/persisters/file_system'
|
6
|
+
|
7
|
+
# @private
|
8
|
+
def initialize
|
9
|
+
@persisters = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Gets the named persister.
|
13
|
+
#
|
14
|
+
# @param name [Symbol] the name of the persister
|
15
|
+
# @return the named persister
|
16
|
+
# @raise [ArgumentError] if there is not a persister for the given name
|
17
|
+
def [](name)
|
18
|
+
@persisters.fetch(name) do |_|
|
19
|
+
@persisters[name] = case name
|
20
|
+
when :file_system then FileSystem
|
21
|
+
else raise ArgumentError, "The requested VCR cassette persister " +
|
22
|
+
"(#{name.inspect}) is not registered."
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Registers a persister.
|
28
|
+
#
|
29
|
+
# @param name [Symbol] the name of the persister
|
30
|
+
# @param value [#[], #[]=] the persister object. It must implement `[]` and `[]=`.
|
31
|
+
def []=(name, value)
|
32
|
+
if @persisters.has_key?(name)
|
33
|
+
warn "WARNING: There is already a VCR cassette persister " +
|
34
|
+
"registered for #{name.inspect}. Overriding it."
|
35
|
+
end
|
36
|
+
|
37
|
+
@persisters[name] = value
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module VCR
|
4
|
+
class Cassette
|
5
|
+
class Persisters
|
6
|
+
# The only built-in cassette persister. Persists cassettes to the file system.
|
7
|
+
module FileSystem
|
8
|
+
extend self
|
9
|
+
|
10
|
+
# @private
|
11
|
+
attr_reader :storage_location
|
12
|
+
|
13
|
+
# @private
|
14
|
+
def storage_location=(dir)
|
15
|
+
FileUtils.mkdir_p(dir) if dir
|
16
|
+
@storage_location = dir ? absolute_path_for(dir) : nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gets the cassette for the given storage key (file name).
|
20
|
+
#
|
21
|
+
# @param [String] file_name the file name
|
22
|
+
# @return [String] the cassette content
|
23
|
+
def [](file_name)
|
24
|
+
path = absolute_path_to_file(file_name)
|
25
|
+
return nil unless File.exist?(path)
|
26
|
+
File.read(path)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets the cassette for the given storage key (file name).
|
30
|
+
#
|
31
|
+
# @param [String] file_name the file name
|
32
|
+
# @param [String] content the content to store
|
33
|
+
def []=(file_name, content)
|
34
|
+
path = absolute_path_to_file(file_name)
|
35
|
+
directory = File.dirname(path)
|
36
|
+
FileUtils.mkdir_p(directory) unless File.exist?(directory)
|
37
|
+
File.open(path, 'w') { |f| f.write(content) }
|
38
|
+
end
|
39
|
+
|
40
|
+
# @private
|
41
|
+
def absolute_path_to_file(file_name)
|
42
|
+
return nil unless storage_location
|
43
|
+
File.join(storage_location, sanitized_file_name_from(file_name))
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def absolute_path_for(path)
|
49
|
+
Dir.chdir(path) { Dir.pwd }
|
50
|
+
end
|
51
|
+
|
52
|
+
def sanitized_file_name_from(file_name)
|
53
|
+
parts = file_name.to_s.split('.')
|
54
|
+
file_extension = '.' + parts.pop if parts.size > 1
|
55
|
+
parts.join('.').gsub(/[^\w\-\/]+/, '_') + file_extension.to_s
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -33,7 +33,7 @@ module VCR
|
|
33
33
|
#
|
34
34
|
# @param name [Symbol] the name of the serializer
|
35
35
|
# @param value [#file_extension, #serialize, #deserialize] the serializer object. It must implement
|
36
|
-
#
|
36
|
+
# `file_extension()`, `serialize(Hash)` and `deserialize(String)`.
|
37
37
|
def []=(name, value)
|
38
38
|
if @serializers.has_key?(name)
|
39
39
|
warn "WARNING: There is already a VCR cassette serializer registered for #{name.inspect}. Overriding it."
|
@@ -36,7 +36,7 @@ module VCR
|
|
36
36
|
# Deserializes the given string using `MultiJson`.
|
37
37
|
#
|
38
38
|
# @param [String] string the JSON string
|
39
|
-
# @
|
39
|
+
# @return [Hash] the deserialized object
|
40
40
|
def deserialize(string)
|
41
41
|
handle_encoding_errors do
|
42
42
|
MultiJson.decode(string)
|
@@ -35,7 +35,7 @@ module VCR
|
|
35
35
|
# Deserializes the given string using Psych.
|
36
36
|
#
|
37
37
|
# @param [String] string the YAML string
|
38
|
-
# @
|
38
|
+
# @return [Hash] the deserialized object
|
39
39
|
def deserialize(string)
|
40
40
|
handle_encoding_errors do
|
41
41
|
::Psych.load(string)
|
@@ -35,7 +35,7 @@ module VCR
|
|
35
35
|
# Deserializes the given string using Syck.
|
36
36
|
#
|
37
37
|
# @param [String] string the YAML string
|
38
|
-
# @
|
38
|
+
# @return [Hash] the deserialized object
|
39
39
|
def deserialize(string)
|
40
40
|
handle_encoding_errors do
|
41
41
|
using_syck { ::YAML.load(string) }
|
@@ -37,7 +37,7 @@ module VCR
|
|
37
37
|
# Deserializes the given string using YAML.
|
38
38
|
#
|
39
39
|
# @param [String] string the YAML string
|
40
|
-
# @
|
40
|
+
# @return [Hash] the deserialized object
|
41
41
|
def deserialize(string)
|
42
42
|
handle_encoding_errors do
|
43
43
|
::YAML.load(string)
|