Current section

Files

Jump to
funx lib ord dsl errors.ex
Raw

lib/ord/dsl/errors.ex

defmodule Funx.Ord.Dsl.Errors do
@moduledoc false
# Centralized error messages for Ord DSL
#
# ## Error Message Contract
#
# These messages are part of the public API. Changes should be:
# 1. Documented in CHANGELOG
# 2. Reflected in tests
# 3. Considered breaking changes if semantics change
#
# Each error has:
# - Clear category (syntax, type, option misuse)
# - Actionable guidance (what to do instead)
# - Examples when helpful
@doc """
Error: DSL syntax must be `asc projection` or `desc projection`.
"""
def invalid_dsl_syntax(got) do
"""
Invalid Ord DSL syntax.
Expected: `asc projection` or `desc projection`
Got: #{inspect(got)}
Valid examples:
asc :name
desc :age
asc Lens.key(:score)
"""
end
@doc """
Error: Captured function with `or_else:` option.
"""
def or_else_with_captured_function do
"""
The `or_else:` option cannot be used with captured functions.
Use one of these alternatives:
1. Create a 0-arity helper that returns a Prism:
def score_prism, do: Prism.key(:score)
asc score_prism(), or_else: 0
2. Use inline Prism syntax:
asc Prism.key(:score), or_else: 0
Reason: Captured functions like &fun/1 don't expose their projection type
at compile time, making or_else semantics ambiguous.
"""
end
@doc """
Error: Anonymous function with `or_else:` option.
"""
def or_else_with_anonymous_function do
"""
The `or_else:` option cannot be used with anonymous functions.
Use one of these alternatives:
1. Create a 0-arity helper that returns a Prism:
def score_prism, do: Prism.key(:score)
asc score_prism(), or_else: 0
2. Use inline Prism syntax:
asc Prism.key(:score), or_else: 0
Reason: Anonymous functions like `fn x -> x.field end` don't expose their
projection type at compile time, making or_else semantics ambiguous.
"""
end
@doc """
Error: Lens with `or_else:` option.
"""
def or_else_with_lens do
"""
The `or_else:` option is only valid with atoms or Prism projections, not with Lens.
Reason: Lens guarantees focus on an existing value (or raises an error).
Adding an or_else would violate this contract.
If you need optional field handling, use a Prism instead:
asc Prism.key(:name), or_else: "Unknown"
Or use an atom with or_else (automatically becomes a Prism):
asc :name, or_else: "Unknown"
"""
end
@doc """
Error: Behaviour module with `or_else:` option.
"""
def or_else_with_behaviour do
"""
The `or_else:` option is only valid with atoms or Prism projections, not with Behaviour modules.
Reason: Behaviour modules define custom projection logic that may not
return Maybe values compatible with or_else handling.
If you need or_else handling, implement it inside your Behaviour:
defmodule MyProjection do
@behaviour Funx.Ord.Dsl.Behaviour
def project(value, opts) do
value.field || Keyword.get(opts, :or_else, 0)
end
end
"""
end
@doc """
Error: Ord variable with `or_else:` option.
"""
def or_else_with_ord_variable do
"""
The `or_else:` option cannot be used with ord variables.
Reason: Ord variables are complete ordering functions that already define
their own comparison logic. The or_else option only applies to field
projections that might return nil values.
If you need to customize the ord variable's behavior, modify it before
using it in the DSL:
base_ord = ord do asc :name, or_else: "Unknown" end
combined = ord do asc base_ord end
"""
end
@doc """
Error: {Prism, or_else} tuple already has or_else, can't use option too.
"""
def redundant_or_else do
"""
Invalid usage: {Prism, or_else} tuple already contains an or_else value.
Do not also use the `or_else:` option.
Choose one:
1. Tuple syntax:
asc {Prism.key(:score), 0}
2. Option syntax:
asc Prism.key(:score), or_else: 0
Both are equivalent and normalize to the same form.
"""
end
@doc """
Error: Projection type not recognized.
"""
def invalid_projection_type(got) do
"""
Invalid projection type.
Expected one of:
- atom (e.g., :name)
- Lens (e.g., Lens.key(:name))
- Prism (e.g., Prism.key(:score))
- {Prism, or_else} (e.g., {Prism.key(:score), 0})
- function (e.g., &String.length/1)
- Behaviour module (e.g., MyProjection)
Got: #{inspect(got)}
"""
end
end