From b92bc72045764e8239ddf0bf294e774b578fcab9 Mon Sep 17 00:00:00 2001 From: linnal Date: Mon, 31 Oct 2022 13:04:38 +0100 Subject: [PATCH 1/2] Adds nullable check in types --- lib/ex_json_schema/validator/type.ex | 15 ++++++++++----- test/ex_json_schema/validator_test.exs | 4 ++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/ex_json_schema/validator/type.ex b/lib/ex_json_schema/validator/type.ex index b36c33a..4832498 100644 --- a/lib/ex_json_schema/validator/type.ex +++ b/lib/ex_json_schema/validator/type.ex @@ -14,6 +14,11 @@ defmodule ExJsonSchema.Validator.Type do @behaviour ExJsonSchema.Validator @impl ExJsonSchema.Validator + + def validate(%{version: version}, %{"nullable" => nullable}, {"type", type}, data, _) do + do_validate(version, type, data, nullable: nullable) + end + def validate(%{version: version}, _, {"type", type}, data, _) do do_validate(version, type, data) end @@ -27,11 +32,11 @@ defmodule ExJsonSchema.Validator.Type do type :: ExJsonSchema.data(), data :: ExJsonSchema.data() ) :: Validator.errors() - defp do_validate(version, type, data) do - if valid?(version, type, data) do - [] - else - [%Error{error: %Error.Type{expected: List.wrap(type), actual: data_type(data)}}] + defp do_validate(version, type, data, [nullable: nullable] \\ [nullable: false]) do + cond do + nullable && is_nil(data) -> [] + valid?(version, type, data) -> [] + true -> [%Error{error: %Error.Type{expected: List.wrap(type), actual: data_type(data)}}] end end diff --git a/test/ex_json_schema/validator_test.exs b/test/ex_json_schema/validator_test.exs index 7b4d71c..7f04eea 100644 --- a/test/ex_json_schema/validator_test.exs +++ b/test/ex_json_schema/validator_test.exs @@ -764,6 +764,10 @@ defmodule ExJsonSchema.ValidatorTest do validate(%{"type" => "string"}, 666, error_formatter: Error.StringFormatter) end + test "xx" do + assert :ok = validate(%{"type" => "string", "nullable" => true}, nil, error_formatter: Error.StringFormatter) + end + test "using the string formatter by default" do assert {:error, [{"Type mismatch. Expected String but got Integer.", "#"}]} = validate(%{"type" => "string"}, 666) end From b4d5c1df24ebb4d6125fcac355abc98ccfdfcadd Mon Sep 17 00:00:00 2001 From: linnal Date: Mon, 31 Oct 2022 17:02:29 +0100 Subject: [PATCH 2/2] WIP Adds nullable validator logic --- lib/ex_json_schema/validator.ex | 3 ++ lib/ex_json_schema/validator/error.ex | 4 +++ .../validator/error/string_formatter.ex | 6 ++++ lib/ex_json_schema/validator/nullable.ex | 35 +++++++++++++++++++ lib/ex_json_schema/validator/type.ex | 16 ++++----- test/ex_json_schema/validator_test.exs | 11 +++++- 6 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 lib/ex_json_schema/validator/nullable.ex diff --git a/lib/ex_json_schema/validator.ex b/lib/ex_json_schema/validator.ex index 9894408..448ba7f 100644 --- a/lib/ex_json_schema/validator.ex +++ b/lib/ex_json_schema/validator.ex @@ -79,6 +79,8 @@ defmodule ExJsonSchema.Validator do def do_validation_errors(root = %Root{}, schema = %{}, data, path) do schema + # TODO + # |> Map.put_new("nullable", false) |> Enum.flat_map(fn {propertyName, _} = property -> case validator_for(propertyName) do nil -> [] @@ -156,6 +158,7 @@ defmodule ExJsonSchema.Validator do defp validator_for("$ref"), do: ExJsonSchema.Validator.Ref defp validator_for("required"), do: ExJsonSchema.Validator.Required defp validator_for("type"), do: ExJsonSchema.Validator.Type + defp validator_for("nullable"), do: ExJsonSchema.Validator.Nullable defp validator_for("uniqueItems"), do: ExJsonSchema.Validator.UniqueItems defp validator_for(_), do: nil end diff --git a/lib/ex_json_schema/validator/error.ex b/lib/ex_json_schema/validator/error.ex index 37e50ab..b045a81 100644 --- a/lib/ex_json_schema/validator/error.ex +++ b/lib/ex_json_schema/validator/error.ex @@ -117,6 +117,10 @@ defmodule ExJsonSchema.Validator.Error do defstruct([:missing]) end + defmodule Nullable do + defstruct([:allowed]) + end + defmodule Type do defstruct([:expected, :actual]) end diff --git a/lib/ex_json_schema/validator/error/string_formatter.ex b/lib/ex_json_schema/validator/error/string_formatter.ex index 92a610d..4416205 100644 --- a/lib/ex_json_schema/validator/error/string_formatter.ex +++ b/lib/ex_json_schema/validator/error/string_formatter.ex @@ -194,6 +194,12 @@ defmodule ExJsonSchema.Validator.Error.StringFormatter do end end + defimpl String.Chars, for: Error.Nullable do + def to_string(%Error.Nullable{allowed: false}) do + "Nullable value is not allowed." + end + end + defimpl String.Chars, for: Error.Type do def to_string(%Error.Type{expected: expected, actual: actual}) do "Type mismatch. Expected #{type_names(expected)} but got #{type_names(actual)}." diff --git a/lib/ex_json_schema/validator/nullable.ex b/lib/ex_json_schema/validator/nullable.ex new file mode 100644 index 0000000..b5e8519 --- /dev/null +++ b/lib/ex_json_schema/validator/nullable.ex @@ -0,0 +1,35 @@ +defmodule ExJsonSchema.Validator.Nullable do + @moduledoc """ + `ExJsonSchema.Validator` implementation for `"nullable"` attributes. + + See: + https://tools.ietf.org/html/draft-fge-json-schema-validation-00#section-5.5.2 + https://tools.ietf.org/html/draft-wright-json-schema-validation-01#section-6.25 + https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-6.1.1 + """ + + alias ExJsonSchema.Validator.Error + + @behaviour ExJsonSchema.Validator + + @impl ExJsonSchema.Validator + def validate(_, _, {"nullable", nullable}, data, _) do + do_validate(nullable, data) + end + + def validate(_, _, _, data, _) do + # by default nullable is not allowed + nullable = false + do_validate(nullable, data) + end + + defp do_validate(nullable, data) do + if !nil_allowed?(nullable) && is_nil(data) do + [%Error{error: %Error.Nullable{allowed: false}}] + else + [] + end + end + + defp nil_allowed?(nullable), do: nullable == true +end diff --git a/lib/ex_json_schema/validator/type.ex b/lib/ex_json_schema/validator/type.ex index 4832498..5574a50 100644 --- a/lib/ex_json_schema/validator/type.ex +++ b/lib/ex_json_schema/validator/type.ex @@ -14,11 +14,6 @@ defmodule ExJsonSchema.Validator.Type do @behaviour ExJsonSchema.Validator @impl ExJsonSchema.Validator - - def validate(%{version: version}, %{"nullable" => nullable}, {"type", type}, data, _) do - do_validate(version, type, data, nullable: nullable) - end - def validate(%{version: version}, _, {"type", type}, data, _) do do_validate(version, type, data) end @@ -32,14 +27,15 @@ defmodule ExJsonSchema.Validator.Type do type :: ExJsonSchema.data(), data :: ExJsonSchema.data() ) :: Validator.errors() - defp do_validate(version, type, data, [nullable: nullable] \\ [nullable: false]) do - cond do - nullable && is_nil(data) -> [] - valid?(version, type, data) -> [] - true -> [%Error{error: %Error.Type{expected: List.wrap(type), actual: data_type(data)}}] + defp do_validate(version, type, data) do + if valid?(version, type, data) do + [] + else + [%Error{error: %Error.Type{expected: List.wrap(type), actual: data_type(data)}}] end end + defp valid?(_, _, nil), do: true defp valid?(_, "number", data), do: is_number(data) defp valid?(_, "array", data), do: is_list(data) defp valid?(_, "object", data), do: is_map(data) diff --git a/test/ex_json_schema/validator_test.exs b/test/ex_json_schema/validator_test.exs index 7f04eea..d367526 100644 --- a/test/ex_json_schema/validator_test.exs +++ b/test/ex_json_schema/validator_test.exs @@ -764,10 +764,19 @@ defmodule ExJsonSchema.ValidatorTest do validate(%{"type" => "string"}, 666, error_formatter: Error.StringFormatter) end - test "xx" do + test "allows attibute to have value nil if nullable is not specified" do + assert :ok = validate(%{"type" => "string"}, nil, error_formatter: Error.StringFormatter) + end + + test "allows attibute to have value nil if nullable is allowed" do assert :ok = validate(%{"type" => "string", "nullable" => true}, nil, error_formatter: Error.StringFormatter) end + test "does not allowe nil values" do + assert {:error, [{"Nullable value is not allowed.", "#"}]} = + validate(%{"type" => "string", "nullable" => false}, nil, error_formatter: Error.StringFormatter) + end + test "using the string formatter by default" do assert {:error, [{"Type mismatch. Expected String but got Integer.", "#"}]} = validate(%{"type" => "string"}, 666) end