Packages

OpenTravel API Extension for Elixir

Current section

Files

Jump to
ex_open_travel lib response parser.ex
Raw

lib/response/parser.ex

defmodule ExOpenTravel.Response.Parser do
@moduledoc """
Provides a functions for parse an xml-like response body.
"""
import SweetXml, only: [xpath: 2, sigil_x: 2]
alias ExOpenTravel.Response
alias ExOpenTravel.Response.FaultProcessor
@soap_version_namespaces %{
"1.1" => :"http://schemas.xmlsoap.org/soap/envelope/",
"1.2" => :"http://www.w3.org/2003/05/soap-envelope"
}
@doc """
Executing with xml response body.
If a list is empty then `parse/1` returns full parsed response structure into map.
"""
@spec parse(String.t(), map) :: map()
def parse(xml_response, meta) do
xml_response = String.trim(xml_response)
try do
fault_tag = get_fault_tag(xml_response)
xml_response
|> xpath(~x"//#{fault_tag}/*"l)
|> parse_elements()
|> Enum.reduce(%{}, fn el, acc ->
with {key, _, [value]} <- el do
Map.put(acc, key, value)
end
end)
|> FaultProcessor.create_response(meta)
rescue
e in ArgumentError -> FaultProcessor.create_response(e, meta)
e in FunctionClauseError -> FaultProcessor.create_response(e, meta)
catch
:exit, e -> FaultProcessor.create_response({:exit, e}, meta)
:fatal, e -> FaultProcessor.create_response({:fatal, e}, meta)
end
end
@spec parse_record(tuple()) :: map() | String.t()
defp parse_record({:xmlElement, tag, _, _, _, _, _, attributes, elements, _, _, _}) do
{get_tag_name(tag),
attributes
|> Enum.map(&parse_record/1)
|> Enum.reduce(%{}, fn {attr_name, value}, acc -> Map.put(acc, attr_name, value) end),
parse_elements(elements)}
end
defp parse_record({:xmlAttribute, attr_name, _, _, _, _, _, _, value, _}),
do: {attr_name, value}
defp parse_record({:xmlText, _, _, _, value, _}) do
transform_record_value(value)
end
def get_tag_name(tag) when is_atom(tag), do: tag |> Atom.to_string() |> get_tag_name()
def get_tag_name(tag), do: tag |> String.split(":") |> List.last()
defp transform_record_value('\n'), do: nil
defp transform_record_value(nil), do: nil
defp transform_record_value(value) when is_list(value),
do: value |> to_string() |> String.trim()
defp transform_record_value(value) when is_binary(value), do: value |> String.trim()
@spec parse_elements(list() | tuple()) :: map()
defp parse_elements([]), do: []
defp parse_elements(elements) when is_tuple(elements), do: parse_record(elements)
defp parse_elements(elements) when is_list(elements) do
elements
|> Enum.map(&parse_record/1)
|> Enum.filter(&(not is_nil(&1)))
|> parse_element_values()
end
@spec parse_element_values(list()) :: any()
defp parse_element_values(elements) do
cond do
Enum.all?(elements, &is_map/1) && unique_tags?(elements) ->
Enum.reduce(elements, &Map.merge/2)
Enum.all?(elements, &is_map/1) ->
elements |> Enum.map(&Map.to_list/1) |> List.flatten()
true ->
elements |> Enum.filter(&(&1 != ""))
end
end
defp unique_tags?(elements) do
keys =
elements
|> Enum.map(&Map.keys/1)
|> List.flatten()
Enum.uniq(keys) == keys
end
defp get_fault_tag(xml_response) do
xml_response
|> get_envelope_namespace()
|> List.to_string()
|> apply_namespace_to_tag("Fault")
end
defp get_envelope_namespace(xml_response) do
env_namespace = @soap_version_namespaces[soap_version()]
xml_response
|> xpath(~x"//namespace::*"l)
|> Enum.find(fn {_, _, _, _, namespace_url} -> namespace_url == env_namespace end)
|> elem(3)
end
defp apply_namespace_to_tag(nil, tag), do: tag
defp apply_namespace_to_tag(env_namespace, tag), do: env_namespace <> ":" <> tag
defp soap_version, do: Application.fetch_env!(:ex_open_travel, :globals)[:version]
def handle_response(
{:ok,
%HTTPoison.Response{
body: body,
headers: headers,
request_url: request_url,
status_code: status_code
}}
) do
{:ok,
%Response{body: body, headers: headers, request_url: request_url, status_code: status_code}}
end
def handle_response({:error, %HTTPoison.Error{reason: reason}}) do
{:error, reason}
end
end