Obscura.Structured recursively redacts maps, lists, keyword lists, strings,
and opted-in structs. String inputs return %Obscura.Anonymizer.Result{};
structured inputs return %Obscura.Structured.Result{}.
Maps and Lists
input = %{
user: %{email: "jane@example.com", password: "secret"},
phones: ["202-555-0188"]
}
{:ok, result} =
Obscura.redact(input,
entities: [:email, :phone],
field_policies: %{password: :drop}
)
result.data
#=> %{user: %{email: "[EMAIL]"}, phones: ["[PHONE]"]}Each redaction item records the structured path, entity, operator, source byte offsets within the leaf string, replacement, and safe metadata.
Structured traversal validates UTF-8 string leaves, option shapes, field
policies, and recursion depth. Improper lists and inputs exceeding max_depth
return controlled errors rather than partially redacted output. Tuples remain
opaque values; callers must convert tuple-held data explicitly when it needs
redaction. Very wide collections can still consume proportional CPU and
memory, so untrusted input size needs an application-level limit.
Field Policies
Field policies are keyed by map key or struct field:
:dropremoves map keys and resets struct fields to their default value.:keepleaves the field unchanged.{:replace, value}replaces the whole field value.{:operator, config}applies an anonymizer operator to the whole field.{:entity, entity}analyzes the field as a specific entity.:traverserecurses into nested values.
Common sensitive keys such as :password, :token, and :api_key are replaced by default unless an explicit policy overrides them.
Structs
Opaque structs such as Date, Time, DateTime, URI, Regex, MapSet, and Range are preserved.
Application structs can opt in with @derive:
defmodule User do
@derive {Obscura.Redactable,
fields: [
email: {:entity, :email},
password_hash: :drop,
profile: :traverse
]}
defstruct [:email, :password_hash, :profile]
endtraverse_structs: true can traverse non-derived structs, and preserve_structs: false converts traversed structs to maps.
Dry Run
Obscura.Structured.analyze/2 runs traversal with dry_run: true and returns only structured items. This is useful for audits where the original data must remain unchanged.
Default Inspect implementations hide result data, item paths, replacements,
and metadata. Explicit result.data and item fields remain sensitive. Dry-run
mode does not shorten the lifetime of the original input or analyzer result
values.