Current section

Files

Jump to
oaskit lib mix tasks openapi.dump.ex
Raw

lib/mix/tasks/openapi.dump.ex

defmodule Mix.Tasks.Openapi.Dump do
alias CliMate.CLI
alias JSV.Codec
use Mix.Task
@command [
module: __MODULE__,
arguments: [
module: [
type: :string,
doc: """
An Elixir module with a `spec/0` callback returning an OpenAPI
specification.
""",
cast: {__MODULE__, :to_module, []}
]
],
options: [
output: [
type: :string,
short: :o,
default: "openapi.json",
doc: "The desired output file path.",
doc_arg: "path/to/file.json"
],
pretty: [
type: :boolean,
default: true,
doc: "JSON pretty-printing."
]
]
]
@requirements ["app.config"]
@shortdoc "Writes an OpenAPI specification in a JSON file."
@moduledoc """
#{@shortdoc}
#{CliMate.CLI.format_usage(@command, format: :moduledoc)}
"""
def run(argv) do
%{arguments: %{module: module}, options: opts} = CLI.parse_or_halt!(argv, @command)
module.spec()
|> Oaskit.normalize_spec!()
|> validate()
|> prune()
|> encode(opts)
|> output(opts)
end
@doc false
def to_module(arg) do
mod = Module.concat([arg])
case Code.ensure_loaded?(mod) do
true -> {:ok, mod}
false -> {:error, "could not find module #{arg}"}
end
end
defp validate(spec) do
_ =
case Oaskit.Internal.SpecValidator.validate(spec) do
{:ok, _} ->
:ok
{:error, verr} ->
CLI.warn("""
Some errors were found when validating the OpenAPI speficication:
#{Exception.format_banner(:error, verr)}
""")
end
spec
end
defp prune(spec) do
JSV.Helpers.Traverse.prewalk(spec, fn
{:val, map} when is_map(map) -> Map.delete(map, "jsv-cast")
other -> elem(other, 1)
end)
end
defp encode(spec, %{pretty: true}) do
cond do
typefix(Codec.supports_ordered_formatting?()) ->
Codec.format_ordered_to_iodata!(spec, &key_sorter/2)
typefix(Codec.supports_formatting?()) ->
Codec.format_to_iodata!(spec)
:other ->
raise ArgumentError, "Pretty printing is not supported by #{Codec.codec()}."
end
end
defp encode(spec, _) do
Codec.encode!(spec)
end
@key_order Map.new(
Enum.with_index([
"openapi",
"title",
"info",
"tags",
"servers",
"security",
"components",
"schemas",
"responses",
"paths"
])
)
defp key_order do
@key_order
end
defp key_sorter(a, b) do
Map.get(key_order(), a, a) <= Map.get(key_order(), b, b)
end
defp output(json, %{output: out_path}) do
Mix.Generator.create_file(out_path, json, force: true)
end
defp typefix(v) do
Process.get(make_ref(), v)
end
end