@@ -23,11 +23,14 @@ defmodule BitcrowdEcto.Repo do
2323 @ type fetch_option ::
2424 { :lock , lock_mode | false }
2525 | { :preload , atom | list }
26- | { :error_tag , any }
26+ | { :error_tag , false | any }
2727 | { :raise_cast_error , boolean ( ) }
2828 | ecto_option
2929
30- @ type fetch_result :: { :ok , Ecto.Schema . t ( ) } | { :error , { :not_found , Ecto.Queryable . t ( ) | any } }
30+ @ type fetch_result ::
31+ { :ok , Ecto.Schema . t ( ) }
32+ | { :error , { :not_found , Ecto.Queryable . t ( ) | any } }
33+ | { :error , :not_found }
3134
3235 @ ecto_options [ :prefix , :timeout , :log , :telemetry_event , :telemetry_options ]
3336
@@ -39,46 +42,62 @@ defmodule BitcrowdEcto.Repo do
3942 | { :telemetry_options , any }
4043
4144 @ doc """
42- Fetches a record by primary key or returns a "tagged" error tuple.
45+ Fetches a record by primary key or returns an error tuple.
4346
4447 See `c:fetch_by/3`.
4548 """
4649 @ doc since: "0.1.0"
4750 @ callback fetch ( schema :: module , id :: any ) :: fetch_result ( )
4851
4952 @ doc """
50- Fetches a record by given clauses or returns a "tagged" error tuple.
53+ Fetches a record by given clauses or returns an error tuple.
5154
5255 See `c:fetch_by/3` for options.
5356 """
5457 @ doc since: "0.1.0"
5558 @ callback fetch ( schema :: module , id :: any , [ fetch_option ( ) ] ) :: fetch_result ( )
5659
5760 @ doc """
58- Fetches a record by given clauses or returns a "tagged" error tuple.
61+ Fetches a record by given clauses or returns an error tuple.
5962
6063 See `c:fetch_by/3` for options.
6164 """
6265 @ doc since: "0.1.0"
6366 @ callback fetch_by ( queryable :: Ecto.Queryable . t ( ) , clauses :: map | keyword ) :: fetch_result ( )
6467
6568 @ doc """
66- Fetches a record by given clauses or returns a "tagged" error tuple.
69+ Fetches a record by given clauses or returns an error tuple.
6770
6871 - On success, the record is wrapped in a `:ok` tuple.
69- - On error, a "tagged" error tuple is returned that contains the *original* queryable or module
70- as the tag, e.g. `{:error, {:not_found, Account}}` for a `fetch_by(Account, id: 1)` call.
72+ - On error, an error tuple is returned
73+
74+ ## Tagged error tuples
75+
76+ By default, the error tuple will be a "tagged" `:not_found` tuple, e.g.
77+ `{:error, {:not_found, Account}}` for a `fetch_by(Account, id: 1)` call, where the "tag" is
78+ the unmodified `queryable` parameter. The idea behind this is that it avoid mix-ups of
79+ naked `:not_found` errors, particularly in `with` clauses.
80+
81+ Tagging behaviour may be disabled by passing the `error_tag: false` option to return
82+ naked `{:error, :not_found}` tuples instead. For existing applications where untagged errors
83+ are the norm, one may set the `tagged_not_found_errors: false` option when using this module.
84+
85+ use BitcrowdEcto.Repo, tagged_not_found_errors: false
86+
87+ ## Automatic conversion of `CastError`
7188
7289 Passing invalid values that would normally result in an `Ecto.Query.CastError` will result in
73- a `:not_found` error tuple as well.
90+ a `:not_found` error tuple. This is useful for low-level handling of invalid UUIDs passed
91+ from a hand-edited URL to the domain layer.
7492
75- This function can also apply row locks .
93+ This behaviour can be disabled by passing `raise_cast_error: false` .
7694
7795 ## Options
7896
7997 * `lock` any of `[:no_key_update, :update]` (defaults to `false`)
8098 * `preload` allows to preload associations
8199 * `error_tag` allows to specify a custom "tag" value (instead of the queryable)
100+ or `false` to disabled tagged error tuples
82101 * `raise_cast_error` raise `CastError` instead of converting to `:not_found` (defaults to `false`)
83102
84103 ## Ecto options
@@ -149,12 +168,18 @@ defmodule BitcrowdEcto.Repo do
149168 @ doc since: "0.1.0"
150169 @ callback advisory_xact_lock ( atom | binary ) :: :ok
151170
152- defmacro __using__ ( _ ) do
171+ defmacro __using__ ( opts ) do
172+ tagged_not_found_errors = Keyword . get ( opts , :tagged_not_found_errors , true )
173+
153174 quote do
154175 alias BitcrowdEcto.Repo , as: BER
155176
156177 @ behaviour BER
157178
179+ @ doc false
180+ @ spec __tagged_not_found_errors__ ( ) :: boolean
181+ def __tagged_not_found_errors__ , do: unquote ( tagged_not_found_errors )
182+
158183 @ impl BER
159184 def fetch ( module , id , opts \\ [ ] ) when is_atom ( module ) do
160185 BER . fetch ( __MODULE__ , module , id , opts )
@@ -206,7 +231,7 @@ defmodule BitcrowdEcto.Repo do
206231 end )
207232
208233 case result do
209- nil -> { :error , { :not_found , Keyword . get ( opts , :error_tag , queryable ) } }
234+ nil -> handle_not_found_error ( repo , queryable , opts )
210235 value -> { :ok , value }
211236 end
212237 end
@@ -247,6 +272,16 @@ defmodule BitcrowdEcto.Repo do
247272 end
248273 end
249274
275+ defp handle_not_found_error ( repo , queryable , opts ) do
276+ tag = Keyword . get ( opts , :error_tag , queryable )
277+
278+ if repo . __tagged_not_found_errors__ ( ) == false or tag == false do
279+ { :error , :not_found }
280+ else
281+ { :error , { :not_found , tag } }
282+ end
283+ end
284+
250285 @ doc false
251286 @ spec count ( module , Ecto.Queryable . t ( ) , keyword ) :: non_neg_integer
252287 def count ( repo , queryable , opts ) do
0 commit comments