vcr 2.1.1 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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)
|