Current section

Files

Jump to
xcribe lib conn_parser.ex
Raw

lib/conn_parser.ex

defmodule Xcribe.ConnParser do
@moduledoc ~S"""
Used to convert a `Plug.Conn` to a `Xcribe.Request`.
Each connection sent to documenting in your tests is given to `ConnParser`.
Is expected that connection has been passed through the app `Endpoint` as a
finished request. The parser will extract all needed info from `Conn` and uses
app `Router` for additional information about the request.
The atribute `description` must be given at `document` macro call with the
option `:as`:
test "test name", %{conn: conn} do
...
Xcribe.Helpers.Document.document(conn, as: "description here")
...
end
If no description is given the current test description will be used.
"""
alias Xcribe.{Config, Request}
@doc """
Parse the given `Plug.Conn` and transform it to a `Xcribe.Request`. A
description can be provided.
"""
def execute(conn, description \\ "") do
conn
|> identify_route()
|> parse_conn(conn, description)
end
defp parse_conn({:error, _} = error, _conn, _description), do: error
defp parse_conn(route, conn, description) do
path = format_path(route.route, conn.path_params)
%Request{
action: route |> router_options() |> Atom.to_string(),
header_params: conn.req_headers,
controller: controller_module(route),
description: description,
params: conn.params,
path: path,
path_params: conn.path_params,
query_params: conn.query_params,
request_body: conn.body_params,
resource: resource_name(path, fetch_namespaces()),
resource_group: resource_group(route),
resp_body: conn.resp_body,
resp_headers: conn.resp_headers,
status_code: conn.status,
verb: String.downcase(conn.method)
}
end
defp identify_route(%{method: method, host: host, path_info: path} = conn) do
conn
|> router_module()
|> apply(:__match_route__, [method, decode_uri(path), host])
|> extract_route_info()
end
defp router_module(%{private: %{phoenix_router: router}}), do: router
defp decode_uri(path_info), do: Enum.map(path_info, &URI.decode/1)
defp extract_route_info({%{} = route_info, _callback_one, _callback_two, _plug_info}),
do: route_info
defp extract_route_info(_), do: {:error, "route not found"}
defp router_options(%{plug_opts: opts}), do: opts
defp router_options(%{opts: opts}), do: opts
defp controller_module(%{plug: controller}), do: controller
defp resource_group(%{pipe_through: [head | _rest]}), do: head
defp resource_name(path, namespaces) do
namespaces
|> Enum.reduce(path, &remove_namespace/2)
|> String.split("/")
|> Enum.filter(&Regex.match?(~r/^\w+$/, &1))
|> Enum.join("_")
end
defp remove_namespace(namespace, path), do: String.replace(path, ~r/^#{namespace}/, "")
defp format_path(path, params),
do: params |> Map.keys() |> Enum.reduce(path, &transform_param/2)
defp transform_param(param, path), do: String.replace(path, ":#{param}", "{#{param}}")
defp fetch_namespaces, do: apply(Config.xcribe_information_source(), :namespaces, [])
end