defmodule Stripe.OpenApi.Phases.Compile do @moduledoc false def run(blueprint, _options) do modules = Enum.map(blueprint.components, fn {_k, component} -> component.module end) for {_name, component} <- blueprint.components do funcs = for operation <- component.operations, operation_definition = lookup_operation( {operation["path"], String.to_atom(operation["operation"])}, blueprint.operations ), operation_definition != nil do arguments = operation_definition.path_parameters |> Enum.map(&String.to_atom(&1.name)) params? = operation_definition.query_parameters != [] || operation_definition.body_parameters != [] argument_names = arguments |> Enum.map(fn name -> Macro.var(name, __MODULE__) end) argument_values = arguments |> Enum.reject(&(&1 == :params)) |> Enum.map(fn name -> Macro.var(name, __MODULE__) end) argument_specs = arguments |> Enum.map(fn :params -> quote do params :: map() end name -> quote do unquote(Macro.var(name, __MODULE__)) :: binary() end end) function_name = String.to_atom(operation["method_name"]) success_response_spec = return_spec(operation_definition.success_response) quote do if unquote(operation_definition.deprecated) do @deprecated "Stripe has deprecated this operation" end @operation unquote(Macro.escape(operation_definition)) @doc unquote(operation_definition.description) if unquote(params?) do @spec unquote(function_name)( client :: term(), unquote_splicing(argument_specs), params :: map() ) :: {:ok, unquote(success_response_spec)} | {:error, Stripe.ApiErrors.t()} | {:error, term()} def unquote(function_name)( client, unquote_splicing(argument_names), params \\ %{} ) do path = Stripe.OpenApi.Path.replace_path_params( @operation.path, @operation.path_parameters, unquote(argument_values) ) Stripe.request(@operation.method, path, client, params || %{}) end else @spec unquote(function_name)(client :: term(), unquote_splicing(argument_specs)) :: {:ok, unquote(success_response_spec)} | {:error, Stripe.ApiErrors.t()} | {:error, term()} def unquote(function_name)( client, unquote_splicing(argument_names) ) do path = Stripe.OpenApi.Path.replace_path_params( @operation.path, @operation.path_parameters, unquote(argument_values) ) Stripe.request(@operation.method, path, client, %{}) end end end end fields = component.properties |> Map.keys() |> Enum.map(&String.to_atom/1) specs = Enum.map(component.properties, fn {key, value} -> {String.to_atom(key), build_spec(value, modules)} end) typedoc_fields = component.properties |> Enum.map_join("\n", fn {key, value} -> typedoc(key, value) end) typedoc = """ The `#{component.name}` type. #{typedoc_fields} """ description = if funcs == [] do component.description else component.description end body = quote do @moduledoc unquote(description) if unquote(fields) != nil do @derive {Inspect, optional: unquote(fields)} defstruct unquote(fields) @typedoc unquote(typedoc) @type t :: %__MODULE__{ unquote_splicing(specs) } end (unquote_splicing(funcs)) end Module.create(component.module, body, Macro.Env.location(__ENV__)) end {:ok, blueprint} end defp return_spec(%OpenApiGen.Blueprint.Reference{name: name}) do module = module_from_ref(name) quote do unquote(module).t() end end defp return_spec(%OpenApiGen.Blueprint.ListOf{type_of: type}) do quote do Stripe.List.t(unquote(return_spec(type))) end end defp return_spec(%OpenApiGen.Blueprint.SearchResult{type_of: type}) do quote do Stripe.SearchResult.t(unquote(return_spec(type))) end end defp return_spec(%{any_of: [type]} = _type) do return_spec(type) end defp return_spec(%OpenApiGen.Blueprint.AnyOf{any_of: [any_of | tail]} = type) do type = Map.put(type, :any_of, tail) {:|, [], [return_spec(any_of), return_spec(type)]} end defp return_spec(_) do [] end defp build_spec(%{"nullable" => true} = type, modules) do type = Map.delete(type, "nullable") {:|, [], [build_spec(type, modules), nil]} end defp build_spec(%{"anyOf" => [type]} = _type, modules) do build_spec(type, modules) end defp build_spec(%{"anyOf" => [any_of | tail]} = type, modules) do type = Map.put(type, "anyOf", tail) {:|, [], [build_spec(any_of, modules), build_spec(type, modules)]} end defp build_spec(%{"type" => "string"}, _) do quote do binary end end defp build_spec(%{"type" => "boolean"}, _) do quote do boolean end end defp build_spec(%{"type" => "integer"}, _) do quote do integer end end defp build_spec(%{"$ref" => ref}, modules) do module = module_from_ref(ref) if module in modules do quote do unquote(module).t() end else quote do term end end end defp build_spec(_, _) do quote do term end end defp module_from_ref(ref) do module = ref |> String.split("/") |> List.last() |> String.split(".") |> Enum.map(&Macro.camelize/1) Module.concat(["Stripe" | module]) end defp typedoc(field, props) do " * `#{field}` #{props["description"]}" end defp lookup_operation(path, operations) do Map.get(operations, path) end end