Define custom derive validators / sanitizers via a Spark DSL.
Usage
defmodule MyApp.Derives do
use GuardedStruct.Derive.Extension
derives do
validator :slug, fn input ->
is_binary(input) and Regex.match?(~r/^[a-z0-9-]+$/, input)
end
sanitizer :slugify, fn input when is_binary(input) ->
input
|> String.downcase()
|> String.replace(~r/[^a-z0-9-]+/u, "-")
end
end
endRegister globally in config/config.exs:
config :guarded_struct, derive_extensions: [MyApp.Derives]Then any GuardedStruct module can use the new ops:
defmodule Post do
use GuardedStruct
guardedstruct do
field :slug, String.t(), derives: "sanitize(slugify) validate(slug)"
end
endValidator return shape
Validator functions return:
true— input passesfalse— input fails (default error message generated){:error, field, action, message}— explicit error tuple- any other value — used as the validated value (for coercing validators)
Introspection
The derives do ... end block is introspectable via
Spark.Dsl.Extension.get_entities/2. Verifiers and transformers can
plug in at the standard Spark extension points.
Options
:extensions(list of module that adoptsSpark.Dsl.Extension) - A list of DSL extensions to add to theSpark.Dsl:otp_app(atom/0) - The otp_app to use for any application configurable options:fragments(list ofmodule/0) - Fragments to include in theSpark.Dsl. See the fragments guide for more.
Summary
Functions
All sanitizer op atoms known across every extension visible from module.
All validator op atoms known across every extension visible from the given module.
Clear the cached extension list. Call from test setup if you mutate
:guarded_struct, :derive_extensions and need the change visible
before the next registered_extensions/0 call.
The user module currently being built. Set by Runtime.with_telemetry/2
around every top-level builder/1 call; nested sub_field builds inherit
via the process dictionary.
Try the current module's extensions for a sanitizer op.
Try a specific list of extensions for a sanitizer op.
Try the current module's extensions for a validator op.
Try a specific list of extensions for a validator op.
Resolve the effective extension list for a specific user module.
Resolved list of registered extension modules from :guarded_struct, :derive_extensions application config.
Resolve a per-module derive_extensions: opt — the raw list user wrote
in use GuardedStruct, derive_extensions: [...] — into a flat list of
extension modules with :config expanded to the current global config
at the position it appears.
Validate a derive_extensions: opt list at compile time. Raises
ArgumentError if entries are not modules / :config, or if :config
appears more than once.
Functions
All sanitizer op atoms known across every extension visible from module.
All validator op atoms known across every extension visible from the given module.
Clear the cached extension list. Call from test setup if you mutate
:guarded_struct, :derive_extensions and need the change visible
before the next registered_extensions/0 call.
The user module currently being built. Set by Runtime.with_telemetry/2
around every top-level builder/1 call; nested sub_field builds inherit
via the process dictionary.
Try the current module's extensions for a sanitizer op.
Try a specific list of extensions for a sanitizer op.
Try the current module's extensions for a validator op.
Try a specific list of extensions for a validator op.
Resolve the effective extension list for a specific user module.
Resolved list of registered extension modules from :guarded_struct, :derive_extensions application config.
Cached in :persistent_term and invalidated automatically when the
underlying config changes (e.g. between test cases that call
Application.put_env/3).
Resolve a per-module derive_extensions: opt — the raw list user wrote
in use GuardedStruct, derive_extensions: [...] — into a flat list of
extension modules with :config expanded to the current global config
at the position it appears.
Resolution rules
nil→ fall back to the global Application config (no per-module opt set)[]→ no extensions at all (intentional opt-out, ignores global)[A, B](no:config) → these only; global is ignored[:config, A]→ global ++ [A] (global wins on op-name collisions)[A, :config]→ [A] ++ global (A wins on op-name collisions)[A, :config, B]→ [A] ++ global ++ [B]
Validate a derive_extensions: opt list at compile time. Raises
ArgumentError if entries are not modules / :config, or if :config
appears more than once.