Page MenuHomePhorge

No OneTemporary

Size
21 KB
Referenced Files
None
Subscribers
None
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

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)

Event Timeline