Page MenuHomePhorge

No OneTemporary

Size
10 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/open_api_spex.ex b/lib/open_api_spex.ex
index bbdf5b6..1f24393 100644
--- a/lib/open_api_spex.ex
+++ b/lib/open_api_spex.ex
@@ -1,250 +1,251 @@
defmodule OpenApiSpex do
@moduledoc """
Provides the entry-points for defining schemas, validating and casting.
"""
alias OpenApiSpex.{
Components,
OpenApi,
Operation,
Operation2,
Reference,
Schema,
SchemaException,
SchemaResolver,
SchemaConsistency
}
alias OpenApiSpex.Cast.Error
@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
SchemaResolver.resolve_schema_modules(spec)
end
+ @doc """
+ Cast and validate a value against a given Schema.
+ """
+ def cast_value(value, schema = %schema_mod{}) when schema_mod in [Schema, Reference] do
+ OpenApiSpex.Cast.cast(schema, value)
+ end
+
+ @doc """
+ Cast and validate a value against a given Schema belonging to a given OpenApi spec.
+ """
+ def cast_value(value, schema = %schema_mod{}, spec = %OpenApi{})
+ when schema_mod in [Schema, Reference] do
+ OpenApiSpex.Cast.cast(schema, value, spec.components.schemas)
+ end
+
def cast_and_validate(
spec = %OpenApi{},
operation = %Operation{},
conn = %Plug.Conn{},
content_type \\ nil
) do
Operation2.cast(operation, conn, content_type, spec.components)
end
@doc """
Cast params to conform to a `OpenApiSpex.Schema`.
See `OpenApiSpex.Schema.cast/3` for additional examples and details.
"""
@spec cast(OpenApi.t(), Schema.t() | Reference.t(), any) :: {:ok, any} | {:error, String.t()}
+ @deprecated "Use OpenApiSpecs.Cast.cast/3 or cast/2 instead"
def cast(spec = %OpenApi{}, schema = %Schema{}, params) do
- IO.warn("#{__MODULE__}.cast/3 is deprecated. Please use OpenApiSpecs.Cast.cast/3 instead.")
Schema.cast(schema, params, spec.components.schemas)
end
def cast(spec = %OpenApi{}, schema = %Reference{}, params) do
- IO.warn("#{__MODULE__}.cast/3 is deprecated. Please use OpenApiSpecs.Cast.cast/3 instead.")
Schema.cast(schema, params, spec.components.schemas)
end
@doc """
Cast all params in `Plug.Conn` to conform to the schemas for `OpenApiSpex.Operation`.
Returns `{:ok, Plug.Conn.t}` with `params` and `body_params` fields updated if successful,
or `{:error, reason}` if casting fails.
`content_type` may optionally be supplied to select the `requestBody` schema.
"""
@spec cast(OpenApi.t(), Operation.t(), Plug.Conn.t(), content_type | nil) ::
{:ok, Plug.Conn.t()} | {:error, String.t()}
when content_type: String.t()
+ @deprecated "Use OpenApiSpecs.Cast.cast_and_validate/3 instead"
def cast(spec = %OpenApi{}, operation = %Operation{}, conn = %Plug.Conn{}, content_type \\ nil) do
- IO.warn(
- "#{__MODULE__}.cast/4 is deprecated. Please use OpenApiSpecs.Operation2.cast/4 instead."
- )
-
Operation.cast(operation, conn, content_type, spec.components.schemas)
end
@doc """
Validate params against `OpenApiSpex.Schema`.
See `OpenApiSpex.Schema.validate/3` for examples of error messages.
"""
@spec validate(OpenApi.t(), Schema.t() | Reference.t(), any) :: :ok | {:error, String.t()}
+ @deprecated "Use OpenApiSpecs.Cast.cast_value/3 or cast_value/2 instead"
def validate(spec = %OpenApi{}, schema = %Schema{}, params) do
- IO.warn(
- "#{__MODULE__}.validate/3 is deprecated. Please use OpenApiSpecs.Cast.cast/3 instead."
- )
-
Schema.validate(schema, params, spec.components.schemas)
end
def validate(spec = %OpenApi{}, schema = %Reference{}, params) do
- IO.warn(
- "#{__MODULE__}.validate/3 is deprecated. Please use OpenApiSpecs.Cast.cast/3 instead."
- )
-
Schema.validate(schema, params, spec.components.schemas)
end
@doc """
Validate all params in `Plug.Conn` against `OpenApiSpex.Operation` `parameter` and `requestBody` schemas.
`content_type` may be optionally supplied to select the `requestBody` schema.
"""
@spec validate(OpenApi.t(), Operation.t(), Plug.Conn.t(), content_type | nil) ::
:ok | {:error, String.t()}
when content_type: String.t()
+ @deprecated "Use OpenApiSpex.cast_and_validate/4 instead"
def validate(
spec = %OpenApi{},
operation = %Operation{},
conn = %Plug.Conn{},
content_type \\ nil
) do
- IO.warn(
- "#{__MODULE__}.validate/4 is deprecated. Please use OpenApiSpex.Operation2.cast/4 instead."
- )
-
Operation.validate(operation, conn, content_type, spec.components.schemas)
end
def path_to_string(%Error{} = error) do
Error.path_to_string(error)
end
@doc """
Declares a struct based `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 `Jason.Encoder` and/or `Poison.Encoder` for the struct
See `OpenApiSpex.Schema` for additional examples and details.
## 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
@compile {:report_warnings, false}
@behaviour OpenApiSpex.Schema
@schema struct(
OpenApiSpex.Schema,
Map.put(Map.delete(unquote(body), :__struct__), :"x-struct", __MODULE__)
)
def schema, do: @schema
@derive Enum.filter([Poison.Encoder, Jason.Encoder], &Code.ensure_loaded?/1)
defstruct Schema.properties(@schema)
@type t :: %__MODULE__{}
Map.from_struct(@schema) |> OpenApiSpex.validate_compiled_schema()
# Throwing warnings to prevent runtime bugs (like in issue #144)
@schema
|> SchemaConsistency.warnings()
|> Enum.each(&IO.warn("Inconsistent schema: #{&1}", Macro.Env.stacktrace(__ENV__)))
end
end
@doc """
Validate the compiled schema's properties to ensure the schema is not improperly
defined. Only errors which would cause a given schema to _always_ fail should be
raised here.
"""
def validate_compiled_schema(schema) do
Enum.each(schema, fn prop_and_val ->
:ok = validate_compiled_schema(prop_and_val, schema)
end)
end
def validate_compiled_schema({_, %Schema{} = schema}, _parent) do
validate_compiled_schema(schema)
end
@doc """
Used for validating the schema at compile time, otherwise we're forced
to raise errors for improperly defined schemas at runtime.
"""
def validate_compiled_schema({:discriminator, %{propertyName: property, mapping: _}}, %{
anyOf: schemas
})
when is_list(schemas) do
Enum.each(schemas, fn schema ->
case schema do
%Schema{title: title} when is_binary(title) -> :ok
_ -> error!(:discriminator_schema_missing_title, schema, property_name: property)
end
end)
end
def validate_compiled_schema({:discriminator, %{propertyName: _, mapping: _}}, schema) do
case {schema.anyOf, schema.allOf, schema.oneOf} do
{nil, nil, nil} ->
error!(:discriminator_missing_composite_key, schema)
_ ->
:ok
end
end
def validate_compiled_schema({_property, _value}, _schema), do: :ok
@doc """
Raises compile time errors for improperly defined schemas.
"""
@spec error!(atom(), Schema.t(), keyword()) :: no_return()
@spec error!(atom(), Schema.t()) :: no_return()
def error!(error, schema, details \\ []) do
raise SchemaException, %{error: error, schema: schema, details: details}
end
@doc """
Resolve a schema or reference to a schema.
"""
@spec resolve_schema(Schema.t() | Reference.t(), Components.schemas_map()) :: Schema.t()
def resolve_schema(%Schema{} = schema, _), do: schema
def resolve_schema(%Reference{} = ref, schemas), do: Reference.resolve_schema(ref, schemas)
end
diff --git a/lib/open_api_spex/test/assertions.ex b/lib/open_api_spex/test/assertions.ex
index 8df78f4..ede6534 100644
--- a/lib/open_api_spex/test/assertions.ex
+++ b/lib/open_api_spex/test/assertions.ex
@@ -1,45 +1,42 @@
defmodule OpenApiSpex.Test.Assertions do
@moduledoc """
Defines helpers for testing API responses and examples against API spec schemas.
"""
alias OpenApiSpex.OpenApi
import ExUnit.Assertions
@dialyzer {:no_match, assert_schema: 3}
@doc """
Asserts that `value` conforms to the schema with title `schema_title` in `api_spec`.
"""
@spec assert_schema(map, String.t(), OpenApi.t()) :: map | no_return
+ @deprecated "use Elixir.OpenApiSpex.Test.Assertions2 instead"
def assert_schema(value = %{}, schema_title, api_spec = %OpenApi{}) do
- IO.warn(
- "Elixir.OpenApiSpex.Test.Assertions is deprecated. Please use Elixir.OpenApiSpex.Test.Assertions2 instead."
- )
-
schemas = api_spec.components.schemas
schema = schemas[schema_title]
if !schema do
flunk("Schema: #{schema_title} not found in #{inspect(Map.keys(schemas))}")
end
data =
case OpenApiSpex.cast(api_spec, schema, value) do
{:ok, data} ->
data
{:error, reason} ->
flunk("Value does not conform to schema #{schema_title}: #{reason}\n#{inspect(value)}")
end
case OpenApiSpex.validate(api_spec, schema, data) do
:ok ->
:ok
{:error, reason} ->
flunk("Value does not conform to schema #{schema_title}: #{reason}\n#{inspect(value)}")
end
data
end
end

File Metadata

Mime Type
text/x-diff
Expires
Wed, Nov 27, 7:52 PM (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40730
Default Alt Text
(10 KB)

Event Timeline