Skip to content

Commit f4b540e

Browse files
committed
Incorporate improvements from #42
- Validate enums of non-string types - Validate anyOf/oneOf/allOf/not before type testing - Improve error message for regex validations
1 parent bd258fb commit f4b540e

File tree

3 files changed

+296
-187
lines changed

3 files changed

+296
-187
lines changed

lib/open_api_spex/schema.ex

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ defmodule OpenApiSpex.Schema do
415415
end
416416
end
417417

418-
@doc """
418+
@doc ~S"""
419419
Validate a value against a Schema.
420420
421421
This expects that the value has already been `cast` to the appropriate data type.
@@ -429,17 +429,54 @@ defmodule OpenApiSpex.Schema do
429429
:ok
430430
431431
iex> OpenApiSpex.Schema.validate(%OpenApiSpex.Schema{type: :string, pattern: "(.*)@(.*)"}, "joegmail.com", %{})
432-
{:error, "#: Value does not match pattern: (.*)@(.*)"}
432+
{:error, "#: Value \"joegmail.com\" does not match pattern: (.*)@(.*)"}
433433
"""
434434
@spec validate(Schema.t | Reference.t, any, %{String.t => Schema.t | Reference.t}) :: :ok | {:error, String.t}
435435
def validate(schema, val, schemas), do: validate(schema, val, "#", schemas)
436436

437437
@spec validate(Schema.t | Reference.t, any, String.t, %{String.t => Schema.t | Reference.t}) :: :ok | {:error, String.t}
438438
def validate(ref = %Reference{}, val, path, schemas), do: validate(Reference.resolve_schema(ref, schemas), val, path, schemas)
439439
def validate(%Schema{nullable: true}, nil, _path, _schemas), do: :ok
440-
def validate(%Schema{type: type}, nil, path, _schemas) do
440+
def validate(%Schema{type: type}, nil, path, _schemas) when not is_nil(type) do
441441
{:error, "#{path}: null value where #{type} expected"}
442442
end
443+
def validate(schema = %Schema{anyOf: valid_schemas}, value, path, schemas) when is_list(valid_schemas) do
444+
if Enum.any?(valid_schemas, &validate(&1, value, path, schemas) == :ok) do
445+
validate(%{schema | anyOf: nil}, value, path, schemas)
446+
else
447+
{:error, "#{path}: Failed to validate against any schema"}
448+
end
449+
end
450+
def validate(schema = %Schema{oneOf: valid_schemas}, value, path, schemas) when is_list(valid_schemas) do
451+
case Enum.count(valid_schemas, &validate(&1, value, path, schemas) == :ok) do
452+
1 -> validate(%{schema | oneOf: nil}, value, path, schemas)
453+
0 -> {:error, "#{path}: Failed to validate against any schema"}
454+
other -> {:error, "#{path}: Validated against #{other} schemas when only one expected"}
455+
end
456+
end
457+
def validate(schema = %Schema{allOf: required_schemas}, value, path, schemas) when is_list(required_schemas) do
458+
required_schemas
459+
|> Enum.map(&validate(&1, value, path, schemas))
460+
|> Enum.reject(& &1 == :ok)
461+
|> Enum.map(fn {:error, msg} -> msg end)
462+
|> case do
463+
[] -> validate(%{schema | allOf: nil}, value, path, schemas)
464+
errors -> {:error, Enum.join(errors, "\n")}
465+
end
466+
end
467+
def validate(schema = %Schema{not: not_schema}, value, path, schemas) when not is_nil(not_schema) do
468+
case validate(not_schema, value, path, schemas) do
469+
{:error, _} -> validate(%{schema | not: nil}, value, path, schemas)
470+
:ok -> {:error, "#{path}: Value is valid for schema given in `not`"}
471+
end
472+
end
473+
def validate(%Schema{enum: options = [_ | _]}, value, path, _schemas) do
474+
case Enum.member?(options, value) do
475+
true -> :ok
476+
_ ->
477+
{:error, "#{path}: Value not in enum: #{inspect(value)}"}
478+
end
479+
end
443480
def validate(schema = %Schema{type: :integer}, value, path, _schemas) when is_integer(value) do
444481
validate_number_types(schema, value, path)
445482
end
@@ -480,41 +517,11 @@ defmodule OpenApiSpex.Schema do
480517
:ok
481518
end
482519
end
483-
def validate(schema = %Schema{anyOf: valid_schemas}, value, path, schemas) when is_list(valid_schemas) do
484-
if Enum.any?(valid_schemas, &validate(&1, value, path, schemas) == :ok) do
485-
validate(%{schema | anyOf: nil}, value, path, schemas)
486-
else
487-
{:error, "#{path}: Failed to validate against any schema"}
488-
end
489-
end
490-
def validate(schema = %Schema{oneOf: valid_schemas}, value, path, schemas) when is_list(valid_schemas) do
491-
case Enum.count(valid_schemas, &validate(&1, value, path, schemas) == :ok) do
492-
1 -> validate(%{schema | oneOf: nil}, value, path, schemas)
493-
0 -> {:error, "#{path}: Failed to validate against any schema"}
494-
other -> {:error, "#{path}: Validated against #{other} schemas when only one expected"}
495-
end
496-
end
497-
def validate(schema = %Schema{allOf: required_schemas}, value, path, schemas) when is_list(required_schemas) do
498-
required_schemas
499-
|> Enum.map(&validate(&1, value, path, schemas))
500-
|> Enum.reject(& &1 == :ok)
501-
|> Enum.map(fn {:error, msg} -> msg end)
502-
|> case do
503-
[] -> validate(%{schema | allOf: nil}, value, path, schemas)
504-
errors -> {:error, Enum.join(errors, "\n")}
505-
end
506-
end
507-
def validate(schema = %Schema{not: not_schema}, value, path, schemas) when not is_nil(not_schema) do
508-
case validate(not_schema, value, path, schemas) do
509-
{:error, _} -> validate(%{schema | not: nil}, value, path, schemas)
510-
:ok -> {:error, "#{path}: Value is valid for schema given in `not`"}
511-
end
512-
end
513520
def validate(%Schema{type: nil}, _value, _path, _schemas) do
514521
# polymorphic schemas will terminate here after validating against anyOf/oneOf/allOf/not
515522
:ok
516523
end
517-
def validate(%Schema{type: expected_type}, value, path, _schemas) do
524+
def validate(%Schema{type: expected_type}, value, path, _schemas) when not is_nil(expected_type) do
518525
{:error,
519526
"#{path}: invalid type #{term_type(value)} where #{expected_type} expected"}
520527
end
@@ -543,7 +550,6 @@ defmodule OpenApiSpex.Schema do
543550
with :ok <- validate_max_length(schema, value, path),
544551
:ok <- validate_min_length(schema, value, path),
545552
:ok <- validate_pattern(schema, value, path),
546-
:ok <- validate_enum(schema, value, path) do
547553
:ok
548554
end
549555
end
@@ -593,17 +599,7 @@ defmodule OpenApiSpex.Schema do
593599
defp validate_pattern(%{pattern: regex = %Regex{}}, val, path) do
594600
case Regex.match?(regex, val) do
595601
true -> :ok
596-
_ -> {:error, "#{path}: Value does not match pattern: #{regex.source}"}
597-
end
598-
end
599-
600-
@spec validate_enum(Schema.t, String.t, String.t) :: :ok | {:error, String.t}
601-
def validate_enum(%{enum: nil}, _val, _path), do: :ok
602-
def validate_enum(%{enum: options}, value, path) do
603-
case Enum.member?(options, value) do
604-
true -> :ok
605-
_ ->
606-
{:error, "#{path}: Value not in enum: #{Enum.join(options, ", ")}"}
602+
_ -> {:error, "#{path}: Value #{inspect(val)} does not match pattern: #{regex.source}"}
607603
end
608604
end
609605

test/open_api_spex_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ defmodule OpenApiSpexTest do
6161

6262
conn = OpenApiSpexTest.Router.call(conn, [])
6363
assert conn.status == 422
64-
assert conn.resp_body == "#/user/name: Value does not match pattern: [a-zA-Z][a-zA-Z0-9_]+"
64+
assert conn.resp_body == "#/user/name: Value \"*1234\" does not match pattern: [a-zA-Z][a-zA-Z0-9_]+"
6565
end
6666
end
6767
end

0 commit comments

Comments
 (0)