Page MenuHomePhorge

No OneTemporary

Size
69 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex.ex b/lib/open_api_spex.ex
index 220bf77..b6d4b64 100644
--- a/lib/open_api_spex.ex
+++ b/lib/open_api_spex.ex
@@ -1,82 +1,98 @@
defmodule OpenApiSpex do
+ @moduledoc """
+ Provides the entry-points for defining schemas, validating and casting.
+ """
+
alias OpenApiSpex.{OpenApi, Operation, Reference, Schema, SchemaResolver}
- @moduledoc """
+ @doc """
+ Adds schemas to the api spec from the modules specified in the Operations.
+
+ Eg, if the response schema for an operation is defined with:
+
+ responses: %{
+ 200 => Operation.response("User", "application/json", UserResponse)
+ }
+
+ Then the `UserResponse.schema()` function will be called to load the schema, and
+ a `Reference` to the loaded schema will be used in the operation response.
+
+ See `OpenApiSpex.schema` macro for a convenient syntax for defining schema modules.
"""
def resolve_schema_modules(spec = %OpenApi{}) do
SchemaResolver.resolve_schema_modules(spec)
end
@doc """
Cast params to conform to a Schema or Operation spec.
"""
@spec cast(OpenApi.t, Schema.t | Reference.t, any) :: {:ok, any} | {:error, String.t}
def cast(spec = %OpenApi{}, schema = %Schema{}, params) do
Schema.cast(schema, params, spec.components.schemas)
end
def cast(spec = %OpenApi{}, schema = %Reference{}, params) do
Schema.cast(schema, params, spec.components.schemas)
end
def cast(spec = %OpenApi{}, operation = %Operation{}, params, content_type \\ nil) do
Operation.cast(operation, params, content_type, spec.components.schemas)
end
@doc """
Validate params against a Schema or Operation spec.
"""
def validate(spec = %OpenApi{}, schema = %Schema{}, params) do
Schema.validate(schema, params, spec.components.schemas)
end
def validate(spec = %OpenApi{}, schema = %Reference{}, params) do
Schema.validate(schema, params, spec.components.schemas)
end
def validate(spec = %OpenApi{}, operation = %Operation{}, params = %{}, content_type \\ nil) do
Operation.validate(operation, params, content_type, spec.components.schemas)
end
@doc """
Declares a OpenApiSpex Schema
- defines the schema/0 callback
- ensures the schema is linked to the module by "x-struct" extension property
- defines a struct with keys matching the schema properties
- defines a @type `t` for the struct
- derives a `Poison.Encoder` for the struct
## Example
require OpenApiSpex
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],
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
"""
defmacro schema(body) do
quote do
@schema struct(OpenApiSpex.Schema, Map.put(unquote(body), :"x-struct", __MODULE__))
def schema, do: @schema
@derive [Poison.Encoder]
defstruct Map.keys(@schema.properties)
@type t :: %__MODULE__{}
end
end
end
diff --git a/lib/open_api_spex/callback.ex b/lib/open_api_spex/callback.ex
index 38d4d50..8239f82 100644
--- a/lib/open_api_spex/callback.ex
+++ b/lib/open_api_spex/callback.ex
@@ -1,6 +1,19 @@
defmodule OpenApiSpex.Callback do
+ @moduledoc """
+ Defines the `OpenApiSpex.Callback.t` type.
+ """
alias OpenApiSpex.PathItem
+
+ @typedoc """
+ [Callback Object](https://swagger.io/specification/#callbackObject)
+
+ A map of possible out-of band callbacks related to the parent operation.
+ Each value in the map is a Path Item Object that describes a set of requests
+ that may be initiated by the API provider and the expected responses.
+ The key value used to identify the callback object is an expression, evaluated at runtime,
+ that identifies a URL to use for the callback operation.
+ """
@type t :: %{
String.t => PathItem.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/components.ex b/lib/open_api_spex/components.ex
index a19c199..95ba30c 100644
--- a/lib/open_api_spex/components.ex
+++ b/lib/open_api_spex/components.ex
@@ -1,29 +1,40 @@
defmodule OpenApiSpex.Components do
+ @moduledoc """
+ Defines the `OpenApiSpex.Components.t` type.
+ """
alias OpenApiSpex.{
Schema, Reference, Response, Parameter, Example,
RequestBody, Header, SecurityScheme, Link, Callback,
Components
}
defstruct [
:schemas,
:responses,
:parameters,
:examples,
:requestBodies,
:headers,
:securitySchemes,
:links,
:callbacks,
]
+
+ @typedoc """
+ [Components Object](https://swagger.io/specification/#componentsObject)
+
+ Holds a set of reusable objects for different aspects of the OAS.
+ All objects defined within the components object will have no effect on the API unless
+ they are explicitly referenced from properties outside the components object.
+ """
@type t :: %Components{
schemas: %{String.t => Schema.t | Reference.t},
responses: %{String.t => Response.t | Reference.t},
parameters: %{String.t => Parameter.t | Reference.t},
examples: %{String.t => Example.t | Reference.t},
requestBodies: %{String.t => RequestBody.t | Reference.t},
headers: %{String.t => Header.t | Reference.t},
securitySchemes: %{String.t => SecurityScheme.t | Reference.t},
links: %{String.t => Link.t | Reference.t},
callbacks: %{String.t => Callback.t | Reference.t}
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/contact.ex b/lib/open_api_spex/contact.ex
index edbe286..4a301ab 100644
--- a/lib/open_api_spex/contact.ex
+++ b/lib/open_api_spex/contact.ex
@@ -1,12 +1,22 @@
defmodule OpenApiSpex.Contact do
+ @moduledoc """
+ Defines the `OpenApiSpex.Contact.t` type.
+ """
+
defstruct [
:name,
:url,
:email
]
+
+ @typedoc """
+ [Contact Object](https://swagger.io/specification/#contactObject)
+
+ Contact information for the exposed API.
+ """
@type t :: %__MODULE__{
name: String.t,
url: String.t,
email: String.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/discriminator.ex b/lib/open_api_spex/discriminator.ex
index 45a9b1c..84d9912 100644
--- a/lib/open_api_spex/discriminator.ex
+++ b/lib/open_api_spex/discriminator.ex
@@ -1,10 +1,22 @@
defmodule OpenApiSpex.Discriminator do
+ @moduledoc """
+ Defines the `OpenApiSpex.Discriminator.t` type.
+ """
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.
+ """
@type t :: %__MODULE__{
propertyName: String.t,
mapping: %{String.t => String.t}
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/encoding.ex b/lib/open_api_spex/encoding.ex
index aa4ddb6..ece2641 100644
--- a/lib/open_api_spex/encoding.ex
+++ b/lib/open_api_spex/encoding.ex
@@ -1,17 +1,26 @@
defmodule OpenApiSpex.Encoding do
+ @moduledoc """
+ Defines the `OpenApiSpex.Encoding.t` type.
+ """
alias OpenApiSpex.{Header, Reference, Parameter}
defstruct [
:contentType,
:headers,
:style,
:explode,
:allowReserved
]
+
+ @typedoc """
+ [Encoding Object](https://swagger.io/specification/#encodingObject)
+
+ A single encoding definition applied to a single schema property.
+ """
@type t :: %__MODULE__{
contentType: String.t,
headers: %{String.t => Header.t | Reference.t},
style: Parameter.style,
explode: boolean,
allowReserved: boolean
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/example.ex b/lib/open_api_spex/example.ex
index d03eee7..456710e 100644
--- a/lib/open_api_spex/example.ex
+++ b/lib/open_api_spex/example.ex
@@ -1,14 +1,23 @@
defmodule OpenApiSpex.Example do
+ @moduledoc """
+ Defines the `OpenApiSpex.Example.t` type.
+ """
defstruct [
:summary,
:description,
:value,
:externalValue
]
+
+ @typedoc """
+ [Example Object](https://swagger.io/specification/#exampleObject)
+
+ In all cases, the example value is expected to be compatible with the type schema of its associated value.
+ """
@type t :: %__MODULE__{
summary: String.t,
description: String.t,
value: any,
externalValue: String.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/external_documentation.ex b/lib/open_api_spex/external_documentation.ex
index 9af2574..0c9eac3 100644
--- a/lib/open_api_spex/external_documentation.ex
+++ b/lib/open_api_spex/external_documentation.ex
@@ -1,10 +1,19 @@
defmodule OpenApiSpex.ExternalDocumentation do
+ @moduledoc """
+ Defines the `OpenApiSpex.ExternalDocumentation.t` type.
+ """
defstruct [
:description,
:url
]
+
+ @typedoc """
+ [External Documentation Object](https://swagger.io/specification/#externalDocumentationObject)
+
+ Allows referencing an external resource for extended documentation.
+ """
@type t :: %__MODULE__{
description: String.t,
url: String.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/header.ex b/lib/open_api_spex/header.ex
index c1380e8..534d5ba 100644
--- a/lib/open_api_spex/header.ex
+++ b/lib/open_api_spex/header.ex
@@ -1,25 +1,38 @@
defmodule OpenApiSpex.Header do
+ @moduledoc """
+ Defines the `OpenApiSpex.Header.t` type.
+ """
alias OpenApiSpex.{Schema, Reference, Example}
defstruct [
:description,
:required,
:deprecated,
:allowEmptyValue,
:explode,
:schema,
:example,
:examples,
style: :simple
]
+
+ @typedoc """
+ [Header Object](https://swagger.io/specification/#headerObject)
+
+ The Header Object follows the structure of the Parameter Object with the following changes:
+
+ - name MUST NOT be specified, it is given in the corresponding headers map.
+ - in MUST NOT be specified, it is implicitly in header.
+ - All traits that are affected by the location MUST be applicable to a location of header (for example, style).
+ """
@type t :: %__MODULE__{
description: String.t,
required: boolean,
deprecated: boolean,
allowEmptyValue: boolean,
style: :simple,
explode: boolean,
schema: Schema.t | Reference.t,
example: any,
examples: %{String.t => Example.t | Reference.t}
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/info.ex b/lib/open_api_spex/info.ex
index 8950197..134c22b 100644
--- a/lib/open_api_spex/info.ex
+++ b/lib/open_api_spex/info.ex
@@ -1,20 +1,30 @@
defmodule OpenApiSpex.Info do
+ @moduledoc """
+ Defines the `OpenApiSpex.Info.t` type.
+ """
alias OpenApiSpex.{Contact, License}
@enforce_keys [:title, :version]
defstruct [
:title,
:description,
:termsOfService,
:contact,
:license,
:version
]
+
+ @typedoc """
+ [Info Object](https://swagger.io/specification/#infoObject)
+
+ The object provides metadata about the API. The metadata MAY be used by the clients if needed,
+ and MAY be presented in editing or documentation generation tools for convenience.
+ """
@type t :: %__MODULE__{
title: String.t,
description: String.t,
termsOfService: String.t,
contact: Contact.t,
license: License.t,
version: String.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/license.ex b/lib/open_api_spex/license.ex
index 4e013f4..d3a9af2 100644
--- a/lib/open_api_spex/license.ex
+++ b/lib/open_api_spex/license.ex
@@ -1,10 +1,19 @@
defmodule OpenApiSpex.License do
+ @moduledoc """
+ Defines the `OpenApiSpex.License.t` type.
+ """
defstruct [
:name,
:url
]
+
+ @typedoc """
+ [License Object](https://swagger.io/specification/#licenseObject)
+
+ License information for the exposed API.
+ """
@type t :: %__MODULE__{
name: String.t,
url: String.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/link.ex b/lib/open_api_spex/link.ex
index a8220d8..505721e 100644
--- a/lib/open_api_spex/link.ex
+++ b/lib/open_api_spex/link.ex
@@ -1,19 +1,38 @@
defmodule OpenApiSpex.Link do
+ @moduledoc """
+ Defines the `OpenApiSpex.Link.t` type.
+ """
alias OpenApiSpex.Server
defstruct [
:operationRef,
:operationId,
:parameters,
:requestBody,
:description,
:server
]
+
+ @typedoc """
+ [Link Object](https://swagger.io/specification/#linkObject)
+
+ The Link object represents a possible design-time link for a response.
+
+ The presence of a link does not guarantee the caller's ability to successfully invoke it,
+ rather it provides a known relationship and traversal mechanism between responses and other operations.
+
+ Unlike dynamic links (i.e. links provided in the response payload),
+ the OAS linking mechanism does not require link information in the runtime response.
+
+ For computing links, and providing instructions to execute them,
+ a runtime expression is used for accessing values in an operation and
+ using them as parameters while invoking the linked operation.
+ """
@type t :: %__MODULE__{
operationRef: String.t,
operationId: String.t,
parameters: %{String.t => any},
requestBody: any,
description: String.t,
server: Server.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/media_type.ex b/lib/open_api_spex/media_type.ex
index 0045133..e9d0b5d 100644
--- a/lib/open_api_spex/media_type.ex
+++ b/lib/open_api_spex/media_type.ex
@@ -1,15 +1,24 @@
defmodule OpenApiSpex.MediaType do
+ @moduledoc """
+ Defines the `OpenApiSpex.MediaType.t` type.
+ """
alias OpenApiSpex.{Schema, Reference, Example, Encoding}
defstruct [
:schema,
:example,
:examples,
:encoding
]
+
+ @typedoc """
+ [Media Type Object](https://swagger.io/specification/#mediaTypeObject)
+
+ Each Media Type Object provides schema and examples for the media type identified by its key.
+ """
@type t :: %__MODULE__{
schema: Schema.t | Reference.t,
example: any,
examples: %{String.t => Example.t | Reference.t},
encoding: %{String => Encoding.t}
}
end
diff --git a/lib/open_api_spex/oauth_flow.ex b/lib/open_api_spex/oauth_flow.ex
index 84cac43..96a54d6 100644
--- a/lib/open_api_spex/oauth_flow.ex
+++ b/lib/open_api_spex/oauth_flow.ex
@@ -1,14 +1,23 @@
defmodule OpenApiSpex.OAuthFlow do
+ @moduledoc """
+ Defines the `OpenApiSpex.OAuthFlow.t` type.
+ """
defstruct [
:authorizationUrl,
:tokenUrl,
:refreshUrl,
:scopes
]
+
+ @typedoc """
+ [OAuth Flow Object](https://swagger.io/specification/#oauthFlowObject)
+
+ Configuration details for a supported OAuth Flow
+ """
@type t :: %__MODULE__{
authorizationUrl: String.t,
tokenUrl: String.t,
refreshUrl: String.t,
scopes: %{String.t => String.t}
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/oauth_flows.ex b/lib/open_api_spex/oauth_flows.ex
index abebedf..9457f08 100644
--- a/lib/open_api_spex/oauth_flows.ex
+++ b/lib/open_api_spex/oauth_flows.ex
@@ -1,15 +1,24 @@
defmodule OpenApiSpex.OAuthFlows do
+ @moduledoc """
+ Defines the `OpenApiSpex.OAuthFlows.t` type.
+ """
alias OpenApiSpex.OAuthFlow
defstruct [
:implicit,
:password,
:clientCredentials,
:authorizationCode
]
+
+ @typedoc """
+ [OAuth Flows Object](https://swagger.io/specification/#oauthFlowsObject)
+
+ Allows configuration of the supported OAuth Flows.
+ """
@type t :: %__MODULE__{
implicit: OAuthFlow.t,
password: OAuthFlow.t,
clientCredentials: OAuthFlow.t,
authorizationCode: OAuthFlow.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/open_api.ex b/lib/open_api_spex/open_api.ex
index 72a0107..273d3d4 100644
--- a/lib/open_api_spex/open_api.ex
+++ b/lib/open_api_spex/open_api.ex
@@ -1,56 +1,65 @@
defmodule OpenApiSpex.OpenApi do
+ @moduledoc """
+ Defines the `OpenApiSpex.OpenApi.t` type.
+ """
alias OpenApiSpex.{
Info, Server, Paths, Components,
SecurityRequirement, Tag, ExternalDocumentation,
OpenApi
}
defstruct [
:info,
:servers,
:paths,
:components,
:security,
:tags,
:externalDocs,
openapi: "3.0",
]
+
+ @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],
paths: Paths.t,
components: Components.t,
security: [SecurityRequirement.t],
tags: [Tag.t],
externalDocs: ExternalDocumentation.t
}
defimpl Poison.Encoder do
def encode(api_spec = %OpenApi{}, options) do
api_spec
|> to_json()
|> Poison.Encoder.encode(options)
end
defp to_json(%Regex{source: source}), do: source
defp to_json(value = %{__struct__: _}) do
value
|> Map.from_struct()
|> 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
end
end
\ No newline at end of file
diff --git a/lib/open_api_spex/operation.ex b/lib/open_api_spex/operation.ex
index 2bc4f96..bcf19d9 100644
--- a/lib/open_api_spex/operation.ex
+++ b/lib/open_api_spex/operation.ex
@@ -1,184 +1,198 @@
defmodule OpenApiSpex.Operation do
+ @moduledoc """
+ Defines the `OpenApiSpex.Operation.t` type.
+ """
alias OpenApiSpex.{
Callback,
ExternalDocumentation,
MediaType,
Operation,
Parameter,
Reference,
RequestBody,
Response,
Responses,
Schema,
SecurityRequirement,
Server,
}
defstruct [
:tags,
:summary,
:description,
:externalDocs,
:operationId,
:parameters,
:requestBody,
:responses,
:callbacks,
:deprecated,
:security,
:servers
]
+
+ @typedoc """
+ [Operation Object](https://swagger.io/specification/#operationObject)
+
+ Describes a single API operation on a path.
+ """
@type t :: %__MODULE__{
tags: [String.t],
summary: String.t,
description: String.t,
externalDocs: ExternalDocumentation.t,
operationId: String.t,
parameters: [Parameter.t | Reference.t],
requestBody: [RequestBody.t | Reference.t],
responses: Responses.t,
callbacks: %{
String.t => Callback.t | Reference.t
},
deprecated: boolean,
security: [SecurityRequirement.t],
servers: [Server.t]
}
@doc """
Constructs an Operation struct from the plug and opts specified in the given route
"""
@spec from_route(PathItem.route) :: t
def from_route(route) do
from_plug(route.plug, route.opts)
end
@doc """
Constructs an Operation struct from plug module and opts
"""
@spec from_plug(module, any) :: t
def from_plug(plug, opts) do
plug.open_api_operation(opts)
end
@doc """
Shorthand for constructing a Parameter name, location, type, description and optional examples
"""
@spec parameter(atom, Parameter.location, Reference.t | Schema.t | atom, String.t, keyword) :: Parameter.t
def parameter(name, location, type, description, opts \\ []) do
params =
[name: name, in: location, description: description, required: location == :path]
|> Keyword.merge(opts)
Parameter
|> struct(params)
|> Parameter.put_schema(type)
end
@doc """
Shorthand for constructing a RequestBody with description, media_type, schema and optional examples
"""
@spec request_body(String.t, String.t, (Schema.t | Reference.t | module), keyword) :: RequestBody.t
def request_body(description, media_type, schema_ref, opts \\ []) do
%RequestBody{
description: description,
content: %{
media_type => %MediaType{
schema: schema_ref,
example: opts[:example],
examples: opts[:examples]
}
},
required: opts[:required] || false
}
end
@doc """
Shorthand for constructing a Response with description, media_type, schema and optional examples
"""
@spec response(String.t, String.t, (Schema.t | Reference.t | module), keyword) :: Response.t
def response(description, media_type, schema_ref, opts \\ []) do
%Response{
description: description,
content: %{
media_type => %MediaType {
schema: schema_ref,
example: opts[:example],
examples: opts[:examples]
}
}
}
end
@doc """
Cast params to the types defined by the schemas of the operation parameters and requestBody
"""
@spec cast(Operation.t, map, String.t | nil, %{String.t => Schema.t}) :: {:ok, map} | {:error, String.t}
def cast(operation = %Operation{}, params = %{}, content_type, schemas) do
parameters = Enum.filter(operation.parameters, fn p -> Map.has_key?(params, Atom.to_string(p.name)) end)
with {:ok, parameter_values} <- cast_parameters(parameters, params, schemas),
{:ok, body} <- cast_request_body(operation.requestBody, params, content_type, schemas) do
{:ok, Map.merge(parameter_values, body)}
end
end
- def cast_parameters([], _params, _schemas), do: {:ok, %{}}
- def cast_parameters([p | rest], params = %{}, schemas) do
+ @spec cast_parameters([Parameter.t], map, %{String.t => Schema.t}) :: {:ok, map} | {:error, String.t}
+ defp cast_parameters([], _params, _schemas), do: {:ok, %{}}
+ defp cast_parameters([p | rest], params = %{}, schemas) do
with {:ok, cast_val} <- Schema.cast(Parameter.schema(p), params[Atom.to_string(p.name)], schemas),
{:ok, cast_tail} <- cast_parameters(rest, params, schemas) do
{:ok, Map.put_new(cast_tail, p.name, cast_val)}
end
end
- def cast_request_body(nil, _, _, _), do: {:ok, %{}}
- def cast_request_body(%RequestBody{content: content}, params, content_type, schemas) do
+ @spec cast_request_body(RequestBody.t | nil, map, String.t | nil, %{String.t => Schema.t}) :: {:ok, map} | {:error, String.t}
+ defp cast_request_body(nil, _, _, _), do: {:ok, %{}}
+ defp cast_request_body(%RequestBody{content: content}, params, content_type, schemas) do
schema = content[content_type].schema
Schema.cast(schema, params, schemas)
end
@doc """
Validate params against the schemas of the operation parameters and requestBody
"""
@spec validate(Operation.t, map, String.t | nil, %{String.t => Schema.t}) :: :ok | {:error, String.t}
def validate(operation = %Operation{}, params = %{}, content_type, schemas) do
with :ok <- validate_required_parameters(operation.parameters, params),
parameters <- Enum.filter(operation.parameters, &Map.has_key?(params, &1.name)),
{:ok, remaining} <- validate_parameter_schemas(parameters, params, schemas),
:ok <- validate_body_schema(operation.requestBody, remaining, content_type, schemas) do
:ok
end
end
- def validate_required_parameters(parameter_list, params = %{}) do
+ @spec validate_required_parameters([Parameter.t], map) :: :ok | {:error, String.t}
+ defp validate_required_parameters(parameter_list, params = %{}) do
required =
parameter_list
|> Stream.filter(fn parameter -> parameter.required end)
|> Enum.map(fn parameter -> parameter.name end)
missing = required -- Map.keys(params)
case missing do
[] -> :ok
_ -> {:error, "Missing required parameters: #{inspect(missing)}"}
end
end
- def validate_parameter_schemas([], params, _schemas), do: {:ok, params}
- def validate_parameter_schemas([p | rest], params, schemas) do
+ @spec validate_parameter_schemas([Parameter.t], map, %{String.t => Schema.t}) :: {:ok, map} | {:error, String.t}
+ defp validate_parameter_schemas([], params, _schemas), do: {:ok, params}
+ defp validate_parameter_schemas([p | rest], params, schemas) do
with :ok <- Schema.validate(Parameter.schema(p), params[p.name], schemas),
{:ok, remaining} <- validate_parameter_schemas(rest, params, schemas) do
{:ok, Map.delete(remaining, p.name)}
end
end
- def validate_body_schema(nil, _, _, _), do: :ok
- def validate_body_schema(%RequestBody{required: false}, params, _content_type, _schemas) when map_size(params) == 0 do
+ @spec validate_body_schema(RequestBody.t | nil, map, String.t | nil, %{String.t => Schema.t}) :: :ok | {:error, String.t}
+ defp validate_body_schema(nil, _, _, _), do: :ok
+ defp validate_body_schema(%RequestBody{required: false}, params, _content_type, _schemas) when map_size(params) == 0 do
:ok
end
- def validate_body_schema(%RequestBody{content: content}, params, content_type, schemas) do
+ defp validate_body_schema(%RequestBody{content: content}, params, content_type, schemas) do
content
|> Map.get(content_type)
|> Map.get(:schema)
|> Schema.validate(params, schemas)
end
end
\ No newline at end of file
diff --git a/lib/open_api_spex/parameter.ex b/lib/open_api_spex/parameter.ex
index c9a78e8..dfce2ce 100644
--- a/lib/open_api_spex/parameter.ex
+++ b/lib/open_api_spex/parameter.ex
@@ -1,62 +1,93 @@
defmodule OpenApiSpex.Parameter do
+ @moduledoc """
+ Defines the `OpenApiSpex.Parameter.t` type.
+ """
alias OpenApiSpex.{
Schema, Reference, Example, MediaType, Parameter
}
defstruct [
:name,
:in,
:description,
:required,
:deprecated,
:allowEmptyValue,
:style,
:explode,
:allowReserved,
:schema,
:example,
:examples,
:content,
]
- @type location :: :query | :header | :path | :cookie
+ @typedoc """
+ Valid values for the `in` key in the `OpenApiSpex.Parameter` struct.
+ """
+ @type location :: :path | :query | :header | :cookie
+
+ @typedoc """
+ Valid values for the `style` key in the `OpenApiSpex.Parameter` struct.
+ """
@type style :: :matrix | :label | :form | :simple | :spaceDelimited | :pipeDelimited | :deep
+
+ @typedoc """
+ [Parameter Object](https://swagger.io/specification/#parameterObject)
+
+ Describes a single operation parameter.
+
+ A unique parameter is defined by a combination of a name and location.
+
+ ## Parameter Locations
+
+ There are four possible parameter locations specified by the in field:
+
+ path - Used together with Path Templating, where the parameter value is actually part of the operation's URL. This does not include the host or base path of the API. For example, in /items/{itemId}, the path parameter is itemId.
+ query - Parameters that are appended to the URL. For example, in /items?id=###, the query parameter is id.
+ header - Custom headers that are expected as part of the request. Note that RFC7230 states header names are case insensitive.
+ cookie - Used to pass a specific cookie value to the API.
+ """
@type t :: %__MODULE__{
name: atom,
in: location,
description: String.t,
required: boolean,
deprecated: boolean,
allowEmptyValue: boolean,
style: style,
explode: boolean,
allowReserved: boolean,
schema: Schema.t | Reference.t | atom,
example: any,
examples: %{String.t => Example.t | Reference.t},
content: %{String.t => MediaType.t}
}
@doc """
Sets the schema for a parameter from a simple type, reference or Schema
"""
@spec put_schema(t, Reference.t | Schema.t | atom) :: t
def put_schema(parameter = %Parameter{}, type = %Reference{}) do
%{parameter | schema: type}
end
def put_schema(parameter = %Parameter{}, type = %Schema{}) do
%{parameter | schema: type}
end
def put_schema(parameter = %Parameter{}, type) when type in [:boolean, :integer, :number, :string, :array, :object] do
%{parameter | schema: %Schema{type: type}}
end
def put_schema(parameter = %Parameter{}, type) when is_atom(type) do
%{parameter | schema: type}
end
+ @doc """
+ Gets the schema for a parameter, from the `schema` key or `content` key, which ever is populated.
+ """
+ @spec schema(Parameter.t) :: Schema.t | Reference.t | atom
def schema(%Parameter{schema: schema = %{}}) do
schema
end
def schema(%Parameter{content: content = %{}}) do
{_type, %MediaType{schema: schema}} = Enum.at(content, 0)
schema
end
end
diff --git a/lib/open_api_spex/path_item.ex b/lib/open_api_spex/path_item.ex
index 4dc3a84..cb8d8f6 100644
--- a/lib/open_api_spex/path_item.ex
+++ b/lib/open_api_spex/path_item.ex
@@ -1,55 +1,72 @@
defmodule OpenApiSpex.PathItem do
+ @moduledoc """
+ Defines the `OpenApiSpex.PathItem.t` type.
+ """
+
alias OpenApiSpex.{Operation, Server, Parameter, PathItem, Reference}
defstruct [
:"$ref",
:summary,
:description,
:get,
:put,
:post,
:delete,
:options,
:head,
:patch,
:trace,
:servers,
:parameters
]
+
+ @typedoc """
+ [Path Item Object](https://swagger.io/specification/#pathItemObject)
+
+ Describes the operations available on a single path.
+ A Path Item MAY be empty, due to ACL constraints.
+ The path itself is still exposed to the documentation viewer
+ but they will not know which operations and parameters are available.
+ """
@type t :: %__MODULE__{
"$ref": String.t,
summary: String.t,
description: String.t,
get: Operation.t,
put: Operation.t,
post: Operation.t,
delete: Operation.t,
options: Operation.t,
head: Operation.t,
patch: Operation.t,
trace: Operation.t,
servers: [Server.t],
parameters: [Parameter.t | Reference.t]
}
+ @typedoc """
+ Represents a route from in a Plug/Phoenix application.
+ Eg from the generated `__routes__` function in a Phoenix.Router module.
+ """
@type route :: %{verb: atom, plug: atom, opts: any}
@doc """
Builds a PathItem struct from a list of routes that share a path.
"""
@spec from_routes([route]) :: nil | t
def from_routes(routes) do
Enum.each(routes, fn route ->
Code.ensure_loaded(route.plug)
end)
routes
|> Enum.filter(&function_exported?(&1.plug, :open_api_operation, 1))
|> from_valid_routes()
end
@spec from_valid_routes([route]) :: nil | t
defp from_valid_routes([]), do: nil
defp from_valid_routes(routes) do
struct(PathItem, Enum.map(routes, &{&1.verb, Operation.from_route(&1)}))
end
end
\ No newline at end of file
diff --git a/lib/open_api_spex/paths.ex b/lib/open_api_spex/paths.ex
index 1e9c304..4dfa579 100644
--- a/lib/open_api_spex/paths.ex
+++ b/lib/open_api_spex/paths.ex
@@ -1,25 +1,35 @@
defmodule OpenApiSpex.Paths do
+ @moduledoc """
+ Defines the `OpenApiSpex.Paths.t` type.
+ """
alias OpenApiSpex.PathItem
+ @typedoc """
+ [Paths Object](https://swagger.io/specification/#pathsObject)
+
+ Holds the relative paths to the individual endpoints and their operations.
+ The path is appended to the URL from the Server Object in order to construct the full URL.
+ The Paths MAY be empty, due to ACL constraints.
+ """
@type t :: %{String.t => PathItem.t}
@doc """
Create a Paths map from the routes in the given router module.
"""
@spec from_router(module) :: t
def from_router(router) do
router.__routes__()
|> Enum.group_by(fn route -> route.path end)
|> Enum.map(fn {k, v} -> {open_api_path(k), PathItem.from_routes(v)} end)
|> Enum.filter(fn {_k, v} -> !is_nil(v) end)
|> Map.new()
end
@spec open_api_path(String.t) :: String.t
defp open_api_path(path) do
path
|> String.split("/")
|> Enum.map(fn ":"<>segment -> "{#{segment}}"; segment -> segment end)
|> Enum.join("/")
end
end
\ No newline at end of file
diff --git a/lib/open_api_spex/reference.ex b/lib/open_api_spex/reference.ex
index 16d712a..5d40386 100644
--- a/lib/open_api_spex/reference.ex
+++ b/lib/open_api_spex/reference.ex
@@ -1,34 +1,34 @@
defmodule OpenApiSpex.Reference do
@moduledoc """
- A simple object to allow referencing other components in the specification, internally and externally.
-
- The Reference Object is defined by JSON Reference and follows the same structure, behavior and rules.
- See https://swagger.io/specification/#referenceObject
-
- ## Example
-
- ref = %OpenApiSpex.Reference{"$ref": "#/components/schemas/user"}
+ Defines the `OpenApiSpex.Reference.t` type.
"""
alias OpenApiSpex.Reference
defstruct [
:"$ref"
]
+
+ @typedoc """
+ [Reference Object](https://swagger.io/specification/#referenceObject)
+
+ A simple object to allow referencing other components in the specification, internally and externally.
+ The Reference Object is defined by JSON Reference and follows the same structure, behavior and rules.
+ """
@type t :: %Reference{
"$ref": String.t
}
@doc """
Resolve a `Reference` to the `Schema` it refers to.
## Examples
iex> alias OpenApiSpex.{Reference, Schema}
...> schemas = %{"user" => %Schema{title: "user", type: :object}}
...> Reference.resolve_schema(%Reference{"$ref": "#/components/schemas/user"}, schemas)
%OpenApiSpex.Schema{type: :object, title: "user"}
"""
@spec resolve_schema(Reference.t, %{String.t => Schema.t}) :: Schema.t | nil
def resolve_schema(%Reference{"$ref": "#/components/schemas/" <> name}, schemas), do: schemas[name]
end
\ No newline at end of file
diff --git a/lib/open_api_spex/request_body.ex b/lib/open_api_spex/request_body.ex
index 134b899..3ef6abe 100644
--- a/lib/open_api_spex/request_body.ex
+++ b/lib/open_api_spex/request_body.ex
@@ -1,13 +1,22 @@
defmodule OpenApiSpex.RequestBody do
+ @moduledoc """
+ Defines the `OpenApiSpex.RequestBody.t` type.
+ """
alias OpenApiSpex.MediaType
defstruct [
:description,
:content,
required: false
]
+
+ @typedoc """
+ [Request Body Object](https://swagger.io/specification/#requestBodyObject)
+
+ Describes a single request body.
+ """
@type t :: %__MODULE__{
description: String.t,
content: %{String.t => MediaType.t},
required: boolean
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/response.ex b/lib/open_api_spex/response.ex
index 24c121c..9b91930 100644
--- a/lib/open_api_spex/response.ex
+++ b/lib/open_api_spex/response.ex
@@ -1,15 +1,24 @@
defmodule OpenApiSpex.Response do
+ @moduledoc """
+ Defines the `OpenApiSpex.Response.t` type.
+ """
alias OpenApiSpex.{Header, Reference, MediaType, Link}
defstruct [
:description,
:headers,
:content,
:links
]
+
+ @typedoc """
+ [Response Object](https://swagger.io/specification/#responseObject)
+
+ Describes a single response from an API Operation, including design-time, static links to operations based on the response.
+ """
@type t :: %__MODULE__{
description: String.t,
headers: %{String.t => Header.t | Reference.t} | nil,
content: %{String.t => MediaType.t},
links: %{String.t => Link.t | Reference.t} | nil
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/responses.ex b/lib/open_api_spex/responses.ex
index 9100257..ec16ae9 100644
--- a/lib/open_api_spex/responses.ex
+++ b/lib/open_api_spex/responses.ex
@@ -1,7 +1,20 @@
defmodule OpenApiSpex.Responses do
+ @moduledoc """
+ Defines teh `OpenApiSpex.Responses.t` type.
+ """
alias OpenApiSpex.{Response, Reference}
+
+ @typedoc """
+ [Responses Object](https://swagger.io/specification/#responsesObject)
+
+ A container for the expected responses of an operation. The container maps a HTTP response code to the expected response.
+ The documentation is not necessarily expected to cover all possible HTTP response codes because they may not be known in advance.
+ However, documentation is expected to cover a successful operation response and any known errors.
+ The default MAY be used as a default response object for all HTTP codes that are not covered individually by the specification.
+ The Responses Object MUST contain at least one response code, and it SHOULD be the response for a successful operation call.
+ """
@type t :: %{
:default => Response.t | Reference.t,
integer => Response.t | Reference.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/schema.ex b/lib/open_api_spex/schema.ex
index bc83f0e..cd29e13 100644
--- a/lib/open_api_spex/schema.ex
+++ b/lib/open_api_spex/schema.ex
@@ -1,372 +1,379 @@
defmodule OpenApiSpex.Schema do
@moduledoc """
- The Schema Object allows the definition of input and output data types. These types can be objects, but also primitives and arrays.
-
- See https://swagger.io/specification/#schemaObject
-
- ## Example
- alias OpenApiSpex.Schema
-
- %Schema{
- title: "User",
- type: :object,
- properties: %{
- id: %Schema{type: :integer, minimum: 1},
- name: %Schema{type: :string, pattern: "[a-zA-Z][a-zA-Z0-9_]+"},
- email: %Scheam{type: :string, format: :email},
- last_login: %Schema{type: :string, format: :"date-time"}
- },
- required: [:name, :email],
- example: %{
- "name" => "joe",
- "email" => "joe@gmail.com"
- }
- }
+ Defines the `OpenApiSpex.Schema.t` type and operations for casting and validating against a schema.
"""
+
alias OpenApiSpex.{
Schema, Reference, Discriminator, Xml, ExternalDocumentation
}
defstruct [
:title,
:multipleOf,
:maximum,
:exclusiveMaximum,
:minimum,
:exclusiveMinimum,
:maxLength,
:minLength,
:pattern,
:maxItems,
:minItems,
:uniqueItems,
:maxProperties,
:minProperties,
:required,
:enum,
:type,
:allOf,
:oneOf,
:anyOf,
:not,
:items,
:properties,
{:additionalProperties, true},
:description,
:format,
:default,
:nullable,
:discriminator,
:readOnly,
:writeOnly,
:xml,
:externalDocs,
:example,
:deprecated,
:"x-struct"
]
+
+ @typedoc """
+ [Schema Object](https://swagger.io/specification/#schemaObject)
+
+ The Schema Object allows the definition of input and output data types.
+ These types can be objects, but also primitives and arrays.
+ This object is an extended subset of the JSON Schema Specification Wright Draft 00.
+
+ ## Example
+ alias OpenApiSpex.Schema
+
+ %Schema{
+ title: "User",
+ type: :object,
+ properties: %{
+ id: %Schema{type: :integer, minimum: 1},
+ name: %Schema{type: :string, pattern: "[a-zA-Z][a-zA-Z0-9_]+"},
+ email: %Scheam{type: :string, format: :email},
+ last_login: %Schema{type: :string, format: :"date-time"}
+ },
+ required: [:name, :email],
+ example: %{
+ "name" => "joe",
+ "email" => "joe@gmail.com"
+ }
+ }
+ """
@type t :: %__MODULE__{
title: String.t,
multipleOf: number | nil,
maximum: number | nil,
exclusiveMaximum: boolean | nil,
minimum: number | nil,
exclusiveMinimum: boolean | nil,
maxLength: integer | nil,
minLength: integer | nil,
pattern: String.t | Regex.t | nil,
maxItems: integer | nil,
minItems: integer | nil,
uniqueItems: boolean | nil,
maxProperties: integer | nil,
minProperties: integer | nil,
required: [String.t] | nil,
enum: [String.t] | nil,
type: atom,
allOf: [Schema.t | Reference.t] | nil,
oneOf: [Schema.t | Reference.t] | nil,
anyOf: [Schema.t | Reference.t] | nil,
not: Schema.t | Reference.t | nil,
items: Schema.t | Reference.t | nil,
properties: %{atom => Schema.t | Reference.t} | nil,
additionalProperties: boolean | Schema.t | Reference.t | nil,
description: String.t,
format: String.t | nil,
default: any | nil,
nullable: boolean | nil,
discriminator: Discriminator.t | nil,
readOnly: boolean | nil,
writeOnly: boolean | nil,
xml: Xml.t | nil,
externalDocs: ExternalDocumentation.t | nil,
example: any,
deprecated: boolean | nil,
"x-struct": module | nil
}
@doc """
Cast a simple value to the elixir type defined by a schema.
By default, object types are cast to maps, however if the "x-struct" attribute is set in the schema,
the result will be constructed as an instance of the given struct type.
## Examples
iex> OpenApiSpex.Schema.cast(%Schema{type: :integer}, "123", %{})
{:ok, 123}
iex> {:ok, dt = %DateTime{}} = OpenApiSpex.Schema.cast(%Schema{type: :string, format: :"date-time"}, "2018-04-02T13:44:55Z", %{})
...> dt |> DateTime.to_iso8601()
"2018-04-02T13:44:55Z"
"""
@spec cast(Schema.t | Reference.t, any, %{String.t => Schema.t | Reference.t}) :: {:ok, any} | {:error, String.t}
def cast(schema = %Schema{"x-struct": mod}, value, schemas) when not is_nil(mod) do
with {:ok, data} <- cast(%{schema | "x-struct": nil}, value, schemas) do
{:ok, struct(mod, data)}
end
end
def cast(%Schema{type: :boolean}, value, _schemas) when is_boolean(value), do: {:ok, value}
def cast(%Schema{type: :boolean}, value, _schemas) when is_binary(value) do
case value do
"true" -> true
"false" -> false
_ -> {:error, "Invalid boolean: #{inspect(value)}"}
end
end
def cast(%Schema{type: :integer}, value, _schemas) when is_integer(value), do: {:ok, value}
def cast(%Schema{type: :integer}, value, _schemas) when is_binary(value) do
case Integer.parse(value) do
{i, ""} -> {:ok, i}
_ -> {:error, :bad_integer}
end
end
def cast(%Schema{type: :number}, value, _schemas) when is_number(value), do: {:ok, value}
def cast(%Schema{type: :number}, value, _schemas) when is_binary(value) do
case Float.parse(value) do
{x, ""} -> {:ok, x}
_ -> {:error, :bad_float}
end
end
def cast(%Schema{type: :string, format: :"date-time"}, value, _schemas) when is_binary(value) do
case DateTime.from_iso8601(value) do
{:ok, datetime = %DateTime{}, _offset} -> {:ok, datetime}
error = {:error, _reason} -> error
end
end
def cast(%Schema{type: :string, format: :date}, value, _schemas) when is_binary(value) do
case Date.from_iso8601(value) do
{:ok, date = %Date{}} -> {:ok, date}
error = {:error, _reason} -> error
end
end
def cast(%Schema{type: :string}, value, _schemas) when is_binary(value), do: {:ok, value}
def cast(%Schema{type: :array, items: nil}, value, _schemas) when is_list(value), do: {:ok, value}
def cast(%Schema{type: :array}, [], _schemas), do: {:ok, []}
def cast(schema = %Schema{type: :array, items: items_schema}, [x | rest], schemas) do
with {:ok, x_cast} <- cast(items_schema, x, schemas),
{:ok, rest_cast} <- cast(schema, rest, schemas) do
{:ok, [x_cast | rest_cast]}
end
end
def cast(schema = %Schema{type: :object}, value, schemas) when is_map(value) do
with {:ok, props} <- cast_properties(schema, Enum.to_list(value), schemas) do
{:ok, Map.new(props)}
end
end
def cast(ref = %Reference{}, val, schemas), do: cast(Reference.resolve_schema(ref, schemas), val, schemas)
def cast(additionalProperties, val, _schemas) when additionalProperties in [true, false, nil], do: val
@spec cast_properties(Schema.t, list, %{String.t => Schema.t}) :: {:ok, list} | {:error, String.t}
defp cast_properties(%Schema{}, [], _schemas), do: {:ok, []}
defp cast_properties(object_schema = %Schema{}, [{key, value} | rest], schemas) do
{name, schema} = Enum.find(
object_schema.properties,
{key, object_schema.additionalProperties},
fn {name, _schema} -> to_string(name) == to_string(key) end)
with {:ok, new_value} <- cast(schema, value, schemas),
{:ok, cast_tail} <- cast_properties(object_schema, rest, schemas) do
{:ok, [{name, new_value} | cast_tail]}
end
end
@doc """
Validate a value against a Schema.
This expects that the value has already been `cast` to the appropriate data type.
## Examples
iex> OpenApiSpex.Schema.validate(%OpenApiSpex.Schema{type: :integer, minimum: 5}, 3, %{})
{:error, "3 is smaller than minimum 5"}
iex> OpenApiSpex.Schema.validate(%OpenApiSpex.Schema{type: :string, pattern: "(.*)@(.*)"}, "joe@gmail.com", %{})
:ok
iex> OpenApiSpex.Schema.validate(%OpenApiSpex.Schema{type: :string, pattern: "(.*)@(.*)"}, "joegmail.com", %{})
{:error, "Value does not match pattern: (.*)@(.*)"}
"""
@spec validate(Schema.t | Reference.t, any, %{String.t => Schema.t | Reference.t}) :: :ok | {:error, String.t}
def validate(ref = %Reference{}, val, schemas), do: validate(Reference.resolve_schema(ref, schemas), val, schemas)
def validate(schema = %Schema{type: type}, value, _schemas) when type in [:integer, :number] do
with :ok <- validate_multiple(schema, value),
:ok <- validate_maximum(schema, value),
:ok <- validate_minimum(schema, value) do
:ok
end
end
def validate(schema = %Schema{type: :string}, value, _schemas) do
with :ok <- validate_max_length(schema, value),
:ok <- validate_min_length(schema, value),
:ok <- validate_pattern(schema, value) do
:ok
end
end
def validate(%Schema{type: :boolean}, value, _schemas) do
case is_boolean(value) do
true -> :ok
_ -> {:error, "Invalid boolean: #{inspect(value)}"}
end
end
def validate(schema = %Schema{type: :array}, value, schemas) do
with :ok <- validate_max_items(schema, value),
:ok <- validate_min_items(schema, value),
:ok <- validate_unique_items(schema, value),
:ok <- validate_array_items(schema, value, schemas) do
:ok
end
end
def validate(schema = %Schema{type: :object, properties: properties}, value, schemas) do
with :ok <- validate_required_properties(schema, value),
:ok <- validate_max_properties(schema, value),
:ok <- validate_min_properties(schema, value),
:ok <- validate_object_properties(properties, value, schemas) do
:ok
end
end
@spec validate_multiple(Schema.t, number) :: :ok | {:error, String.t}
defp validate_multiple(%{multipleOf: nil}, _), do: :ok
defp validate_multiple(%{multipleOf: n}, value) when (round(value / n) * n == value), do: :ok
defp validate_multiple(%{multipleOf: n}, value), do: {:error, "#{value} is not a multiple of #{n}"}
@spec validate_maximum(Schema.t, number) :: :ok | {:error, String.t}
defp validate_maximum(%{maximum: nil}, _), do: :ok
defp validate_maximum(%{maximum: n, exclusiveMaximum: true}, value) when value < n, do: :ok
defp validate_maximum(%{maximum: n}, value) when value <= n, do: :ok
defp validate_maximum(%{maximum: n}, value), do: {:error, "#{value} is larger than maximum #{n}"}
@spec validate_minimum(Schema.t, number) :: :ok | {:error, String.t}
defp validate_minimum(%{minimum: nil}, _), do: :ok
defp validate_minimum(%{minimum: n, exclusiveMinimum: true}, value) when value > n, do: :ok
defp validate_minimum(%{minimum: n}, value) when value >= n, do: :ok
defp validate_minimum(%{minimum: n}, value), do: {:error, "#{value} is smaller than minimum #{n}"}
@spec validate_max_length(Schema.t, String.t) :: :ok | {:error, String.t}
defp validate_max_length(%{maxLength: nil}, _), do: :ok
defp validate_max_length(%{maxLength: n}, value) do
case String.length(value) <= n do
true -> :ok
_ -> {:error, "String length is larger than maxLength: #{n}"}
end
end
@spec validate_min_length(Schema.t, String.t) :: :ok | {:error, String.t}
defp validate_min_length(%{minLength: nil}, _), do: :ok
defp validate_min_length(%{minLength: n}, value) do
case String.length(value) >= n do
true -> :ok
_ -> {:error, "String length is smaller than minLength: #{n}"}
end
end
@spec validate_pattern(Schema.t, String.t) :: :ok | {:error, String.t}
defp validate_pattern(%{pattern: nil}, _), do: :ok
defp validate_pattern(schema = %{pattern: regex}, val) when is_binary(regex) do
with {:ok, regex} <- Regex.compile(regex) do
validate_pattern(%{schema | pattern: regex}, val)
end
end
defp validate_pattern(%{pattern: regex = %Regex{}}, val) do
case Regex.match?(regex, val) do
true -> :ok
_ -> {:error, "Value does not match pattern: #{regex.source}"}
end
end
@spec validate_max_items(Schema.t, list) :: :ok | {:error, String.t}
defp validate_max_items(%Schema{maxItems: nil}, _), do: :ok
defp validate_max_items(%Schema{maxItems: n}, value) when length(value) <= n, do: :ok
defp validate_max_items(%Schema{maxItems: n}, value) do
{:error, "Array length #{length(value)} is larger than maxItems: #{n}"}
end
@spec validate_min_items(Schema.t, list) :: :ok | {:error, String.t}
defp validate_min_items(%Schema{minItems: nil}, _), do: :ok
defp validate_min_items(%Schema{minItems: n}, value) when length(value) >= n, do: :ok
defp validate_min_items(%Schema{minItems: n}, value) do
{:error, "Array length #{length(value)} is smaller than minItems: #{n}"}
end
@spec validate_unique_items(Schema.t, list) :: :ok | {:error, String.t}
defp validate_unique_items(%Schema{uniqueItems: true}, value) do
unique_size =
value
|> MapSet.new()
|> MapSet.size()
case unique_size == length(value) do
true -> :ok
_ -> {:error, "Array items must be unique"}
end
end
defp validate_unique_items(_, _), do: :ok
@spec validate_array_items(Schema.t, list, %{String.t => Schema.t}) :: :ok | {:error, String.t}
defp validate_array_items(%Schema{type: :array, items: nil}, value, _schemas) when is_list(value), do: :ok
defp validate_array_items(%Schema{type: :array}, [], _schemas), do: :ok
defp validate_array_items(schema = %Schema{type: :array, items: item_schema}, [x | rest], schemas) do
with :ok <- validate(item_schema, x, schemas) do
validate(schema, rest, schemas)
end
end
@spec validate_required_properties(Schema.t, %{}) :: :ok | {:error, String.t}
defp validate_required_properties(%Schema{type: :object, required: nil}, _), do: :ok
defp validate_required_properties(%Schema{type: :object, required: required}, value) do
missing = required -- Map.keys(value)
case missing do
[] -> :ok
_ -> {:error, "Missing required properties: #{inspect(missing)}"}
end
end
@spec validate_max_properties(Schema.t, %{}) :: :ok | {:error, String.t}
defp validate_max_properties(%Schema{type: :object, maxProperties: nil}, _), do: :ok
defp validate_max_properties(%Schema{type: :object, maxProperties: n}, val) when map_size(val) <= n, do: :ok
defp validate_max_properties(%Schema{type: :object, maxProperties: n}, val) do
{:error, "Object property count #{map_size(val)} is greater than maxProperties: #{n}"}
end
@spec validate_min_properties(Schema.t, %{}) :: :ok | {:error, String.t}
defp validate_min_properties(%Schema{type: :object, minProperties: nil}, _), do: :ok
defp validate_min_properties(%Schema{type: :object, minProperties: n}, val) when map_size(val) >= n, do: :ok
defp validate_min_properties(%Schema{type: :object, minProperties: n}, val) do
{:error, "Object property count #{map_size(val)} is less than minProperties: #{n}"}
end
@spec validate_object_properties(Enumerable.t, %{}, %{String.t => Schema.t | Reference.t}) :: :ok | {:error, String.t}
defp validate_object_properties(properties = %{}, value = %{}, schemas = %{}) do
properties
|> Enum.filter(fn {name, _schema} -> Map.has_key?(value, name) end)
|> validate_object_properties(value, schemas)
end
defp validate_object_properties([], _, _), do: :ok
defp validate_object_properties([{name, schema} | rest], value, schemas = %{}) do
case validate(schema, Map.get(value, name), schemas) do
:ok -> validate_object_properties(rest, value, schemas)
error -> error
end
end
end
\ No newline at end of file
diff --git a/lib/open_api_spex/schema_resolver.ex b/lib/open_api_spex/schema_resolver.ex
index 065b814..b0894e1 100644
--- a/lib/open_api_spex/schema_resolver.ex
+++ b/lib/open_api_spex/schema_resolver.ex
@@ -1,158 +1,176 @@
defmodule OpenApiSpex.SchemaResolver do
+ @moduledoc """
+ Internal module used to resolve `OpenApiSpex.Schema` structs from atoms.
+ """
alias OpenApiSpex.{
OpenApi,
Components,
PathItem,
Operation,
Parameter,
Reference,
MediaType,
Schema,
RequestBody,
Response
}
+ @doc """
+ Adds schemas to the api spec from the modules specified in the Operations.
+
+ Eg, if the response schema for an operation is defined with:
+
+ responses: %{
+ 200 => Operation.response("User", "application/json", UserResponse)
+ }
+
+ Then the `UserResponse.schema()` function will be called to load the schema, and
+ a `Reference` to the loaded schema will be used in the operation response.
+
+ See `OpenApiSpex.schema` macro for a convenient syntax for defining schema modules.
+ """
+ @spec resolve_schema_modules(OpenApi.t) :: OpenApi.t
def resolve_schema_modules(spec = %OpenApi{}) do
components = spec.components || %Components{}
schemas = components.schemas || %{}
{paths, schemas} = resolve_schema_modules_from_paths(spec.paths, schemas)
schemas = resolve_schema_modules_from_schemas(schemas)
%{spec | paths: paths, components: %{components| schemas: schemas}}
end
- def resolve_schema_modules_from_paths(paths = %{}, schemas = %{}) do
+ defp resolve_schema_modules_from_paths(paths = %{}, schemas = %{}) do
Enum.reduce(paths, {paths, schemas}, fn {path, path_item}, {paths, schemas} ->
{new_path_item, schemas} = resolve_schema_modules_from_path_item(path_item, schemas)
{Map.put(paths, path, new_path_item), schemas}
end)
end
- def resolve_schema_modules_from_path_item(path = %PathItem{}, schemas) do
+ defp resolve_schema_modules_from_path_item(path = %PathItem{}, schemas) do
path
|> Map.from_struct()
|> Enum.filter(fn {_k, v} -> match?(%Operation{}, v) end)
|> Enum.reduce({path, schemas}, fn {k, operation}, {path, schemas} ->
{new_operation, schemas} = resolve_schema_modules_from_operation(operation, schemas)
{Map.put(path, k, new_operation), schemas}
end)
end
- def resolve_schema_modules_from_operation(operation = %Operation{}, schemas) do
+ defp resolve_schema_modules_from_operation(operation = %Operation{}, schemas) do
{parameters, schemas} = resolve_schema_modules_from_parameters(operation.parameters, schemas)
{request_body, schemas} = resolve_schema_modules_from_request_body(operation.requestBody, schemas)
{responses, schemas} = resolve_schema_modules_from_responses(operation.responses, schemas)
new_operation = %{operation | parameters: parameters, requestBody: request_body, responses: responses}
{new_operation, schemas}
end
- def resolve_schema_modules_from_parameters(nil, schemas), do: {nil, schemas}
- def resolve_schema_modules_from_parameters(parameters, schemas) do
+ defp resolve_schema_modules_from_parameters(nil, schemas), do: {nil, schemas}
+ defp resolve_schema_modules_from_parameters(parameters, schemas) do
{parameters, schemas} =
Enum.reduce(parameters, {[], schemas}, fn parameter, {parameters, schemas} ->
{new_parameter, schemas} = resolve_schema_modules_from_parameter(parameter, schemas)
{[new_parameter | parameters], schemas}
end)
{Enum.reverse(parameters), schemas}
end
- def resolve_schema_modules_from_parameter(parameter = %Parameter{schema: schema, content: nil}, schemas) when is_atom(schema) do
+ defp resolve_schema_modules_from_parameter(parameter = %Parameter{schema: schema, content: nil}, schemas) when is_atom(schema) do
{ref, new_schemas} = resolve_schema_modules_from_schema(schema, schemas)
new_parameter = %{parameter | schema: ref}
{new_parameter, new_schemas}
end
- def resolve_schema_modules_from_parameter(parameter = %Parameter{schema: nil, content: content = %{}}, schemas) do
+ defp resolve_schema_modules_from_parameter(parameter = %Parameter{schema: nil, content: content = %{}}, schemas) do
{new_content, schemas} = resolve_schema_modules_from_content(content, schemas)
{%{parameter | content: new_content}, schemas}
end
- def resolve_schema_modules_from_parameter(parameter = %Parameter{}, schemas) do
+ defp resolve_schema_modules_from_parameter(parameter = %Parameter{}, schemas) do
{parameter, schemas}
end
- def resolve_schema_modules_from_content(nil, schemas), do: {nil, schemas}
- def resolve_schema_modules_from_content(content, schemas) do
+ defp resolve_schema_modules_from_content(nil, schemas), do: {nil, schemas}
+ defp resolve_schema_modules_from_content(content, schemas) do
Enum.reduce(content, {content, schemas}, fn {mime, media}, {content, schemas} ->
{new_media, schemas} = resolve_schema_modules_from_media_type(media, schemas)
{Map.put(content, mime, new_media), schemas}
end)
end
- def resolve_schema_modules_from_media_type(media = %MediaType{schema: schema}, schemas) when is_atom(schema) do
+ defp resolve_schema_modules_from_media_type(media = %MediaType{schema: schema}, schemas) when is_atom(schema) do
{ref, new_schemas} = resolve_schema_modules_from_schema(schema, schemas)
new_media = %{media | schema: ref}
{new_media, new_schemas}
end
- def resolve_schema_modules_from_media_type(media = %MediaType{}, schemas) do
+ defp resolve_schema_modules_from_media_type(media = %MediaType{}, schemas) do
{media, schemas}
end
- def resolve_schema_modules_from_request_body(nil, schemas), do: {nil, schemas}
- def resolve_schema_modules_from_request_body(request_body = %RequestBody{}, schemas) do
+ defp resolve_schema_modules_from_request_body(nil, schemas), do: {nil, schemas}
+ defp resolve_schema_modules_from_request_body(request_body = %RequestBody{}, schemas) do
{content, schemas} = resolve_schema_modules_from_content(request_body.content, schemas)
new_request_body = %{request_body | content: content}
{new_request_body, schemas}
end
- def resolve_schema_modules_from_responses(responses = %{}, schemas = %{}) do
+ defp resolve_schema_modules_from_responses(responses = %{}, schemas = %{}) do
Enum.reduce(responses, {responses, schemas}, fn {status, response}, {responses, schemas} ->
{new_response, schemas} = resolve_schema_modules_from_response(response, schemas)
{Map.put(responses, status, new_response), schemas}
end)
end
- def resolve_schema_modules_from_response(response = %Response{}, schemas = %{}) do
+ defp resolve_schema_modules_from_response(response = %Response{}, schemas = %{}) do
{content, schemas} = resolve_schema_modules_from_content(response.content, schemas)
new_response = %{response | content: content}
{new_response, schemas}
end
- def resolve_schema_modules_from_schemas(schemas = %{}) do
+ defp resolve_schema_modules_from_schemas(schemas = %{}) do
Enum.reduce(schemas, schemas, fn {name, schema}, schemas ->
{schema, schemas} = resolve_schema_modules_from_schema(schema, schemas)
Map.put(schemas, name, schema)
end)
end
- def resolve_schema_modules_from_schema(false, schemas), do: {false, schemas}
- def resolve_schema_modules_from_schema(true, schemas), do: {true, schemas}
- def resolve_schema_modules_from_schema(nil, schemas), do: {nil, schemas}
- def resolve_schema_modules_from_schema(schema, schemas) when is_atom(schema) do
+ defp resolve_schema_modules_from_schema(false, schemas), do: {false, schemas}
+ defp resolve_schema_modules_from_schema(true, schemas), do: {true, schemas}
+ defp resolve_schema_modules_from_schema(nil, schemas), do: {nil, schemas}
+ defp resolve_schema_modules_from_schema(schema, schemas) when is_atom(schema) do
title = schema.schema().title
new_schemas =
if Map.has_key?(schemas, title) do
schemas
else
{new_schema, schemas} = resolve_schema_modules_from_schema(schema.schema(), schemas)
Map.put(schemas, title, new_schema)
end
{%Reference{"$ref": "#/components/schemas/#{title}"}, new_schemas}
end
- def resolve_schema_modules_from_schema(schema = %Schema{}, schemas) do
+ defp resolve_schema_modules_from_schema(schema = %Schema{}, schemas) do
{all_of, schemas} = resolve_schema_modules_from_schema(schema.allOf, schemas)
{one_of, schemas} = resolve_schema_modules_from_schema(schema.oneOf, schemas)
{any_of, schemas} = resolve_schema_modules_from_schema(schema.anyOf, schemas)
{not_schema, schemas} = resolve_schema_modules_from_schema(schema.not, schemas)
{items, schemas} = resolve_schema_modules_from_schema(schema.items, schemas)
{additional, schemas} = resolve_schema_modules_from_schema(schema.additionalProperties, schemas)
{properties, schemas} = resolve_schema_modules_from_schema_properties(schema.properties, schemas)
schema =
%{schema |
allOf: all_of,
oneOf: one_of,
anyOf: any_of,
not: not_schema,
items: items,
additionalProperties: additional,
properties: properties
}
{schema, schemas}
end
- def resolve_schema_modules_from_schema(ref = %Reference{}, schemas), do: {ref, schemas}
+ defp resolve_schema_modules_from_schema(ref = %Reference{}, schemas), do: {ref, schemas}
- def resolve_schema_modules_from_schema_properties(nil, schemas), do: {nil, schemas}
- def resolve_schema_modules_from_schema_properties(properties, schemas) do
+ defp resolve_schema_modules_from_schema_properties(nil, schemas), do: {nil, schemas}
+ defp resolve_schema_modules_from_schema_properties(properties, schemas) do
Enum.reduce(properties, {properties, schemas}, fn {name, property}, {properties, schemas} ->
{new_property, schemas} = resolve_schema_modules_from_schema(property, schemas)
{Map.put(properties, name, new_property), schemas}
end)
end
end
\ No newline at end of file
diff --git a/lib/open_api_spex/security_requirement.ex b/lib/open_api_spex/security_requirement.ex
index efabc17..93ec776 100644
--- a/lib/open_api_spex/security_requirement.ex
+++ b/lib/open_api_spex/security_requirement.ex
@@ -1,3 +1,17 @@
defmodule OpenApiSpex.SecurityRequirement do
+ @moduledoc """
+ Defines the `OpenApiSpex.SecurityRequirement.t` type.
+ """
+
+ @typedoc """
+ [Security Requirement Object](https://swagger.io/specification/#securityRequirementObject)
+
+ Lists the required security schemes to execute this operation.
+ The name used for each property MUST correspond to a security scheme declared in the Security Schemes under the Components Object.
+ Security Requirement Objects that contain multiple schemes require that all schemes MUST be satisfied for a request to be authorized.
+ This enables support for scenarios where multiple query parameters or HTTP headers are required to convey security information.
+ When a list of Security Requirement Objects is defined on the Open API object or Operation Object,
+ only one of Security Requirement Objects in the list needs to be satisfied to authorize the request.
+ """
@type t :: %{String.t => [String.t]}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/security_scheme.ex b/lib/open_api_spex/security_scheme.ex
index ab74baf..a2f767a 100644
--- a/lib/open_api_spex/security_scheme.ex
+++ b/lib/open_api_spex/security_scheme.ex
@@ -1,23 +1,34 @@
defmodule OpenApiSpex.SecurityScheme do
+ @moduledoc """
+ Defines the `OpenApiSpex.SecurityScheme.t` type.
+ """
alias OpenApiSpex.OAuthFlows
defstruct [
:type,
:description,
:name,
:in,
:scheme,
:bearerFormat,
:flows,
:openIdConnectUrl
]
+
+ @typedoc """
+ [Security Scheme Object](https://swagger.io/specification/#securitySchemeObject)
+
+ Defines a security scheme that can be used by the operations.
+ Supported schemes are HTTP authentication, an API key (either as a header or as a query parameter),
+ OAuth2's common flows (implicit, password, application and access code) as defined in RFC6749, and OpenID Connect Discovery.
+ """
@type t :: %__MODULE__{
type: String.t,
description: String.t,
name: String.t,
in: String.t,
scheme: String.t,
bearerFormat: String.t,
flows: OAuthFlows.t,
openIdConnectUrl: String.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/server.ex b/lib/open_api_spex/server.ex
index 3a6358d..05409fe 100644
--- a/lib/open_api_spex/server.ex
+++ b/lib/open_api_spex/server.ex
@@ -1,29 +1,38 @@
defmodule OpenApiSpex.Server do
+ @moduledoc """
+ Defines the `OpenApiSpex.Server.t` type.
+ """
alias OpenApiSpex.{Server, ServerVariable}
defstruct [
:url,
:description,
variables: %{}
]
+
+ @typedoc """
+ [Server Object](https://swagger.io/specification/#serverObject)
+
+ An object representing a Server.
+ """
@type t :: %Server{
url: String.t,
description: String.t | nil,
variables: %{String.t => ServerVariable.t}
}
@doc """
Builds a Server from a phoenix Endpoint module
"""
@spec from_endpoint(module, [otp_app: atom]) :: t
def from_endpoint(endpoint, opts) do
app = opts[:otp_app]
url_config = Application.get_env(app, endpoint, []) |> Keyword.get(:url, [])
scheme = Keyword.get(url_config, :scheme, "http")
host = Keyword.get(url_config, :host, "localhost")
port = Keyword.get(url_config, :port, "80")
path = Keyword.get(url_config, :path, "/")
%Server{
url: "#{scheme}://#{host}:#{port}#{path}"
}
end
end
\ No newline at end of file
diff --git a/lib/open_api_spex/server_variable.ex b/lib/open_api_spex/server_variable.ex
new file mode 100644
index 0000000..521fb3f
--- /dev/null
+++ b/lib/open_api_spex/server_variable.ex
@@ -0,0 +1,21 @@
+defmodule OpenApiSpex.ServerVariable do
+ @moduledoc """
+ Defines the `OpenApiSpex.ServerVariable.t` type.
+ """
+ defstruct [
+ :enum,
+ :default,
+ :description
+ ]
+
+ @typedoc """
+ [Server Variable Object](https://swagger.io/specification/#serverVariableObject)
+
+ An object representing a Server Variable for server URL template substitution.
+ """
+ @type t :: %__MODULE__{
+ enum: [String.t],
+ default: String.t,
+ description: String.t
+ }
+end
\ No newline at end of file
diff --git a/lib/open_api_spex/servier_variable.ex b/lib/open_api_spex/servier_variable.ex
deleted file mode 100644
index 4924287..0000000
--- a/lib/open_api_spex/servier_variable.ex
+++ /dev/null
@@ -1,12 +0,0 @@
-defmodule OpenApiSpex.ServerVariable do
- defstruct [
- :enum,
- :default,
- :description
- ]
- @type t :: %__MODULE__{
- enum: [String.t],
- default: String.t,
- description: String.t
- }
-end
\ No newline at end of file
diff --git a/lib/open_api_spex/tag.ex b/lib/open_api_spex/tag.ex
index ca75c0e..07a299f 100644
--- a/lib/open_api_spex/tag.ex
+++ b/lib/open_api_spex/tag.ex
@@ -1,13 +1,24 @@
defmodule OpenApiSpex.Tag do
+ @moduledoc """
+ Defines the `OpenApiSpex.Tag.t` type.
+ """
+
alias OpenApiSpex.ExternalDocumentation
defstruct [
:name,
:description,
:externalDocs
]
+
+ @typedoc """
+ [Tag Object](https://swagger.io/specification/#tagObject)
+
+ Adds metadata to a single tag that is used by the Operation Object.
+ It is not mandatory to have a Tag Object per tag defined in the Operation Object instances.
+ """
@type t :: %__MODULE__{
name: String.t,
description: String.t,
externalDocs: ExternalDocumentation.t
}
end
\ No newline at end of file
diff --git a/lib/open_api_spex/xml.ex b/lib/open_api_spex/xml.ex
index 1980898..525dc35 100644
--- a/lib/open_api_spex/xml.ex
+++ b/lib/open_api_spex/xml.ex
@@ -1,16 +1,27 @@
defmodule OpenApiSpex.Xml do
+ @moduledoc """
+ Defines the `OpenApiSpex.Xml.t` type.
+ """
defstruct [
:name,
:namespace,
:prefix,
:attribute,
:wrapped
]
+
+ @typedoc """
+ [XML Object](https://swagger.io/specification/#xmlObject)
+
+ A metadata object that allows for more fine-tuned XML model definitions.
+ When using arrays, XML element names are not inferred (for singular/plural forms)
+ and the name property SHOULD be used to add that information. See examples for expected behavior.
+ """
@type t :: %__MODULE__{
name: String.t,
namespace: String.t,
prefix: String.t,
attribute: boolean,
wrapped: boolean
}
end
\ No newline at end of file

File Metadata

Mime Type
text/x-diff
Expires
Sat, Nov 23, 1:04 PM (23 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
38983
Default Alt Text
(69 KB)

Event Timeline