defmodule ExJsonSchema.Validator do alias ExJsonSchema.Validator.Error alias ExJsonSchema.Schema alias ExJsonSchema.Schema.Root @type errors :: [%Error{}] | list @type options :: [error_formatter: module() | false] @callback validate( Root.t(), ExJsonSchema.data(), {String.t(), ExJsonSchema.data()}, ExJsonSchema.data(), ExJsonSchema.json_path() ) :: errors @spec validate(Root.t() | ExJsonSchema.object(), ExJsonSchema.data()) :: :ok | {:error, errors} | no_return def validate(root, data, options \\ []) def validate(root = %Root{}, data, options) when is_list(options) do validate_fragment(root, root.schema, data, options) end def validate(schema = %{}, data, options) when is_list(options) do validate(Schema.resolve(schema), data, options) end @spec validate_fragment( Root.t(), ExJsonSchema.json_path() | Schema.resolved(), ExJsonSchema.data(), options ) :: :ok | {:error, errors} | Schema.invalid_reference_error() | no_return def validate_fragment(root, schema_or_ref, data, options \\ []) when is_list(options) do result = case validation_errors(root, schema_or_ref, data) do {:error, _error} = error -> error [] -> :ok errors -> {:error, errors} end case Keyword.get(options, :error_formatter, Error.StringFormatter) do false -> result formatter -> format_errors(result, formatter) end end @spec validation_errors( Root.t(), ExJsonSchema.json_path() | Schema.resolved(), ExJsonSchema.data(), String.t() ) :: errors | Schema.invalid_reference_error() | no_return def validation_errors(root, schema_or_ref, data, path \\ "#") def validation_errors(root, ref, data, path) when is_binary(ref) do case Schema.get_fragment(root, ref) do {:ok, schema} -> validation_errors(root, schema, data, path) error -> error end end def validation_errors(root = %Root{}, %{"$ref" => ref}, data, path) do do_validation_errors(root, %{"$ref" => ref}, data, path) end def validation_errors(%Root{}, true, _data, _path) do [] end def validation_errors(%Root{}, false, _data, path) do [%Error{error: %Error.False{}, path: path}] end def validation_errors(root = %Root{}, schema = %{}, data, path) do do_validation_errors(root, schema, data, path) end def do_validation_errors(root = %Root{}, schema = %{}, data, path) do schema |> Enum.flat_map(fn {propertyName, _} = property -> case validator_for(propertyName) do nil -> [] validator -> validator.validate(root, schema, property, data, path) end end) |> Enum.map(fn %Error{path: nil} = error -> %{error | path: path} error -> error end) end @spec valid?(Root.t() | ExJsonSchema.object(), ExJsonSchema.data()) :: boolean | no_return def valid?(root = %Root{}, data), do: valid_fragment?(root, root.schema, data) def valid?(schema = %{}, data), do: valid?(Schema.resolve(schema), data) @spec valid_fragment?( Root.t(), ExJsonSchema.json_path() | Schema.resolved(), ExJsonSchema.data() ) :: boolean | Schema.invalid_reference_error() | no_return def valid_fragment?(root, schema_or_ref, data) do case validation_errors(root, schema_or_ref, data) do {:error, _error} = error -> error [] -> true _errors -> false end end def map_to_invalid_errors(errors_with_index) do errors_with_index |> Enum.map(fn {errors, index} -> %Error.InvalidAtIndex{errors: errors, index: index} end) end defp format_errors(:ok, _error_formatter), do: :ok defp format_errors({:error, errors}, error_formatter) when is_list(errors) do {:error, error_formatter.format(errors)} end defp format_errors({:error, _} = error, _error_formatter), do: error defp validator_for("allOf"), do: ExJsonSchema.Validator.AllOf defp validator_for("anyOf"), do: ExJsonSchema.Validator.AnyOf defp validator_for("const"), do: ExJsonSchema.Validator.Const defp validator_for("contains"), do: ExJsonSchema.Validator.Contains defp validator_for("contentEncoding"), do: ExJsonSchema.Validator.ContentEncodingContentMediaType defp validator_for("dependencies"), do: ExJsonSchema.Validator.Dependencies defp validator_for("enum"), do: ExJsonSchema.Validator.Enum defp validator_for("exclusiveMaximum"), do: ExJsonSchema.Validator.ExclusiveMaximum defp validator_for("exclusiveMinimum"), do: ExJsonSchema.Validator.ExclusiveMinimum defp validator_for("format"), do: ExJsonSchema.Validator.Format defp validator_for("if"), do: ExJsonSchema.Validator.IfThenElse defp validator_for("items"), do: ExJsonSchema.Validator.Items defp validator_for("maxItems"), do: ExJsonSchema.Validator.MaxItems defp validator_for("maxLength"), do: ExJsonSchema.Validator.MaxLength defp validator_for("maxProperties"), do: ExJsonSchema.Validator.MaxProperties defp validator_for("maximum"), do: ExJsonSchema.Validator.Maximum defp validator_for("minItems"), do: ExJsonSchema.Validator.MinItems defp validator_for("minLength"), do: ExJsonSchema.Validator.MinLength defp validator_for("minProperties"), do: ExJsonSchema.Validator.MinProperties defp validator_for("minimum"), do: ExJsonSchema.Validator.Minimum defp validator_for("multipleOf"), do: ExJsonSchema.Validator.MultipleOf defp validator_for("not"), do: ExJsonSchema.Validator.Not defp validator_for("oneOf"), do: ExJsonSchema.Validator.OneOf defp validator_for("pattern"), do: ExJsonSchema.Validator.Pattern defp validator_for("properties"), do: ExJsonSchema.Validator.Properties defp validator_for("propertyNames"), do: ExJsonSchema.Validator.PropertyNames 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("uniqueItems"), do: ExJsonSchema.Validator.UniqueItems defp validator_for(_), do: nil end