Skip to content

Commit

Permalink
Implement flag to omit vendor extensions in mix openapi.spec.json (#439)
Browse files Browse the repository at this point in the history
* Implement flag to omit vendor extensions in mix openapi.spec.json

The `--vendor-extensions=boolean` flag when set to:

* true   : Keeps vendor extensions in the generated openapi.json file
* false  : Strips vendor extensions in the generated openapi.json file

The current vendor extensions are x-struct and x-validate.
  • Loading branch information
zorbash authored May 10, 2022
1 parent 005baa3 commit 9de8af4
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 61 deletions.
31 changes: 26 additions & 5 deletions lib/mix/tasks/openapi.spec.json.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
defmodule Mix.Tasks.Openapi.Spec.Json do
@moduledoc """
Serialize the given OpenApi spec module to a JSON file.
## Examples
$ mix openapi.spec.json --spec PhoenixAppWeb.ApiSpec apispec.json
$ mix openapi.spec.json --spec PhoenixAppWeb.ApiSpec --pretty=true
$ mix openapi.spec.json --spec PhoenixAppWeb.ApiSpec --vendor-extensions=false
## Command line options
* `--spec` - The ApiSpec module from which to generate the OpenAPI JSON file
* `--pretty` - Whether to prettify the generated JSON (defaults to false)
* `--vendor-extensions` - Whether to include open_api_spex OpenAPI vendor extensions
(defaults to true)
"""
use Mix.Task
require Mix.Generator
Expand All @@ -10,7 +26,7 @@ defmodule Mix.Tasks.Openapi.Spec.Json do
defmodule Options do
@moduledoc false

defstruct filename: nil, spec: nil, pretty: false
defstruct filename: nil, spec: nil, pretty: false, vendor_extensions: true
end

@impl true
Expand All @@ -21,14 +37,18 @@ defmodule Mix.Tasks.Openapi.Spec.Json do
write_spec(content, opts.filename)
end

def generate_spec(%{spec: spec, pretty: pretty}) do
def generate_spec(%{spec: spec, pretty: pretty, vendor_extensions: vendor_extensions}) do
case Code.ensure_compiled(spec) do
{:module, _} ->
if function_exported?(spec, :spec, 0) do
json_encoder = OpenApiSpex.OpenApi.json_encoder()
spec = spec.spec()

case json_encoder.encode(spec, pretty: pretty) do
json_encoding_result = spec
|> OpenApiSpex.OpenApi.to_map(vendor_extensions: vendor_extensions)
|> json_encoder.encode(pretty: pretty)

case json_encoding_result do
{:ok, json} ->
json

Expand All @@ -51,13 +71,14 @@ defmodule Mix.Tasks.Openapi.Spec.Json do
end

defp parse_options(argv) do
parse_options = [strict: [spec: :string, endpoint: :string, pretty: :boolean]]
parse_options = [strict: [spec: :string, endpoint: :string, pretty: :boolean, vendor_extensions: :boolean]]
{opts, args, _} = OptionParser.parse(argv, parse_options)

%Options{
filename: args |> List.first() || @default_filename,
spec: find_spec(opts),
pretty: Keyword.get(opts, :pretty, false)
pretty: Keyword.get(opts, :pretty, false),
vendor_extensions: Keyword.get(opts, :vendor_extensions, true)
}
end

Expand Down
118 changes: 62 additions & 56 deletions lib/open_api_spex/open_api.ex
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ defmodule OpenApiSpex.OpenApi do
@callback spec() :: t

@json_encoder Enum.find([Jason, Poison], &Code.ensure_loaded?/1)
@vendor_extensions ~w(
x-struct
x-validate
)

def json_encoder, do: @json_encoder

Expand All @@ -79,74 +83,76 @@ defmodule OpenApiSpex.OpenApi do
defimpl encoder do
def encode(api_spec = %OpenApi{}, options) do
api_spec
|> to_json()
|> OpenApi.to_map
|> unquote(encoder).encode(options)
end
end
end
end

defp to_json(%Regex{source: source}), do: source

defp to_json(%object{} = value) when object in [MediaType, Schema, Example] do
value
|> Extendable.to_map()
|> Stream.map(fn
{:value, v} when object == Example -> {"value", to_json_example(v)}
{:example, v} -> {"example", to_json_example(v)}
{k, v} -> {to_string(k), to_json(v)}
end)
|> Stream.filter(fn
{_, nil} -> false
_ -> true
end)
|> Enum.into(%{})
end

defp to_json(value = %{__struct__: _}) do
value
|> Extendable.to_map()
|> to_json()
end
def to_map(value), do: to_map(value, [])
def to_map(%Regex{source: source}, _opts), do: source

def to_map(%object{} = value, opts) when object in [MediaType, Schema, Example] do
value
|> Extendable.to_map()
|> Stream.map(fn
{:value, v} when object == Example -> {"value", to_map_example(v, opts)}
{:example, v} -> {"example", to_map_example(v, opts)}
{k, v} -> {to_string(k), to_map(v, opts)}
end)
|> Stream.filter(fn
{k, _} when k in @vendor_extensions -> opts[:vendor_extensions]
{_, nil} -> false
_ -> true
end)
|> Enum.into(%{})
end

defp to_json(value) when is_map(value) do
value
|> Stream.map(fn {k, v} -> {to_string(k), to_json(v)} end)
|> Stream.filter(fn
{_, nil} -> false
_ -> true
end)
|> Enum.into(%{})
end
def to_map(value = %{__struct__: _}, opts) do
value
|> Extendable.to_map()
|> to_map(opts)
end

defp to_json(value) when is_list(value) do
Enum.map(value, &to_json/1)
end
def to_map(value, opts) when is_map(value) do
value
|> Stream.map(fn {k, v} -> {to_string(k), to_map(v, opts)} end)
|> Stream.filter(fn
{_, nil} -> false
_ -> true
end)
|> Enum.into(%{})
end

defp to_json(nil), do: nil
defp to_json(true), do: true
defp to_json(false), do: false
defp to_json(value) when is_atom(value), do: to_string(value)
defp to_json(value), do: value
def to_map(value, opts) when is_list(value) do
Enum.map(value, &to_map(&1, opts))
end

defp to_json_example(value = %{__struct__: _}) do
value
|> Extendable.to_map()
|> to_json_example()
end
def to_map(nil, _opts), do: nil
def to_map(true, _opts), do: true
def to_map(false, _opts), do: false
def to_map(value, _opts) when is_atom(value), do: to_string(value)
def to_map(value, _opts), do: value

defp to_json_example(value) when is_map(value) do
value
|> Stream.map(fn {k, v} -> {to_string(k), to_json_example(v)} end)
|> Enum.into(%{})
end
defp to_map_example(value = %{__struct__: _}, opts) do
value
|> Extendable.to_map()
|> to_map_example(opts)
end

defp to_json_example(value) when is_list(value) do
Enum.map(value, &to_json_example/1)
end
defp to_map_example(value, opts) when is_map(value) do
value
|> Stream.map(fn {k, v} -> {to_string(k), to_map_example(v, opts)} end)
|> Enum.into(%{})
end

defp to_json_example(value), do: to_json(value)
end
end
defp to_map_example(value, opts) when is_list(value) do
Enum.map(value, &to_map_example(&1, opts))
end

defp to_map_example(value, opts), do: to_map(value, opts)

def from_map(map) do
OpenApi.Decode.decode(map)
end
Expand Down

0 comments on commit 9de8af4

Please sign in to comment.