Skip to content

Commit

Permalink
Fix cast x-validate when decoded schema (#647)
Browse files Browse the repository at this point in the history
* Fix cast x-validate when decoded schema

* fix credo complain when using apply/3

---------

Co-authored-by: Giorgio Torres <giorgio.torres@hpe.com>
  • Loading branch information
GPrimola and Giorgio Torres authored Dec 16, 2024
1 parent 1447498 commit 620c57e
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 20 deletions.
8 changes: 6 additions & 2 deletions lib/open_api_spex/cast.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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}),
Expand Down
51 changes: 33 additions & 18 deletions test/cast_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions test/support/encoded_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,15 @@
"not": {
"type": "string"
}
},
"CustomValidationDecoded": {
"type": "object",
"properties": {
"even_num": {
"type": "integer",
"x-validate": "OpenApiSpec.CastTest.CustomValidator.EvenInt"
}
}
}
},
"links": {
Expand Down

2 comments on commit 620c57e

@rifkiKargo
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @GPrimola, may i know why you dont just put |> convert_value_to_atom_if_present("x-validate") in OpenApiSpex.OpenApi.Decode.prepare_schema/1 ?. I also got the same issue(locally) where my module is a string instead of atom. Added the mentioned line seems to fix it(at least for my case). Would be great if you can enlighten me, thanks!

@zorbash
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GPrimola the suggestion of @rifkiKargo makes a lot of sense and it has the advantage that the string -> atom module will be performed once when decoding. The only drawback is that convert_value_to_atom_if_present("x-validate") won't work with strings like "OpenApiSpec.CastTest.CustomValidator.EvenInt" it works though with "Elixir.OpenApiSpec.CastTest.CustomValidator.EvenInt".

Please sign in to comment.