Skip to content

Commit 99b8a69

Browse files
authored
Validate options given to schema fields (#3750)
Adds a compile time validation that asserts that non-parameterized types don't have options that are not used. Useful to avoid typos having runtime implications.
1 parent 384d588 commit 99b8a69

File tree

2 files changed

+64
-14
lines changed

2 files changed

+64
-14
lines changed

lib/ecto/schema.ex

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,24 @@ defmodule Ecto.Schema do
486486
end
487487
end
488488

489+
@field_opts [
490+
:default,
491+
:source,
492+
:autogenerate,
493+
:read_after_writes,
494+
:virtual,
495+
:primary_key,
496+
:load_in_query,
497+
:redact,
498+
:foreign_key,
499+
:on_replace,
500+
:defaults,
501+
:type,
502+
:where,
503+
:references,
504+
:skip_default_validation
505+
]
506+
489507
@doc """
490508
Defines an embedded schema with the given field definitions.
491509
@@ -1883,12 +1901,10 @@ defmodule Ecto.Schema do
18831901

18841902
@doc false
18851903
def __field__(mod, name, type, opts) do
1886-
if type == :any and !opts[:virtual] do
1887-
raise ArgumentError, "only virtual fields can have type :any, " <>
1888-
"invalid type for field #{inspect name}"
1889-
end
1890-
18911904
type = check_field_type!(mod, name, type, opts)
1905+
1906+
opts = Keyword.put(opts, :type, type)
1907+
check_options!(opts, @field_opts, "field/3")
18921908
Module.put_attribute(mod, :changeset_fields, {name, type})
18931909
validate_default!(type, opts[:default], opts[:skip_default_validation])
18941910
define_field(mod, name, type, opts)
@@ -2161,15 +2177,27 @@ defmodule Ecto.Schema do
21612177
end
21622178

21632179
defp check_options!(opts, valid, fun_arity) do
2164-
type = Keyword.get(opts, :type)
2180+
case opts[:type] do
2181+
{:parameterized, _, _} ->
2182+
:ok
21652183

2166-
if is_atom(type) and Code.ensure_compiled(type) == {:module, type} and function_exported?(type, :type, 1) do
2167-
:ok
2168-
else
2169-
case Enum.find(opts, fn {k, _} -> not(k in valid) end) do
2170-
{k, _} -> raise ArgumentError, "invalid option #{inspect k} for #{fun_arity}"
2171-
nil -> :ok
2172-
end
2184+
{_, {:parameterized, _, _}} ->
2185+
:ok
2186+
2187+
:any ->
2188+
if !opts[:virtual], do:
2189+
raise ArgumentError, "only virtual fields can have type :any, " <>
2190+
"invalid type for field #{inspect opts[:name]}"
2191+
2192+
type ->
2193+
if is_atom(type) and Code.ensure_compiled(type) == {:module, type} and function_exported?(type, :type, 1) do
2194+
:ok
2195+
else
2196+
case Enum.find(opts, fn {k, _} -> not(k in valid) end) do
2197+
{k, _} -> raise ArgumentError, "invalid option #{inspect k} for #{fun_arity}"
2198+
nil -> :ok
2199+
end
2200+
end
21732201
end
21742202
end
21752203

test/ecto/schema_test.exs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule Ecto.SchemaTest do
88

99
schema "my schema" do
1010
field :name, :string, default: "eric", autogenerate: {String, :upcase, ["eric"]}
11-
field :email, :string, uniq: true, read_after_writes: true
11+
field :email, :string, read_after_writes: true
1212
field :password, :string, redact: true
1313
field :temp, :any, default: "temp", virtual: true, redact: true
1414
field :count, :decimal, read_after_writes: true, source: :cnt
@@ -429,6 +429,28 @@ defmodule Ecto.SchemaTest do
429429
end
430430
end
431431

432+
test "invalid option for field" do
433+
assert_raise ArgumentError, ~s/invalid option :starts_on for field\/3/, fn ->
434+
defmodule SchemaInvalidFieldOption do
435+
use Ecto.Schema
436+
437+
schema "invalid_option" do
438+
field :count, :integer, starts_on: 3
439+
end
440+
end
441+
end
442+
443+
# doesn't validate for parameterized types
444+
defmodule SchemaInvalidOptionParameterized do
445+
use Ecto.Schema
446+
447+
schema "invalid_option_parameterized" do
448+
field :my_enum, Ecto.Enum, values: [:a, :b], random_option: 3
449+
field :my_enums, Ecto.Enum, values: [:a, :b], random_option: 3
450+
end
451+
end
452+
end
453+
432454
test "invalid field type" do
433455
assert_raise ArgumentError, "invalid or unknown type {:apa} for field :name", fn ->
434456
defmodule SchemaInvalidFieldType do

0 commit comments

Comments
 (0)