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 b36c33a..5574a50 100644 --- a/lib/ex_json_schema/validator/type.ex +++ b/lib/ex_json_schema/validator/type.ex @@ -35,6 +35,7 @@ defmodule ExJsonSchema.Validator.Type do 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 7b4d71c..d367526 100644 --- a/test/ex_json_schema/validator_test.exs +++ b/test/ex_json_schema/validator_test.exs @@ -764,6 +764,19 @@ defmodule ExJsonSchema.ValidatorTest do validate(%{"type" => "string"}, 666, error_formatter: Error.StringFormatter) end + 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