Packages
exvcr
0.9.1
0.17.1
0.17.0
0.16.0
0.15.2
0.15.1
0.15.0
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.5
0.13.4
0.13.3
0.13.2
0.13.1
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.2
0.11.1
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.9
0.3.8
0.3.7
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.7
0.1.6
0.1.5
HTTP request/response recording library for elixir, inspired by VCR.
Current section
Files
Jump to
Current section
Files
lib/exvcr/handler.ex
defmodule ExVCR.Handler do
@moduledoc """
Provide operations for request/response.
"""
alias ExVCR.Recorder
alias ExVCR.Actor.Options
alias ExVCR.Util
@doc """
Get response from either server or cache.
"""
def get_response(recorder, request) do
get_response_from_cache(request, recorder) || get_response_from_server(request, recorder)
end
@doc """
Get response from the cache (pre-recorded cassettes).
"""
def get_response_from_cache(request, recorder) do
recorder_options = Options.get(recorder.options)
adapter = ExVCR.Recorder.options(recorder)[:adapter]
params = adapter.generate_keys_for_request(request)
{response, responses} = find_response(Recorder.get(recorder), params, recorder_options)
response = adapter.hook_response_from_cache(request, response)
case { response, stub_mode?(recorder_options) } do
{ nil, true } ->
raise ExVCR.InvalidRequestError,
message: "response for [URL:#{params[:url]}, METHOD:#{params[:method]}] was not found"
{ nil, false } ->
nil
{ response, _ } ->
ExVCR.Checker.add_cache_count(recorder)
Recorder.set(responses, recorder)
adapter.get_response_value_from_cache(response)
end
end
defp stub_mode?(options) do
options[:custom] == true || options[:stub] != nil
end
defp stub_with_non_empty_request_body?(options) do
options[:stub] != nil && List.first(options[:stub]).request.request_body != ""
end
defp has_match_requests_on(type, options) do
flags = options[:match_requests_on] || []
if is_list(flags) == false do
raise "Invalid match_requests_on option is specified - #{inspect flags}"
end
Enum.member?(flags, type)
end
defp find_response(responses, keys, recorder_options), do: find_response(responses, keys, recorder_options, [])
defp find_response([], _keys, _recorder_options, _acc), do: {nil, nil}
defp find_response([response|tail], keys, recorder_options, acc) do
case match_response(response, keys, recorder_options) do
true -> {response[:response], Enum.reverse(acc) ++ tail ++ [response]}
false -> find_response(tail, keys, recorder_options, [response|acc])
end
end
defp match_response(response, keys, recorder_options) do
match_by_url(response, keys, recorder_options)
and match_by_method(response, keys)
and match_by_request_body(response, keys, recorder_options)
and match_by_headers(response, keys, recorder_options)
end
defp match_by_url(response, keys, recorder_options) do
request_url = response[:request].url
key_url = to_string(keys[:url]) |> ExVCR.Filter.filter_sensitive_data
if stub_mode?(recorder_options) do
if match = Regex.run(~r/~r\/(.+)\//, request_url) do
pattern = Regex.compile!(Enum.at(match, 1))
Regex.match?(pattern, key_url)
else
request_url == key_url
end
else
request_url = parse_url(request_url, recorder_options)
key_url = parse_url(key_url, recorder_options)
request_url == key_url
end
end
defp match_by_headers(response, keys, options) do
if has_match_requests_on(:headers, options) do
response[:request].headers
|> Enum.to_list
|> Util.stringify_keys
|> Keyword.equal?(keys[:headers])
else
true
end
end
defp parse_url(url, options) do
if has_match_requests_on(:query, options) do
to_string(url)
else
to_string(url) |> ExVCR.Filter.strip_query_params
end
end
defp match_by_method(head, params) do
if params[:method] == nil || head[:request].method == nil do
true
else
to_string(params[:method]) == head[:request].method
end
end
defp match_by_request_body(response, keys, recorder_options) do
if stub_with_non_empty_request_body?(recorder_options) || has_match_requests_on(:request_body, recorder_options) do
request_body = response[:request].body || response[:request].request_body
key_body = keys[:request_body] |> to_string |> ExVCR.Filter.filter_sensitive_data
if match = Regex.run(~r/~r\/(.+)\//, request_body) do
pattern = Regex.compile!(Enum.at(match, 1))
Regex.match?(pattern, key_body)
else
request_body == key_body
end
else
true
end
end
defp get_response_from_server(request, recorder) do
raise_error_if_cassette_already_exists(recorder, inspect(request))
adapter = ExVCR.Recorder.options(recorder)[:adapter]
response = :meck.passthrough(request)
|> adapter.hook_response_from_server
Recorder.append(recorder, adapter.convert_to_string(request, response))
ExVCR.Checker.add_server_count(recorder)
response
end
defp raise_error_if_cassette_already_exists(recorder, request_description) do
file_path = ExVCR.Recorder.get_file_path(recorder)
if File.exists?(file_path) do
message = """
Request did not match with any one in the current cassette: #{file_path}.
Delete the current cassette with [mix vcr.delete] and re-record.
Request: #{request_description}
"""
raise ExVCR.RequestNotMatchError, message: message
end
end
end