defmodule Retroper do @moduledoc """ `Retroper` is the tool to convert between `Estructura.Aston` instances, _XML_ and _JSON_. To make it isomorphic, _JSON_ is supposed to be presented in the form of `Estructura.Aston`-like nested objects, all having `name`, `attributes`, and `content` fields. """ alias XmlToMap, as: XmlMap xsd = Application.compile_env(:retroper, :xsd, "specs/dealconfirmation_42.xsd") {:ok, model} = xsd |> to_charlist() |> :erlsom.compile_xsd_file() @model model def default_model, do: @model def json_to_xml(json, model \\ @model, validate \\ true, format \\ :none) when is_binary(json) do with {:ok, map} <- Jason.decode(json), do: map_to_xml(map, model, validate, format) end def xml_to_xml(xml, model \\ @model, validate \\ true, format \\ :none) when is_binary(xml) do with %{} = map <- XmlMap.nested_map(xml), do: map_to_xml(map, model, validate, format) end def xml_to_json(xml, model \\ @model, validate \\ true, format \\ :none) when is_binary(xml) do with %{} = map <- XmlMap.nested_map(xml), do: map_to_json(map, model, validate, format) end def json_to_json(json, model \\ @model, validate \\ true, format \\ :none) when is_binary(json) do with {:ok, %{} = map} <- Jason.decode(json), do: map_to_json(map, model, validate, format) end def map_to_json(map, model \\ @model, validate \\ true, format \\ :none) do with {:ok, tree} <- Estructura.Aston.coerce(map), ast = Estructura.Aston.to_ast(tree), xml = XmlBuilder.generate([:xml_decl, ast], standalone: true, format: format), {:ok, _, _} <- if(validate, do: :erlsom.scan(xml, model), else: {:ok, :ok, :ok}), do: Jason.encode(tree) end def map_to_xml(map, model \\ @model, validate \\ true, format \\ :none) do with {:ok, tree} <- Estructura.Aston.coerce(map), ast = Estructura.Aston.to_ast(tree), xml = XmlBuilder.generate([:xml_decl, ast], standalone: true, format: format), {:ok, _, _} <- if(validate, do: :erlsom.scan(xml, model), else: {:ok, :ok, :ok}), do: {:ok, xml} end for from <- ~w|json map xml|a, to <- ~w|json xml| do @dialyzer {:no_return, {:"#{from}_to_#{to}!", 1}} @dialyzer {:no_return, {:"#{from}_to_#{to}!", 2}} @dialyzer {:no_return, {:"#{from}_to_#{to}!", 3}} @dialyzer {:no_return, {:"#{from}_to_#{to}!", 4}} def unquote(:"#{from}_to_#{to}!")(map, model \\ @model, validate \\ true, format \\ :none) do case unquote(:"#{from}_to_#{to}")(map, model, validate, format) do {:ok, result} -> result {:error, reason} -> raise ArgumentError, message: "Cannot convert `#{unquote(from)}` to `#{unquote(to)}, reason: #{inspect(reason)}" end end end end