defmodule Xcribe.ApiBlueprint.Formatter do @moduledoc false alias Xcribe.{ContentDecoder, JsonSchema, Request} import Xcribe.Helpers.Formatter def merge_request(requests, new_request) do group_key = object_key(new_request) Map.update( requests, group_key, new_request[group_key], &merge_group(&1, new_request[group_key]) ) end def full_request_object(%Request{resource_group: group} = request) do %{ capitalize_all_words(to_string(group)) => %{ summary: "", description: "", resources: resource_object(request) } } end def resource_object(%Request{} = request) do %{ resource_key(request) => %{ name: resource_name(request), summary: "", description: "", parameters: resource_parameters(request), actions: action_object(request) } } end def action_object(%Request{} = request) do %{ action_key(request) => %{ name: action_name(request), summary: "", description: "", parameters: action_parameters(request), requests: request_object(request) } } end def request_object(%Request{description: desc, header_params: headers} = request) do %{ desc => %{ content_type: content_type(headers), headers: headers(headers), body: request.request_body, schema: request_schema(request), response: response_object(request) } } end def response_object(%Request{resp_headers: headers, status_code: code} = request) do %{ status: code, content_type: content_type(headers), headers: headers(headers), body: response_body(request), schema: response_schema(request) } end def action_parameters(%Request{path_params: path_params}) do Enum.reduce(path_params, %{}, &reduce_path_params/2) end def resource_parameters(%Request{path: path, path_params: path_params}) do path_params |> Map.take(url_params(path)) |> Enum.reduce(%{}, &reduce_path_params/2) end def response_schema(%Request{resp_body: body, resp_headers: headers}) do headers |> content_type() |> json_schema_for(body) end def response_body(%Request{resp_body: body, resp_headers: headers}) do case content_type(headers) do nil -> %{} content_type -> ContentDecoder.decode!(body, content_type) end end def request_schema(%Request{request_body: body}) when body == %{}, do: %{} def request_schema(%Request{request_body: body, header_params: headers}) do headers |> content_type() |> json_schema_for(body) end def action_name(%Request{action: action, resource: resource}), do: "#{capitalize_all_words(resource)}#{action}" def action_key(%Request{path: path, verb: verb}) do Enum.reduce( url_params(path), "#{String.upcase(verb)} #{path}", &String.replace(&2, &1, format_path_parameter(&1)) ) end def resource_name(%Request{resource: resource}), do: resource |> capitalize_all_words() |> String.trim() def resource_key(%Request{path: path}) do Enum.reduce( url_params(path), resource_path(path), &String.replace(&2, &1, format_path_parameter(&1)) ) end defp merge_group(group, new_request), do: %{group | resources: merge_group_resources(group.resources, new_request.resources)} defp merge_group_resources(resources, new_request) do resource_key = object_key(new_request) Map.update( resources, resource_key, new_request[resource_key], &merge_resource(&1, new_request[resource_key]) ) end defp merge_resource(resource, new_request) do %{resource | actions: merge_resource_actions(resource.actions, new_request.actions)} end defp merge_resource_actions(actions, new_request) do action_key = object_key(new_request) Map.update( actions, action_key, new_request[action_key], &merge_action(&1, new_request[action_key]) ) end defp merge_action(action, new_request) do %{action | requests: Map.merge(action.requests, new_request.requests)} end defp object_key(%{} = request) do request |> Map.keys() |> List.first() end defp resource_path(path) do ~r/(.*)(?=\/{.*}$)|(.*)/ |> Regex.run(path, capture: :all_but_first) |> List.last() end defp capitalize_all_words(string), do: Enum.reduce(String.split(string, "_"), "", &"#{&2}#{String.capitalize(&1)} ") defp json_schema_for("application/json" = type, body) when is_binary(body), do: json_schema_for(type, ContentDecoder.decode!(body, type)) defp json_schema_for("application/json", body) when is_map(body) or is_list(body), do: JsonSchema.schema_for(body) defp json_schema_for(_content_type, _body), do: %{} defp reduce_path_params({param, value}, parameters) do Map.put( parameters, format_path_parameter(param), schema_for(value, true) ) end defp schema_for(value, required) do {nil, value} |> JsonSchema.schema_for() |> add_required(required) end defp headers(headers), do: headers |> Enum.into(%{}) |> Map.delete("content-type") defp add_required(map, true), do: Map.put(map, :required, true) defp url_params(path) do case Regex.run(~r/\{(.*?)\}.+/, path, capture: :all_but_first) do nil -> [] result -> result end end end