cowl
Types
Selects which characters to expose in a Peek strategy.
pub type PeekMode {
First(n: Int)
Last(n: Int)
Both(n: Int, m: Int)
}
Constructors
-
First(n: Int)First
ncharacters, then the filler. -
Last(n: Int)Filler, then the last
ncharacters. -
Both(n: Int, m: Int)First
ncharacters, filler, then the lastm.
An opaque wrapper that keeps a value secret.
The value lives inside a closure, so io.debug, echo, and
string.inspect never reveal it.
An optional default_masker controls what mask returns when no explicit
Strategy is given. Constructors like token set a smart default;
secret and labeled leave it unset, falling back to "***".
pub opaque type Secret(a)
How to render a string secret as text.
pub type Strategy {
Stars
Fixed(text: String)
Label
Peek(mode: PeekMode, filler: String)
Custom(f: fn(String) -> String)
}
Constructors
-
StarsAlways
"***". -
Fixed(text: String)A fixed replacement string; the secret value is ignored.
-
LabelThe label in brackets, or
"[secret]"if no label is set. -
Peek(mode: PeekMode, filler: String)Show a small window of the secret with filler around it.
-
Custom(f: fn(String) -> String)Apply an arbitrary function to the raw value.
⚠️ The function receives the raw secret value. Never pass a logging function directly — it will expose the secret. Use a pure transform only.
Values
pub fn and_then(
secret: Secret(a),
f: fn(a) -> Secret(b),
) -> Secret(b)
Chain a transformation that itself returns a Secret, avoiding Secret(Secret(b)).
The outer secret’s label is preserved. The inner secret’s default masker
is carried forward (since it already knows how to display b).
The inner secret’s label is discarded.
cowl.secret("hunter2")
|> cowl.and_then(fn(pw) { hash_password(pw) |> cowl.secret })
pub fn equal(a: Secret(a), b: Secret(a)) -> Bool
Compare two secrets by value — labels and maskers are ignored.
pub fn field(secret: Secret(a)) -> #(String, String)
Return #(label, "***") for structured logging. Falls back to "secret" if unlabeled.
pub fn field_with(
secret: Secret(String),
strategy: Strategy,
) -> #(String, String)
Like field, with an explicit masking strategy. Requires Secret(String).
pub fn from_option(
opt: option.Option(a),
) -> option.Option(Secret(a))
Wrap the Some value of an Option as a secret, returning None unchanged.
pub fn from_result(res: Result(a, e)) -> Result(Secret(a), e)
Wrap the Ok value of a Result as a secret, passing errors through.
pub fn get_label(secret: Secret(a)) -> option.Option(String)
Return the label, if any.
pub fn labeled(value: a, label: String) -> Secret(a)
Wrap value as a secret with a label.
pub fn labeled_from_option(
opt: option.Option(a),
label: String,
) -> option.Option(Secret(a))
Like from_option, but also attaches a label.
pub fn labeled_from_result(
res: Result(a, e),
label: String,
) -> Result(Secret(a), e)
Like from_result, but also attaches a label.
pub fn map(secret: Secret(a), f: fn(a) -> b) -> Secret(b)
Transform the wrapped value while keeping it secret.
The label is preserved. The default masker is not transferred because
the masker type fn(a) -> String cannot apply to the new type b.
Attach a new masker via new or token if needed.
pub fn map_label(
secret: Secret(a),
f: fn(String) -> String,
) -> Secret(a)
Transform only the label, leaving the value and masker untouched.
pub fn mask(secret: Secret(a)) -> String
Render the secret as a safe string using its default masker.
- If a
default_maskerwas set (e.g. viatokenornew), it is called. - Otherwise returns
"***".
Use mask_with to apply an explicit Strategy at the call site.
pub fn mask_with(
secret: Secret(String),
strategy: Strategy,
) -> String
Render a string secret using the given strategy, ignoring any default masker.
pub fn new(value: a, masker: fn(a) -> String) -> Secret(a)
Wrap value with an explicit masker function used by mask.
The masker receives the raw value and must return a safe string representation. Never use a logging function as the masker.
pub fn remove_label(secret: Secret(a)) -> Secret(a)
Remove the label from a secret. The value and masker are untouched.
pub fn secret(value: a) -> Secret(a)
Wrap value as a secret with no label and no custom masker.
mask falls back to "***".
pub fn string(value: String) -> Secret(String)
Wrap a String value as a secret. Equivalent to secret but
constrains the type to String at the call site.
pub fn tap_masked(
secret: Secret(a),
f: fn(String) -> b,
) -> Secret(a)
Run f on the masked representation for its side effects; return the
original secret unchanged. Safe to use with logging functions.
api_key
|> cowl.tap_masked(fn(masked) { logger.info("key in use: " <> masked) })
|> make_request
pub fn to_string(secret: Secret(a)) -> String
A safe debug string — always "Secret(***)" or "Secret(<masked>)".
Never reveals the actual value.
pub fn token(value: String) -> Secret(String)
Wrap an API token with a smart partial-reveal masker.
mask will show the first 4 and last 4 characters with "..." in
between — enough to identify a token without exposing it.
cowl.token("sk-abc123xyz789") |> cowl.mask
// "sk-a...y789" (if len > 8)
pub fn with_label(secret: Secret(a), label: String) -> Secret(a)
Set or replace the label on a secret. The value and masker are untouched.
pub fn with_secret(secret: Secret(a), f: fn(a) -> b) -> b
Pass the raw value to f without letting it escape the return type.
This is the preferred way to consume a secret. The raw value is confined to the callback scope and cannot propagate further through your code.
cowl.with_secret(api_key, fn(raw) { send_request(raw) })