Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F112453
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/open_api_spex.ex b/lib/open_api_spex.ex
index 387c12c..5544eda 100644
--- a/lib/open_api_spex.ex
+++ b/lib/open_api_spex.ex
@@ -1,86 +1,36 @@
defmodule OpenApiSpex do
- alias OpenApiSpex.{OpenApi, Operation, Parameter, RequestBody, Schema, SchemaResolver}
+ alias OpenApiSpex.{OpenApi, Operation, Parameter, Reference, RequestBody, Schema, SchemaResolver}
@moduledoc """
"""
def resolve_schema_modules(spec = %OpenApi{}) do
SchemaResolver.resolve_schema_modules(spec)
end
- def cast_parameters(spec = %OpenApi{}, operation = %Operation{}, params = %{}, content_type \\ nil) do
- schemas = spec.components.schemas
-
- parameters_result =
- operation.parameters
- |> Stream.filter(fn parameter -> Map.has_key?(params, Atom.to_string(parameter.name)) end)
- |> Stream.map(fn parameter -> %{name: parameter.name, schema: Parameter.schema(parameter)} end)
- |> Stream.map(fn %{schema: schema, name: name} -> {name, Schema.cast(schema, params[name], schemas)} end)
- |> Enum.reduce({:ok, %{}}, fn
- {name, {:ok, val}}, {:ok, acc} -> {:ok, Map.put(acc, name, val)}
- _, {:error, reason} -> {:error, reason}
- {_name, {:error, reason}}, _ -> {:error, reason}
- end)
-
- body_result =
- case operation.requestBody do
- nil -> {:ok, %{}}
- %RequestBody{content: content} ->
- schema = content[content_type].schema
- Schema.cast(schema, params, spec.components.schemas)
- end
-
- with {:ok, cast_params} <- parameters_result,
- {:ok, cast_body} <- body_result do
- params = Map.merge(cast_params, cast_body)
- {:ok, params}
- end
+ @doc """
+ Cast params to conform to a Schema or Operation spec.
+ """
+ def cast(spec = %OpenApi{}, schema = %Schema{}, params) do
+ Schema.cast(schema, params, spec.compnents.schemas)
end
-
- def validate_parameters(spec = %OpenApi{}, operation = %Operation{}, params = %{}, content_type \\ nil) do
- schemas = spec.components.schemas
-
- with :ok <- validate_required_parameters(operation.parameters, params),
- {:ok, remaining} <- validate_parameter_schemas(operation.parameters, params, schemas),
- :ok <- validate_body_schema(operation.requestBody, remaining, content_type, schemas) do
- :ok
- end
+ def cast(spec = %OpenApi{}, schema = %Reference{}, params) do
+ Schema.cast(schema, params, spec.compnents.schemas)
end
-
- def 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
+ def cast(spec = %OpenApi{}, operation = %Operation{}, params, content_type \\ nil) do
+ Operation.cast(operation, params, content_type, spec.components.schemas)
end
- def validate_parameter_schemas(parameter_list, params, schemas) do
- errors =
- parameter_list
- |> Stream.filter(fn parameter -> Map.has_key?(params, parameter.name) end)
- |> Stream.map(fn parameter -> Parameter.schema(parameter) end)
- |> Stream.map(fn schema -> Schema.validate(schema, params, schemas) end)
- |> Enum.filter(fn result -> result != :ok end)
- case errors do
- [] -> {:ok, Map.drop(params, Enum.map(parameter_list, fn p -> p.name end)) }
- _ -> {:error, "Parameter validation errors: #{inspect(errors)}"}
- 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_body_schema(nil, _, _, _), do: :ok
- def validate_body_schema(%RequestBody{required: false}, params, _content_type, _schemas) when map_size(params) == 0 do
- :ok
+ def validate(spec = %OpenApi{}, schema = %Reference{}, params) do
+ Schema.validate(schema, params, spec.components.schemas)
end
- def validate_body_schema(%RequestBody{content: content}, params, content_type, schemas) do
- content
- |> Map.get(content_type)
- |> Map.get(:schema)
- |> Schema.validate(params, schemas)
+ def validate(spec = %OpenApi{}, operation = %Operation{}, params = %{}, content_type \\ nil) do
+ Operation.validate(operation, params, content_type, spec.components.schemas)
end
end
diff --git a/lib/open_api_spex/operation.ex b/lib/open_api_spex/operation.ex
index b81bed6..c204785 100644
--- a/lib/open_api_spex/operation.ex
+++ b/lib/open_api_spex/operation.ex
@@ -1,111 +1,184 @@
defmodule OpenApiSpex.Operation do
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
]
@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, atom, atom, String.t, keyword) :: RequestBody.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
+ 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
+ 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
+ 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
+ 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
+ :ok
+ end
+ def 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/path_item.ex b/lib/open_api_spex/path_item.ex
index 84a6b04..4dc3a84 100644
--- a/lib/open_api_spex/path_item.ex
+++ b/lib/open_api_spex/path_item.ex
@@ -1,57 +1,55 @@
defmodule OpenApiSpex.PathItem do
alias OpenApiSpex.{Operation, Server, Parameter, PathItem, Reference}
defstruct [
:"$ref",
:summary,
:description,
:get,
:put,
:post,
:delete,
:options,
:head,
:patch,
:trace,
:servers,
:parameters
]
@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]
}
@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
- Enum.reduce(routes, %PathItem{}, fn route, path_item ->
- Map.put(path_item, route.verb, Operation.from_route(route))
- end)
+ 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/plug/cast.ex b/lib/open_api_spex/plug/cast.ex
index 7bb6b63..5502fde 100644
--- a/lib/open_api_spex/plug/cast.ex
+++ b/lib/open_api_spex/plug/cast.ex
@@ -1,24 +1,24 @@
defmodule OpenApiSpex.Plug.Cast do
alias Plug.Conn
def init(opts), do: opts
def call(conn = %{private: %{open_api_spex: private_data}}, operation_id: operation_id) do
spec = private_data.spec
operation = private_data.operation_lookup[operation_id]
content_type = Conn.get_req_header(conn, "content-type") |> Enum.at(0)
private_data = Map.put(private_data, :operation_id, operation_id)
conn = Conn.put_private(conn, :open_api_spex, private_data)
- case OpenApiSpex.cast_parameters(spec, operation, conn.params, content_type) do
+ case OpenApiSpex.cast(spec, operation, conn.params, content_type) do
{:ok, params} -> %{conn | params: params}
{:error, reason} ->
conn
|> Plug.Conn.send_resp(422, "#{reason}")
|> Plug.Conn.halt()
end
end
def call(conn = %{private: %{phoenix_controller: controller, phoenix_action: action}}, _opts) do
call(conn, operation_id: controller.open_api_operation(action).operationId)
end
end
\ No newline at end of file
diff --git a/lib/open_api_spex/plug/validate.ex b/lib/open_api_spex/plug/validate.ex
index a6ee703..40e6888 100644
--- a/lib/open_api_spex/plug/validate.ex
+++ b/lib/open_api_spex/plug/validate.ex
@@ -1,21 +1,21 @@
defmodule OpenApiSpex.Plug.Validate do
alias Plug.Conn
def init(opts), do: opts
def call(conn, _opts) do
spec = conn.private.open_api_spex.spec
operation_id = conn.private.open_api_spex.operation_id
operation_lookup = conn.private.open_api_spex.operation_lookup
operation = operation_lookup[operation_id]
content_type = Conn.get_req_header(conn, "content-type") |> Enum.at(0)
- with :ok <- OpenApiSpex.validate_parameters(spec, operation, conn.params, content_type) do
+ with :ok <- OpenApiSpex.validate(spec, operation, conn.params, content_type) do
conn
else
{:error, reason} ->
conn
|> Conn.send_resp(422, "#{reason}")
|> Conn.halt()
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 c007d74..065b814 100644
--- a/lib/open_api_spex/schema_resolver.ex
+++ b/lib/open_api_spex/schema_resolver.ex
@@ -1,159 +1,158 @@
defmodule OpenApiSpex.SchemaResolver do
alias OpenApiSpex.{
OpenApi,
Components,
PathItem,
Operation,
Parameter,
Reference,
MediaType,
Schema,
RequestBody,
Response
}
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
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
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
{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
{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
{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
{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
{parameter, schemas}
end
def resolve_schema_modules_from_content(nil, schemas), do: {nil, schemas}
def 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
{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
{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
{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
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
{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
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
title = schema.schema().title
- new_schemas = cond do
- Map.has_key?(schemas, title) ->
+ new_schemas =
+ if Map.has_key?(schemas, title) do
schemas
-
- true ->
+ else
{new_schema, schemas} = resolve_schema_modules_from_schema(schema.schema(), schemas)
Map.put(schemas, title, new_schema)
- end
+ end
{%Reference{"$ref": "#/components/schemas/#{title}"}, new_schemas}
end
def 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}
def resolve_schema_modules_from_schema_properties(nil, schemas), do: {nil, schemas}
def 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
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Nov 23, 2:35 PM (1 d, 1 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39004
Default Alt Text
(21 KB)
Attached To
Mode
R22 open_api_spex
Attached
Detach File
Event Timeline
Log In to Comment