Page MenuHomePhorge

No OneTemporary

Size
9 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex/open_api.ex b/lib/open_api_spex/open_api.ex
index 35bb570..0a9b954 100644
--- a/lib/open_api_spex/open_api.ex
+++ b/lib/open_api_spex/open_api.ex
@@ -1,129 +1,130 @@
defmodule OpenApiSpex.OpenApi do
@moduledoc """
Defines the `OpenApiSpex.OpenApi.t` type and the behaviour for application modules that
construct an `OpenApiSpex.OpenApi.t` at runtime.
"""
alias OpenApiSpex.{
Extendable, Info, Server, Paths, Components,
SecurityRequirement, Tag, ExternalDocumentation,
- OpenApi, MediaType, Schema
+ OpenApi, MediaType, Schema, Example
}
@enforce_keys [:info, :paths]
defstruct [
openapi: "3.0.0",
info: nil,
servers: [],
paths: nil,
components: nil,
security: [],
tags: [],
externalDocs: nil,
extensions: nil
]
@typedoc """
[OpenAPI Object](https://swagger.io/specification/#oasObject)
This is the root document object of the OpenAPI document.
"""
@type t :: %OpenApi{
openapi: String.t,
info: Info.t,
servers: [Server.t] | nil,
paths: Paths.t,
components: Components.t | nil,
security: [SecurityRequirement.t] | nil,
tags: [Tag.t] | nil,
externalDocs: ExternalDocumentation.t | nil,
extensions: %{String.t() => any()} | nil,
}
@doc """
A spec/0 callback function is required for use with the `OpenApiSpex.Plug.PutApiSpec` plug.
## Example
@impl OpenApiSpex.OpenApi
def spec do
%OpenApi{
servers: [
# Populate the Server info from a phoenix endpoint
Server.from_endpoint(MyAppWeb.Endpoint)
],
info: %Info{
title: "My App",
version: "1.0"
},
# populate the paths from a phoenix router
paths: Paths.from_router(MyAppWeb.Router)
}
|> OpenApiSpex.resolve_schema_modules() # discover request/response schemas from path specs
end
"""
@callback spec() :: t
@json_encoder Enum.find([Jason, Poison], &Code.ensure_loaded?/1)
def json_encoder, do: @json_encoder
for encoder <- [Poison.Encoder, Jason.Encoder] do
if Code.ensure_loaded?(encoder) do
defimpl encoder do
def encode(api_spec = %OpenApi{}, options) do
api_spec
|> to_json()
|> unquote(encoder).encode(options)
end
defp to_json(%Regex{source: source}), do: source
- defp to_json(value = %object{}) when object in [MediaType, Schema] do
+ defp to_json(value = %object{}) 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
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
defp to_json(value) when is_list(value) do
Enum.map(value, &to_json/1)
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
defp to_json_example(value = %{__struct__: _}) do
value
|> Extendable.to_map()
|> to_json_example()
end
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_json_example(value) when is_list(value) do
Enum.map(value, fn
x when is_map(x) or is_list(x) -> to_json_example(x)
x -> to_json(x)
end)
end
defp to_json_example(value), do: to_json(value)
end
end
end
end
diff --git a/test/encode_test.exs b/test/encode_test.exs
index 2ca8a24..2a68305 100644
--- a/test/encode_test.exs
+++ b/test/encode_test.exs
@@ -1,147 +1,203 @@
defmodule OpenApiSpex.EncodeTest do
use ExUnit.Case
alias OpenApiSpex.{
Info,
OpenApi,
Schema
}
test "Vendor extensions x-logo properly encoded" do
spec = %OpenApi{
info: %Info{
title: "Test",
version: "1.0.0",
extensions: %{
"x-logo" => %{
"url" => "https://example.com/logo.png",
"backgroundColor" => "#FFFFFF",
"altText" => "Example logo"
}
}
},
paths: %{}
}
decoded =
OpenApiSpex.resolve_schema_modules(spec)
|> Jason.encode!()
|> Jason.decode!()
assert decoded["info"]["x-logo"]["url"] == "https://example.com/logo.png"
assert decoded["info"]["x-logo"]["backgroundColor"] == "#FFFFFF"
assert decoded["info"]["x-logo"]["altText"] == "Example logo"
assert is_nil(decoded["info"]["extensions"])
end
test "Vendor extensions x-tagGroups properly encoded" do
spec = %OpenApi{
info: %Info{
title: "Test",
version: "1.0.0"
},
extensions: %{
"x-tagGroups" => [
%{
"name" => "Methods",
"tags" => [
"Search",
"Fetch",
"Delete"
]
}
]
},
paths: %{}
}
decoded =
OpenApiSpex.resolve_schema_modules(spec)
|> Jason.encode!()
|> Jason.decode!()
assert hd(decoded["x-tagGroups"])["name"] == "Methods"
assert hd(decoded["x-tagGroups"])["tags"] == ["Search", "Fetch", "Delete"]
assert is_nil(decoded["extensions"])
end
test "Example field properly encoded (MediaType, Schema)" do
spec = %OpenApi{
info: %Info{
title: "Test",
version: "1.0.0"
},
paths: %{
"/example" => %OpenApiSpex.PathItem{
get: %OpenApiSpex.Operation{
responses: %{
200 => %OpenApiSpex.Response{
description: "An example",
content: %{
"application/json" => %OpenApiSpex.MediaType{
example: %{
"id" => 678,
"first_name" => "John",
"last_name" => "Doe",
"phone_number" => nil
}
}
}
}
}
}
}
},
components: %OpenApiSpex.Components{
schemas: %{
"User" => %Schema{
type: :object,
properties: %{
id: %Schema{type: :integer},
first_name: %Schema{type: :string},
last_name: %Schema{type: :string},
phone_number: %Schema{type: :string, nullable: true}
},
example: %{
"id" => 42,
"first_name" => "Jane",
"last_name" => "Doe",
"phone_number" => nil
}
}
}
}
}
decoded =
OpenApiSpex.resolve_schema_modules(spec)
|> Jason.encode!()
|> Jason.decode!()
assert Map.has_key?(
get_in(decoded, [
"paths",
"/example",
"get",
"responses",
"200",
"content",
"application/json",
"example"
]),
"phone_number"
)
assert Map.has_key?(
get_in(decoded, [
"components",
"schemas",
"User",
- "example",
+ "example"
+ ]),
+ "phone_number"
+ )
+ end
+
+ test "Value field from Example object properly encoded" do
+ spec = %OpenApi{
+ info: %Info{
+ title: "Test",
+ version: "1.0.0"
+ },
+ paths: %{
+ "/example" => %OpenApiSpex.PathItem{
+ get: %OpenApiSpex.Operation{
+ responses: %{
+ 200 => %OpenApiSpex.Response{
+ description: "An example",
+ content: %{
+ "application/json" => %OpenApiSpex.MediaType{
+ examples: %{
+ "John" => %OpenApiSpex.Example{
+ summary: "Its John",
+ value: %{
+ "id" => 678,
+ "first_name" => "John",
+ "last_name" => "Doe",
+ "phone_number" => nil
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ decoded =
+ OpenApiSpex.resolve_schema_modules(spec)
+ |> Jason.encode!()
+ |> Jason.decode!()
+
+ assert Map.has_key?(
+ get_in(decoded, [
+ "paths",
+ "/example",
+ "get",
+ "responses",
+ "200",
+ "content",
+ "application/json",
+ "examples",
+ "John",
+ "value"
]),
"phone_number"
)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Thu, Nov 28, 3:31 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40812
Default Alt Text
(9 KB)

Event Timeline