diff --git a/lib/open_api_spex/cast/error.ex b/lib/open_api_spex/cast/error.ex index 4182d994..49cefec0 100644 --- a/lib/open_api_spex/cast/error.ex +++ b/lib/open_api_spex/cast/error.ex @@ -29,6 +29,7 @@ defmodule OpenApiSpex.Cast.Error do @type one_of_error :: {:one_of, [String.t()]} @type unexpected_field_error :: {:unexpected_field, String.t() | atom()} @type unique_items_error :: {:unique_items} + @type custom_error :: {:custom, String.t()} @type reason :: :all_of @@ -56,6 +57,7 @@ defmodule OpenApiSpex.Cast.Error do | :one_of | :unexpected_field | :unique_items + | :custom @type args :: all_of_error() @@ -84,6 +86,7 @@ defmodule OpenApiSpex.Cast.Error do | one_of_error() | unexpected_field_error() | unique_items_error() + | custom_error() @type t :: %__MODULE__{ reason: reason(), @@ -244,6 +247,11 @@ defmodule OpenApiSpex.Cast.Error do |> add_context_fields(ctx) end + def new(ctx, {:custom, message}) do + %__MODULE__{reason: :custom, meta: %{message: message}} + |> add_context_fields(ctx) + end + @spec message(t()) :: String.t() def message(%{reason: :invalid_schema_type, type: type}) do @@ -365,6 +373,10 @@ defmodule OpenApiSpex.Cast.Error do "Object property count #{meta.property_count} is less than minProperties: #{meta.min_properties}" end + def message(%{reason: :custom, meta: meta}) do + meta.message + end + def message_with_path(error) do prepend_path(error, message(error)) end diff --git a/test/cast_test.exs b/test/cast_test.exs index 1cb2baf5..09de70df 100644 --- a/test/cast_test.exs +++ b/test/cast_test.exs @@ -222,6 +222,34 @@ defmodule OpenApiSpec.CastTest do assert Error.message_with_path(error) == "#/age: Invalid strict_integer. Got: string" end + test "cast custom error with custom validator" do + defmodule EvenInt do + require OpenApiSpex + + alias OpenApiSpex.Cast + + OpenApiSpex.schema(%{ + description: "An even integer", + type: :integer, + "x-validate": __MODULE__ + }) + + def cast(context = %Cast{value: value}) when is_integer(value) and rem(value, 2) == 0, + do: Cast.ok(context) + + def cast(context), do: Cast.error(context, {:custom, "Must be an even integer"}) + end + + schema = %Schema{type: :object, properties: %{even_number: EvenInt.schema()}} + + assert {:error, errors} = cast(value: %{"even_number" => 1}, schema: schema) + assert [error] = errors + assert %Error{} = error + assert error.reason == :custom + assert error.path == [:even_number] + assert Error.message_with_path(error) == "#/even_number: Must be an even integer" + end + test "nil value with xxxOf" do schema = %Schema{anyOf: [%Schema{nullable: true, type: :string}]} assert {:ok, nil} = cast(value: nil, schema: schema)