Page MenuHomePhorge

No OneTemporary

Size
34 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex/cast/discriminator.ex b/lib/open_api_spex/cast/discriminator.ex
index 663a8fa..203f9c0 100644
--- a/lib/open_api_spex/cast/discriminator.ex
+++ b/lib/open_api_spex/cast/discriminator.ex
@@ -1,101 +1,112 @@
defmodule OpenApiSpex.Cast.Discriminator do
@moduledoc """
Defines the `OpenApiSpex.Discriminator.t` type.
"""
- alias OpenApiSpex.Cast
+ alias OpenApiSpex.{Cast, Reference, Schema}
@enforce_keys :propertyName
defstruct [
:propertyName,
:mapping
]
@typedoc """
[Discriminator Object](https://swagger.io/specification/#discriminatorObject)
When request bodies or response payloads may be one of a number of different schemas,
a discriminator object can be used to aid in serialization, deserialization, and validation.
The discriminator is a specific object in a schema which is used to inform the consumer of the
specification of an alternative schema based on the value associated with it.
A discriminator requires a composite key be set on the schema:
* `allOf`
* `oneOf`
* `anyOf`
"""
@type t :: %__MODULE__{
propertyName: String.t(),
mapping: %{String.t() => String.t()} | nil
}
def cast(ctx) do
case cast_discriminator(ctx) do
{:ok, result} -> {:ok, result}
error -> error
end
end
defp cast_discriminator(%_{value: value, schema: schema} = ctx) do
{discriminator_property, mappings} = discriminator_details(schema)
case Map.pop(value, "#{discriminator_property}") do
{"", _} ->
error(:no_value_for_discriminator, ctx)
{discriminator_value, _castable_value} ->
# The cast specified by the composite key (allOf, anyOf, oneOf) MUST succeed
# or return an error according to the Open API Spec.
composite_ctx = %{
ctx
| schema: %{schema | discriminator: nil},
path: ["#{discriminator_property}" | ctx.path]
}
cast_composition(composite_ctx, ctx, discriminator_value, mappings)
end
end
defp cast_composition(composite_ctx, ctx, discriminator_value, mappings) do
with {composite_schemas, {:ok, _}} <- cast_composition(composite_ctx),
%{} = schema <-
find_discriminator_schema(discriminator_value, mappings, composite_schemas) do
Cast.cast(%{composite_ctx | schema: schema})
else
nil -> error(:invalid_discriminator_value, ctx)
other -> other
end
end
defp cast_composition(%_{schema: %{anyOf: schemas, discriminator: nil}} = ctx)
when is_list(schemas),
- do: {schemas, Cast.cast(ctx)}
+ do: {locate_schemas(schemas, ctx.schemas), Cast.cast(ctx)}
defp cast_composition(%_{schema: %{allOf: schemas, discriminator: nil}} = ctx)
when is_list(schemas),
- do: {schemas, Cast.cast(ctx)}
+ do: {locate_schemas(schemas, ctx.schemas), Cast.cast(ctx)}
defp cast_composition(%_{schema: %{oneOf: schemas, discriminator: nil}} = ctx)
when is_list(schemas),
- do: {schemas, Cast.cast(ctx)}
+ do: {locate_schemas(schemas, ctx.schemas), Cast.cast(ctx)}
defp find_discriminator_schema(discriminator, mappings = %{}, schemas) do
with {:ok, "#/components/schemas/" <> name} <- Map.fetch(mappings, discriminator) do
find_discriminator_schema(name, nil, schemas)
else
+ {:ok, name} -> find_discriminator_schema(name, nil, schemas)
:error -> find_discriminator_schema(discriminator, nil, schemas)
end
end
defp find_discriminator_schema(discriminator, _, schemas) do
Enum.find(schemas, &Kernel.==(&1.title, discriminator))
end
defp discriminator_details(%{discriminator: %{propertyName: property_name, mapping: mappings}}),
do: {String.to_existing_atom(property_name), mappings}
defp error(message, %{schema: %{discriminator: %{propertyName: property}}} = ctx) do
Cast.error(ctx, {message, property})
end
+
+ defp locate_schemas(schemas, ctx_schemas) do
+ schemas
+ |> Enum.map(fn
+ %Schema{} = schema ->
+ schema
+ %Reference{} = schema ->
+ Reference.resolve_schema(schema, ctx_schemas)
+ end)
+ end
end
diff --git a/lib/open_api_spex/cast_parameters.ex b/lib/open_api_spex/cast_parameters.ex
index cc84246..dc6f613 100644
--- a/lib/open_api_spex/cast_parameters.ex
+++ b/lib/open_api_spex/cast_parameters.ex
@@ -1,104 +1,115 @@
defmodule OpenApiSpex.CastParameters do
@moduledoc false
alias OpenApiSpex.{Cast, Operation, Parameter, Schema, Reference, Components}
alias OpenApiSpex.Cast.{Error, Object}
alias Plug.Conn
@spec cast(Plug.Conn.t(), Operation.t(), Components.t()) ::
{:error, [Error.t()]} | {:ok, Conn.t()}
def cast(conn, operation, components) do
with {:ok, params} <- cast_to_params(conn, operation, components) do
{:ok, %{conn | params: params}}
end
end
defp cast_to_params(conn, operation, components) do
operation
|> schemas_by_location(components)
|> Enum.map(fn {location, schema} -> cast_location(location, schema, components, conn) end)
|> reduce_cast_results()
end
defp get_params_by_location(conn, :query, _) do
Plug.Conn.fetch_query_params(conn).query_params
end
defp get_params_by_location(conn, :path, _) do
conn.path_params
end
defp get_params_by_location(conn, :cookie, _) do
Plug.Conn.fetch_cookies(conn).req_cookies
end
defp get_params_by_location(conn, :header, expected_names) do
conn.req_headers
|> Enum.filter(fn {key, _value} ->
Enum.member?(expected_names, String.downcase(key))
end)
|> Map.new()
end
defp create_location_schema(parameters) do
%Schema{
type: :object,
additionalProperties: false,
properties: parameters |> Map.new(fn p -> {p.name, Parameter.schema(p)} end),
required: parameters |> Enum.filter(& &1.required) |> Enum.map(& &1.name)
}
|> maybe_add_additional_properties()
end
defp schemas_by_location(operation, components) do
param_specs_by_location =
operation.parameters
|> Enum.map(fn
- %Reference{} = ref -> Reference.resolve_parameter(ref, components.parameters)
- %Parameter{} = param -> param
+ %Reference{} = ref ->
+ Reference.resolve_parameter(ref, components.parameters)
+
+ %Parameter{schema: %Reference{"$ref": "#/components/parameters/" <> _}} = param ->
+ schema = Reference.resolve_parameter(param.schema, components.parameter)
+ param |> Map.put(:schema, schema)
+
+ %Parameter{schema: %Reference{"$ref": "#/components/schemas/" <> _}} = param ->
+ schema = Reference.resolve_schema(param.schema, components.schemas)
+ param |> Map.put(:schema, schema)
+
+ %Parameter{} = param ->
+ param
end)
|> Enum.group_by(& &1.in)
Map.new(param_specs_by_location, fn {location, parameters} ->
{location, create_location_schema(parameters)}
end)
end
defp cast_location(location, schema, components, conn) do
params =
get_params_by_location(
conn,
location,
schema.properties |> Map.keys() |> Enum.map(&Atom.to_string/1)
)
ctx = %Cast{
value: params,
schema: schema,
schemas: components.schemas
}
Object.cast(ctx)
end
defp reduce_cast_results(results) do
Enum.reduce_while(results, {:ok, %{}}, fn
{:ok, params}, {:ok, all_params} -> {:cont, {:ok, Map.merge(all_params, params)}}
cast_error, _ -> {:halt, cast_error}
end)
end
defp maybe_add_additional_properties(schema) do
ap_schema =
Enum.reject(
schema.properties,
fn {_name, %{additionalProperties: ap}} ->
is_nil(ap) or ap == false
end
)
case ap_schema do
[{_, %{additionalProperties: ap}}] -> %{schema | additionalProperties: ap}
_ -> schema
end
end
end
diff --git a/test/plug/cast_test.exs b/test/plug/cast_test.exs
index 72a7824..f3f05fe 100644
--- a/test/plug/cast_test.exs
+++ b/test/plug/cast_test.exs
@@ -1,268 +1,302 @@
defmodule OpenApiSpex.Plug.CastTest do
use ExUnit.Case
describe "query params - basics" do
test "Valid Param" do
conn =
:get
|> Plug.Test.conn("/api/users?validParam=true")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 200
end
test "Invalid value" do
conn =
:get
|> Plug.Test.conn("/api/users?validParam=123")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 422
end
test "Invalid Param" do
conn =
:get
|> Plug.Test.conn("/api/users?validParam=123")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 422
error_resp = Jason.decode!(conn.resp_body)
assert error_resp == %{
"errors" => [
%{
"message" => "Invalid boolean. Got: string",
"source" => %{"pointer" => "/validParam"},
"title" => "Invalid value"
}
]
}
end
test "with requestBody" do
body =
Jason.encode!(%{
phone_number: "123-456-789",
postal_address: "123 Lane St"
})
conn =
:post
|> Plug.Test.conn("/api/users/123/contact_info", body)
|> Plug.Conn.put_req_header("content-type", "application/json")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 200
end
end
describe "query params - param with custom error handling" do
test "Valid Param" do
conn =
:get
|> Plug.Test.conn("/api/custom_error_users?validParam=true")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 200
end
test "Invalid value" do
conn =
:get
|> Plug.Test.conn("/api/custom_error_users?validParam=123")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 400
end
test "Invalid Param" do
conn =
:get
|> Plug.Test.conn("/api/custom_error_users?validParam=123")
|> OpenApiSpexTest.Router.call([])
assert conn.status == 400
assert conn.resp_body == "Invalid boolean. Got: string"
end
end
describe "body params" do
test "Valid Request" do
request_body = %{
"user" => %{
"id" => 123,
"name" => "asdf",
"email" => "foo@bar.com",
"updated_at" => "2017-09-12T14:44:55Z"
}
}
conn =
:post
|> Plug.Test.conn("/api/users", Jason.encode!(request_body))
|> Plug.Conn.put_req_header("content-type", "application/json; charset=UTF-8")
|> OpenApiSpexTest.Router.call([])
assert conn.body_params == %OpenApiSpexTest.Schemas.UserRequest{
user: %OpenApiSpexTest.Schemas.User{
id: 123,
name: "asdf",
email: "foo@bar.com",
updated_at: ~N[2017-09-12T14:44:55Z] |> DateTime.from_naive!("Etc/UTC")
}
}
assert Jason.decode!(conn.resp_body) == %{
"data" => %{
"email" => "foo@bar.com",
"id" => 1234,
"inserted_at" => nil,
"name" => "asdf",
"updated_at" => "2017-09-12T14:44:55Z"
}
}
end
test "Invalid Request" do
request_body = %{
"user" => %{
"id" => 123,
"name" => "*1234",
"email" => "foo@bar.com",
"updated_at" => "2017-09-12T14:44:55Z"
}
}
conn =
:post
|> Plug.Test.conn("/api/users", Jason.encode!(request_body))
|> Plug.Conn.put_req_header("content-type", "application/json")
conn = OpenApiSpexTest.Router.call(conn, [])
assert conn.status == 422
resp_data = Jason.decode!(conn.resp_body)
assert resp_data ==
%{
"errors" => [
%{
"message" => "Invalid format. Expected ~r/[a-zA-Z][a-zA-Z0-9_]+/",
"source" => %{"pointer" => "/user/name"},
"title" => "Invalid value"
}
]
}
end
end
describe "oneOf body params" do
test "Valid Request" do
request_body = %{
"pet" => %{
"pet_type" => "Dog",
"bark" => "woof"
}
}
conn =
:post
|> Plug.Test.conn("/api/pets", Jason.encode!(request_body))
|> Plug.Conn.put_req_header("content-type", "application/json; charset=UTF-8")
|> OpenApiSpexTest.Router.call([])
assert conn.body_params == %OpenApiSpexTest.Schemas.PetRequest{
pet: %OpenApiSpexTest.Schemas.Dog{
pet_type: "Dog",
bark: "woof"
}
}
assert Jason.decode!(conn.resp_body) == %{
"data" => %{
"pet_type" => "Dog",
"bark" => "woof"
}
}
end
@tag :capture_log
test "Invalid Request" do
request_body = %{
"pet" => %{
"pet_type" => "Human",
"says" => "yes"
}
}
conn =
:post
|> Plug.Test.conn("/api/pets", Jason.encode!(request_body))
|> Plug.Conn.put_req_header("content-type", "application/json")
conn = OpenApiSpexTest.Router.call(conn, [])
assert conn.status == 422
resp_body = Jason.decode!(conn.resp_body)
assert resp_body == %{
"errors" => [
%{
"source" => %{
"pointer" => "/pet"
},
"title" => "Invalid value",
"message" => "Failed to cast value to one of: [] (no schemas provided)"
}
]
}
end
test "Header params" do
conn =
:post
|> Plug.Test.conn("/api/pets/1/adopt")
|> Plug.Conn.put_req_header("content-type", "application/json; charset=UTF-8")
|> Plug.Conn.put_req_header("x-user-id", "123456")
|> OpenApiSpexTest.Router.call([])
assert Jason.decode!(conn.resp_body) == %{
"data" => %{
"pet_type" => "Dog",
"bark" => "woof"
}
}
end
+
+ test "Optional param" do
+ conn =
+ :post
+ |> Plug.Test.conn("/api/pets/1/adopt?status=adopted")
+ |> Plug.Conn.put_req_header("content-type", "application/json; charset=UTF-8")
+ |> Plug.Conn.put_req_header("x-user-id", "123456")
+ |> OpenApiSpexTest.Router.call([])
+
+ assert Jason.decode!(conn.resp_body) == %{
+ "data" => %{
+ "pet_type" => "Dog",
+ "bark" => "woof"
+ }
+ }
+ end
+
test "Cookie params" do
conn =
:post
|> Plug.Test.conn("/api/pets/1/adopt")
|> Plug.Conn.put_req_header("content-type", "application/json; charset=UTF-8")
|> Plug.Conn.put_req_header("x-user-id", "123456")
|> Plug.Conn.put_req_header("cookie", "debug=1")
|> OpenApiSpexTest.Router.call([])
assert Jason.decode!(conn.resp_body) == %{
"data" => %{
"pet_type" => "Debug-Dog",
"bark" => "woof"
}
}
end
+ test "Discriminator with mapping" do
+ body =
+ Jason.encode!(%{
+ appointment_type: "grooming",
+ hair_trim: true,
+ nail_clip: false
+ })
+
+ conn =
+ :post
+ |> Plug.Test.conn("/api/pets/appointment", body)
+ |> Plug.Conn.put_req_header("content-type", "application/json")
+ |> OpenApiSpexTest.Router.call([])
+
+ assert Jason.decode!(conn.resp_body) == %{"data" => [%{"pet_type" => "Dog", "bark" => "bow wow"}]}
+ end
+
test "freeForm params" do
conn =
:get
|> Plug.Test.conn("/api/utility/echo/any?one=this&two=cam&three=be&anything=true")
|> OpenApiSpexTest.Router.call([])
assert Jason.decode!(conn.resp_body) == %{
"one" => "this",
"two" => "cam",
"three" => "be",
"anything" => "true"
}
end
end
end
diff --git a/test/support/pet_controller.ex b/test/support/pet_controller.ex
index 0707547..52ccf46 100644
--- a/test/support/pet_controller.ex
+++ b/test/support/pet_controller.ex
@@ -1,128 +1,150 @@
defmodule OpenApiSpexTest.PetController do
use Phoenix.Controller
alias OpenApiSpex.Operation
alias OpenApiSpexTest.Schemas
plug OpenApiSpex.Plug.CastAndValidate
def open_api_operation(action) do
apply(__MODULE__, :"#{action}_operation", [])
end
@doc """
API Spec for :show action
"""
def show_operation() do
import Operation
%Operation{
tags: ["pets"],
summary: "Show pet",
description: "Show a pet by ID",
operationId: "PetController.show",
parameters: [
parameter(:id, :path, :integer, "Pet ID", example: 123, minimum: 1)
],
responses: %{
200 => response("Pet", "application/json", Schemas.PetResponse)
}
}
end
def show(conn, %{id: _id}) do
json(conn, %Schemas.PetResponse{
data: %Schemas.Dog{
pet_type: "Dog",
bark: "woof"
}
})
end
def index_operation() do
import Operation
%Operation{
tags: ["pets"],
summary: "List pets",
description: "List all petes",
operationId: "PetController.index",
parameters: [
parameter(:validParam, :query, :boolean, "Valid Param", example: true)
],
responses: %{
200 => response("Pet List Response", "application/json", Schemas.PetsResponse)
}
}
end
def index(conn, _params) do
json(conn, %Schemas.PetsResponse{
data: [
%Schemas.Dog{
pet_type: "Dog",
bark: "joe@gmail.com"
}
]
})
end
def create_operation() do
import Operation
%Operation{
tags: ["pets"],
summary: "Create pet",
description: "Create a pet",
operationId: "PetController.create",
parameters: [],
requestBody: request_body("The pet attributes", "application/json", Schemas.PetRequest),
responses: %{
201 => response("Pet", "application/json", Schemas.PetRequest)
}
}
end
def create(conn = %{body_params: %Schemas.PetRequest{pet: pet}}, _) do
json(conn, %Schemas.PetResponse{
data: pet
})
end
def adopt_operation() do
import Operation
%Operation{
tags: ["pets"],
summary: "Adopt pet",
description: "Adopt a pet",
operationId: "PetController.adopt",
parameters: [
parameter(:"x-user-id", :header, :string, "User that performs this action", required: true),
parameter(:id, :path, :integer, "Pet ID", example: 123, minimum: 1),
- parameter(:status, :query, :string, "New status"),
+ parameter(:status, :query, Schemas.PetStatus, "New status"),
parameter(:debug, :cookie, %OpenApiSpex.Schema{type: :integer, enum: [0, 1], default: 0}, "Debug"),
],
responses: %{
200 => response("Pet", "application/json", Schemas.PetRequest)
}
}
end
def adopt(conn, %{:"x-user-id" => _user_id, :id => _id, :debug => 0}) do
json(conn, %Schemas.PetResponse{
data: %Schemas.Dog{
pet_type: "Dog",
bark: "woof"
}
})
end
def adopt(conn, %{:"x-user-id" => _user_id, :id => _id, :debug => 1}) do
json(conn, %Schemas.PetResponse{
data: %Schemas.Dog{
pet_type: "Debug-Dog",
bark: "woof"
}
})
end
+
+ def appointment_operation() do
+ import Operation
+
+ %Operation{
+ tags: ["pets"],
+ summary: "Create pet",
+ description: "Create a pet",
+ operationId: "PetController.appointment",
+ parameters: [],
+ requestBody: request_body("The pet attributes", "application/json", Schemas.PetAppointmentRequest),
+ responses: %{
+ 201 => response("Pet", "application/json", Schemas.PetResponse)
+ }
+ }
+ end
+
+ def appointment(conn, _) do
+ json(conn, %Schemas.PetResponse{
+ data: [%{pet_type: "Dog", bark: "bow wow"}]
+})
+ end
end
diff --git a/test/support/router.ex b/test/support/router.ex
index d6528cf..159a457 100644
--- a/test/support/router.ex
+++ b/test/support/router.ex
@@ -1,29 +1,30 @@
defmodule OpenApiSpexTest.Router do
use Phoenix.Router
alias Plug.Parsers
alias OpenApiSpex.Plug.{PutApiSpec, RenderSpec}
pipeline :api do
plug :accepts, ["json"]
plug PutApiSpec, module: OpenApiSpexTest.ApiSpec
plug Parsers, parsers: [:json], pass: ["text/*"], json_decoder: Jason
end
scope "/api", OpenApiSpexTest do
pipe_through :api
resources "/users", UserController, only: [:create, :index, :show]
# Used by ParamsTest
resources "/custom_error_users", CustomErrorUserController, only: [:index]
get "/users/:id/payment_details", UserController, :payment_details
post "/users/:id/contact_info", UserController, :contact_info
post "/users/create_entity", UserController, :create_entity
get "/openapi", RenderSpec, []
resources "/pets", PetController, only: [:create, :index, :show]
post "/pets/:id/adopt", PetController, :adopt
+ post "/pets/appointment", PetController, :appointment
get "/utility/echo/any", UtilityController, :echo_any
end
end
diff --git a/test/support/schemas.ex b/test/support/schemas.ex
index ed2003a..b6eccef 100644
--- a/test/support/schemas.ex
+++ b/test/support/schemas.ex
@@ -1,409 +1,498 @@
defmodule OpenApiSpexTest.Schemas do
require OpenApiSpex
alias OpenApiSpex.Reference
alias OpenApiSpex.Schema
defmodule Helper do
def prepare_struct([%Reference{"$ref": "#/components/schemas/" <> name} | tail]) do
schema =
apply(String.to_existing_atom("Elixir.OpenApiSpexTest.Schemas." <> name), :schema, [])
prepare_struct([schema | tail])
end
def prepare_struct([%Schema{properties: props} | tail]) when is_map(props) do
keys = Map.keys(props)
keys ++ prepare_struct(tail)
end
def prepare_struct([%Schema{allOf: allOf} | tail]) when is_list(allOf) do
prepare_struct(allOf ++ tail)
end
def prepare_struct([module_name | tail]) when is_atom(module_name) do
prepare_struct([module_name.schema() | tail])
end
def prepare_struct([]) do
[]
end
end
defmodule Size do
OpenApiSpex.schema(%{
title: "Size",
description: "A size of a pet",
type: :object,
properties: %{
unit: %Schema{type: :string, description: "SI unit name", default: "cm"},
value: %Schema{type: :integer, description: "Size in given unit", default: 100}
},
required: [:unit, :value]
})
end
defmodule User do
OpenApiSpex.schema(%{
title: "User",
description: "A user of the app",
type: :object,
properties: %{
id: %Schema{type: :integer, description: "User ID"},
name: %Schema{type: :string, description: "User name", pattern: ~r/[a-zA-Z][a-zA-Z0-9_]+/},
email: %Schema{type: :string, description: "Email address", format: :email},
inserted_at: %Schema{
type: :string,
description: "Creation timestamp",
format: :"date-time"
},
updated_at: %Schema{type: :string, description: "Update timestamp", format: :"date-time"}
},
required: [:name, :email],
additionalProperties: false,
example: %{
"id" => 123,
"name" => "Joe User",
"email" => "joe@gmail.com",
"inserted_at" => "2017-09-12T12:34:55Z",
"updated_at" => "2017-09-13T10:11:12Z"
}
})
end
defmodule ContactInfo do
OpenApiSpex.schema(%{
title: "ContactInfo",
description: "A users contact information",
type: :object,
properties: %{
phone_number: %Schema{type: :string, description: "Phone number"},
postal_address: %Schema{type: :string, description: "Postal address"}
},
required: [:phone_number],
additionalProperties: false,
example: %{
"phone_number" => "555-123-456",
"postal_address" => "123 Evergreen Tce"
}
})
end
defmodule CreditCardPaymentDetails do
OpenApiSpex.schema(%{
title: "CreditCardPaymentDetails",
description: "Payment details when using credit-card method",
type: :object,
properties: %{
credit_card_number: %Schema{type: :string, description: "Credit card number"},
name_on_card: %Schema{type: :string, description: "Name as appears on card"},
expiry: %Schema{type: :string, description: "4 digit expiry MMYY"}
},
required: [:credit_card_number, :name_on_card, :expiry],
example: %{
"credit_card_number" => "1234-5678-1234-6789",
"name_on_card" => "Joe User",
"expiry" => "1234"
}
})
end
defmodule DirectDebitPaymentDetails do
OpenApiSpex.schema(%{
title: "DirectDebitPaymentDetails",
description: "Payment details when using direct-debit method",
type: :object,
properties: %{
account_number: %Schema{type: :string, description: "Bank account number"},
account_name: %Schema{type: :string, description: "Name of account"},
bsb: %Schema{type: :string, description: "Branch identifier"}
},
required: [:account_number, :account_name, :bsb],
example: %{
"account_number" => "12349876",
"account_name" => "Joes Savings Account",
"bsb" => "123-4567"
}
})
end
defmodule PaymentDetails do
OpenApiSpex.schema(%{
title: "PaymentDetails",
description: "Abstract Payment details type",
type: :object,
oneOf: [
CreditCardPaymentDetails,
DirectDebitPaymentDetails
]
})
end
defmodule UserRequest do
OpenApiSpex.schema(%{
title: "UserRequest",
description: "POST body for creating a user",
type: :object,
properties: %{
user: User
},
example: %{
"user" => %{
"name" => "Joe User",
"email" => "joe@gmail.com"
}
}
})
end
defmodule UserResponse do
OpenApiSpex.schema(%{
title: "UserResponse",
description: "Response schema for single user",
type: :object,
properties: %{
data: User
},
example: %{
"data" => %{
"id" => 123,
"name" => "Joe User",
"email" => "joe@gmail.com",
"inserted_at" => "2017-09-12T12:34:55Z",
"updated_at" => "2017-09-13T10:11:12Z"
}
}
})
end
defmodule UsersResponse do
OpenApiSpex.schema(%{
title: "UsersResponse",
description: "Response schema for multiple users",
type: :object,
properties: %{
data: %Schema{description: "The users details", type: :array, items: User}
},
example: %{
"data" => [
%{
"id" => 123,
"name" => "Joe User",
"email" => "joe@gmail.com"
},
%{
"id" => 456,
"name" => "Jay Consumer",
"email" => "jay@yahoo.com"
}
]
}
})
end
defmodule EntityWithDict do
OpenApiSpex.schema(%{
title: "EntityWithDict",
description: "Entity with a dictionary defined via additionalProperties",
type: :object,
properties: %{
id: %Schema{type: :integer, description: "Entity ID"},
stringDict: %Schema{
type: :object,
description: "String valued dict",
additionalProperties: %Schema{type: :string}
},
anyTypeDict: %Schema{
type: :object,
description: "Untyped valued dict",
additionalProperties: true
}
},
example: %{
"id" => 123,
"stringDict" => %{"key1" => "value1", "key2" => "value2"},
"anyTypeDict" => %{"key1" => 42, "key2" => %{"foo" => "bar"}}
}
})
end
defmodule PetType do
require OpenApiSpex
OpenApiSpex.schema(%{
title: "PetType",
type: :object,
required: [:pet_type],
properties: %{
pet_type: %Schema{
type: :string
}
}
})
end
defmodule Cat do
alias OpenApiSpex.Schema
@behaviour OpenApiSpex.Schema
@derive [Jason.Encoder]
@schema %Schema{
title: "Cat",
type: :object,
allOf: [
PetType,
%Schema{
type: :object,
properties: %{
meow: %Schema{type: :string}
},
required: [:meow]
}
],
"x-struct": __MODULE__
}
def schema, do: @schema
defstruct OpenApiSpexTest.Schemas.Helper.prepare_struct(@schema.allOf)
end
defmodule Dog do
alias OpenApiSpex.Schema
@behaviour OpenApiSpex.Schema
@derive [Jason.Encoder]
@schema %Schema{
title: "Dog",
type: :object,
allOf: [
PetType,
%Schema{
type: :object,
properties: %{
bark: %Schema{type: :string}
},
required: [:bark]
}
],
"x-struct": __MODULE__
}
def schema, do: @schema
defstruct OpenApiSpexTest.Schemas.Helper.prepare_struct(@schema.allOf)
end
defmodule CatOrDog do
require OpenApiSpex
OpenApiSpex.schema(%{
title: "CatOrDog",
oneOf: [Cat, Dog]
})
end
defmodule Array do
require OpenApiSpex
OpenApiSpex.schema(%{
title: "Array",
type: :array,
items: Dog,
example: [%{pet_type: "Dog", bark: "A lot"}]
})
end
defmodule Primitive do
require OpenApiSpex
OpenApiSpex.schema(%{
title: "Primitive",
type: :integer,
example: 1
})
end
defmodule PrimitiveArray do
require OpenApiSpex
OpenApiSpex.schema(%{
title: "PrimitiveArray",
type: :array,
items: %Schema{type: "string"},
example: ["Foo"]
})
end
defmodule PetResponse do
OpenApiSpex.schema(%{
title: "PetResponse",
description: "Response schema for single pet",
type: :object,
properties: %{
data: OpenApiSpexTest.Schemas.CatOrDog
},
example: %{
"data" => %{
"pet_type" => "Dog",
"bark" => "woof"
}
}
})
end
defmodule Pet do
require OpenApiSpex
alias OpenApiSpex.{Schema, Discriminator}
OpenApiSpex.schema(%{
title: "Pet",
type: :object,
oneOf: [Cat, Dog],
discriminator: %Discriminator{
propertyName: "pet_type"
}
})
end
defmodule PetsResponse do
OpenApiSpex.schema(%{
title: "PetsResponse",
description: "Response schema for multiple pets",
type: :object,
properties: %{
data: %Schema{
description: "The pets details",
type: :array,
items: OpenApiSpexTest.Schemas.CatOrDog
}
},
example: %{
"data" => [
%{
"pet_type" => "Dog",
"bark" => "woof"
},
%{
"pet_type" => "Cat",
"meow" => "meow"
}
]
}
})
end
defmodule PetRequest do
OpenApiSpex.schema(%{
title: "PetRequest",
description: "POST body for creating a pet",
type: :object,
properties: %{
pet: CatOrDog
},
example: %{
"pet" => %{
"pet_type" => "Dog",
"bark" => "woof"
}
}
})
end
+
+ defmodule PetStatus do
+ OpenApiSpex.schema(%{
+ title: "PetStatus",
+ description: "The current status of a pet",
+ type: :string,
+ enum: ["adopted", "unadopted"]
+ })
+ end
+
+ defmodule AppointmentType do
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "AppointmentType",
+ type: :object,
+ properties: %{
+ appointment_type: %Schema{
+ type: :string
+ }
+ },
+ required: [:appointment_type]
+ })
+ end
+
+ defmodule TrainingAppointment do
+ OpenApiSpex.schema(%{
+ title: "TrainingAppointment",
+ description: "Request for a training appointment",
+ type: :object,
+ allOf: [
+ AppointmentType,
+ %Schema{
+ type: :object,
+ properties: %{
+ level: %Schema{
+ description: "The level of training",
+ type: :integer
+ }
+ },
+ required: [:level]
+ }
+ ]
+ })
+ end
+
+ defmodule GroomingAppointment do
+ OpenApiSpex.schema(%{
+ title: "GroomingAppointment",
+ description: "Request for a training appointment",
+ type: :object,
+ allOf: [
+ AppointmentType,
+ %Schema{
+ type: :object,
+ properties: %{
+ hair_trim: %Schema{
+ description: "Whether or not to include a hair trim",
+ type: :boolean
+ },
+ nail_clip: %Schema{
+ description: "Whether or not to include nail clip",
+ type: :boolean
+ }
+ },
+ required: [:hair_trim, :nail_clip]
+ }
+ ]
+ })
+ end
+
+ defmodule PetAppointmentRequest do
+ OpenApiSpex.schema(%{
+ title: "PetAppointmentRequest",
+ description: "POST body for making a pet appointment",
+ type: :object,
+ oneOf: [
+ TrainingAppointment,
+ GroomingAppointment
+ ],
+ discriminator: %OpenApiSpex.Discriminator{
+ propertyName: "appointment_type",
+ mapping: %{
+ "training" => "TrainingAppointment",
+ "grooming" => "GroomingAppointment"
+ }
+ }
+ })
+ end
end

File Metadata

Mime Type
text/x-diff
Expires
Tue, Nov 26, 12:06 PM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40294
Default Alt Text
(34 KB)

Event Timeline