diff --git a/lib/open_api_spex/cast.ex b/lib/open_api_spex/cast.ex index 269cf3f4..ea623d61 100644 --- a/lib/open_api_spex/cast.ex +++ b/lib/open_api_spex/cast.ex @@ -119,8 +119,12 @@ defmodule OpenApiSpex.Cast do @spec cast(t()) :: {:ok, term()} | {:error, [Error.t()]} # Custom validator - def cast(%__MODULE__{schema: %{"x-validate": module}} = ctx) when module != nil, - do: module.cast(ctx) + def cast(%__MODULE__{schema: %{"x-validate": module}} = ctx) + when is_atom(module) and module != nil, + do: module.cast(ctx) + + def cast(%__MODULE__{schema: %{"x-validate": module}} = ctx) when is_binary(module), + do: module |> Elixir.String.split(".") |> Module.concat() |> then(& &1.cast(ctx)) # nil schema def cast(%__MODULE__{value: value, schema: nil}), diff --git a/test/cast_test.exs b/test/cast_test.exs index 09de70df..f0d71273 100644 --- a/test/cast_test.exs +++ b/test/cast_test.exs @@ -7,6 +7,23 @@ defmodule OpenApiSpec.CastTest do def cast(ctx), do: Cast.cast(ctx) describe "cast/1" do + defmodule CustomValidator.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 + test "unknown schema type" do assert {:error, [error]} = cast(value: "string", schema: %Schema{type: :nope}) assert error.reason == :invalid_schema_type @@ -223,24 +240,7 @@ defmodule OpenApiSpec.CastTest do 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()}} + schema = %Schema{type: :object, properties: %{even_number: CustomValidator.EvenInt.schema()}} assert {:error, errors} = cast(value: %{"even_number" => 1}, schema: schema) assert [error] = errors @@ -250,6 +250,21 @@ defmodule OpenApiSpec.CastTest do assert Error.message_with_path(error) == "#/even_number: Must be an even integer" end + test "cast with custom validator from decoded schema" do + spec = + "./test/support/encoded_schema.json" + |> File.read!() + |> Jason.decode!() + |> OpenApiSpex.OpenApi.Decode.decode() + + %{ + components: %{schemas: %{"CustomValidationDecoded" => custom_validation_schema}} + } = spec + + assert {:ok, %{even_num: 2}} = + cast(value: %{"even_num" => 2}, schema: custom_validation_schema) + end + test "nil value with xxxOf" do schema = %Schema{anyOf: [%Schema{nullable: true, type: :string}]} assert {:ok, nil} = cast(value: nil, schema: schema) diff --git a/test/support/encoded_schema.json b/test/support/encoded_schema.json index 7d77ea03..a7992696 100644 --- a/test/support/encoded_schema.json +++ b/test/support/encoded_schema.json @@ -280,6 +280,15 @@ "not": { "type": "string" } + }, + "CustomValidationDecoded": { + "type": "object", + "properties": { + "even_num": { + "type": "integer", + "x-validate": "OpenApiSpec.CastTest.CustomValidator.EvenInt" + } + } } }, "links": {