Packages

Type-safe secret masking for Gleam. Hide passwords, API keys, and other sensitive values without ceremony.

Current section

Files

Jump to
cowl src cowl.erl
Raw

src/cowl.erl

-module(cowl).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/cowl.gleam").
-export([secret/1, labeled/2, with_label/2, remove_label/1, get_label/1, mask_with/2, mask/1, to_string/1, equal/2, reveal/1, use_secret/2, map/2, map_label/2, tap/2, from_result/1, labeled_from_result/2, field_with/2, field/1]).
-export_type([secret/1, strategy/0, peek_mode/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-opaque secret(DQR) :: {secret, fun(() -> DQR), gleam@option:option(binary())}.
-type strategy() :: stars |
{fixed, binary()} |
label |
{peek, peek_mode(), binary()} |
{custom, fun((binary()) -> binary())}.
-type peek_mode() :: {first, integer()} |
{last, integer()} |
{both, integer(), integer()}.
-file("src/cowl.gleam", 49).
?DOC(" Wrap `value` as a secret with no label.\n").
-spec secret(DQS) -> secret(DQS).
secret(Value) ->
{secret, fun() -> Value end, none}.
-file("src/cowl.gleam", 54).
?DOC(" Wrap `value` as a secret with a `label`.\n").
-spec labeled(DQU, binary()) -> secret(DQU).
labeled(Value, Label) ->
{secret, fun() -> Value end, {some, Label}}.
-file("src/cowl.gleam", 59).
?DOC(" Set or replace the label on a secret.\n").
-spec with_label(secret(DQW), binary()) -> secret(DQW).
with_label(Secret, Label) ->
{secret, erlang:element(2, Secret), {some, Label}}.
-file("src/cowl.gleam", 64).
?DOC(" Remove the label from a secret. The value is untouched.\n").
-spec remove_label(secret(DQZ)) -> secret(DQZ).
remove_label(Secret) ->
{secret, erlang:element(2, Secret), none}.
-file("src/cowl.gleam", 69).
?DOC(" Return the label, if any.\n").
-spec get_label(secret(any())) -> gleam@option:option(binary()).
get_label(Secret) ->
erlang:element(3, Secret).
-file("src/cowl.gleam", 102).
-spec apply_peek(binary(), peek_mode(), binary()) -> binary().
apply_peek(Value, Mode, Filler) ->
Len = string:length(Value),
case Len of
0 ->
Filler;
_ ->
case Mode of
{first, N} ->
case N =< 0 of
true ->
Filler;
false ->
case N >= Len of
true ->
Value;
false ->
<<(gleam@string:slice(Value, 0, N))/binary,
Filler/binary>>
end
end;
{last, N@1} ->
case N@1 =< 0 of
true ->
Filler;
false ->
case N@1 >= Len of
true ->
Value;
false ->
<<Filler/binary,
(gleam@string:slice(
Value,
Len - N@1,
N@1
))/binary>>
end
end;
{both, N@2, M} ->
case (N@2 =< 0) orelse (M =< 0) of
true ->
Filler;
false ->
case (N@2 + M) >= Len of
true ->
Value;
false ->
<<<<(gleam@string:slice(Value, 0, N@2))/binary,
Filler/binary>>/binary,
(gleam@string:slice(Value, Len - M, M))/binary>>
end
end
end
end.
-file("src/cowl.gleam", 83).
?DOC(" Render a string secret using the given strategy.\n").
-spec mask_with(secret(binary()), strategy()) -> binary().
mask_with(Secret, Strategy) ->
case Strategy of
stars ->
<<"***"/utf8>>;
{fixed, Text} ->
Text;
label ->
case erlang:element(3, Secret) of
{some, L} ->
<<<<"["/utf8, L/binary>>/binary, "]"/utf8>>;
none ->
<<"[secret]"/utf8>>
end;
{peek, Mode, Filler} ->
apply_peek((erlang:element(2, Secret))(), Mode, Filler);
{custom, F} ->
F((erlang:element(2, Secret))())
end.
-file("src/cowl.gleam", 78).
?DOC(" Render a string secret as `\"***\"`.\n").
-spec mask(secret(binary())) -> binary().
mask(Secret) ->
mask_with(Secret, stars).
-file("src/cowl.gleam", 98).
?DOC(" A safe debug string — always `\"Secret(***)\"`, never the actual value.\n").
-spec to_string(secret(binary())) -> binary().
to_string(Secret) ->
<<<<"Secret("/utf8, (mask(Secret))/binary>>/binary, ")"/utf8>>.
-file("src/cowl.gleam", 147).
?DOC(" Compare two secrets by value — labels are ignored.\n").
-spec equal(secret(DRI), secret(DRI)) -> boolean().
equal(A, B) ->
(erlang:element(2, A))() =:= (erlang:element(2, B))().
-file("src/cowl.gleam", 156).
?DOC(" Unwrap and return the raw value.\n").
-spec reveal(secret(DRL)) -> DRL.
reveal(Secret) ->
(erlang:element(2, Secret))().
-file("src/cowl.gleam", 161).
?DOC(" Pass the raw value to `f` without letting it escape the return type.\n").
-spec use_secret(secret(DRN), fun((DRN) -> DRP)) -> DRP.
use_secret(Secret, F) ->
F((erlang:element(2, Secret))()).
-file("src/cowl.gleam", 170).
?DOC(" Transform the wrapped value while keeping it secret. The label is preserved.\n").
-spec map(secret(DRQ), fun((DRQ) -> DRS)) -> secret(DRS).
map(Secret, F) ->
{secret,
fun() -> F((erlang:element(2, Secret))()) end,
erlang:element(3, Secret)}.
-file("src/cowl.gleam", 175).
?DOC(" Transform only the label, leaving the value untouched.\n").
-spec map_label(secret(DRU), fun((binary()) -> binary())) -> secret(DRU).
map_label(Secret, F) ->
{secret, erlang:element(2, Secret), case erlang:element(3, Secret) of
{some, L} ->
{some, F(L)};
none ->
none
end}.
-file("src/cowl.gleam", 186).
?DOC(
" Run `f` for its side effects and return the original secret unchanged.\n"
"\n"
" ⚠️ Never pass a logging function directly — it will print the raw value.\n"
" Use `cowl.to_string` for a safe representation instead.\n"
).
-spec tap(secret(DRX), fun((DRX) -> any())) -> secret(DRX).
tap(Secret, F) ->
_ = F((erlang:element(2, Secret))()),
Secret.
-file("src/cowl.gleam", 196).
?DOC(" Wrap the `Ok` value of a `Result` as a secret, passing errors through.\n").
-spec from_result({ok, DSB} | {error, DSC}) -> {ok, secret(DSB)} | {error, DSC}.
from_result(Res) ->
case Res of
{ok, V} ->
{ok, secret(V)};
{error, E} ->
{error, E}
end.
-file("src/cowl.gleam", 204).
?DOC(" Like `from_result`, but also attaches a label.\n").
-spec labeled_from_result({ok, DSI} | {error, DSJ}, binary()) -> {ok,
secret(DSI)} |
{error, DSJ}.
labeled_from_result(Res, Label) ->
case Res of
{ok, V} ->
{ok, labeled(V, Label)};
{error, E} ->
{error, E}
end.
-file("src/cowl.gleam", 224).
?DOC(" Like `field`, with an explicit masking strategy.\n").
-spec field_with(secret(binary()), strategy()) -> {binary(), binary()}.
field_with(Secret, Strategy) ->
Key = case erlang:element(3, Secret) of
{some, L} ->
L;
none ->
<<"secret"/utf8>>
end,
{Key, mask_with(Secret, Strategy)}.
-file("src/cowl.gleam", 219).
?DOC(" Return `#(label, \"***\")` for structured logging. Falls back to `\"secret\"` if unlabeled.\n").
-spec field(secret(binary())) -> {binary(), binary()}.
field(Secret) ->
field_with(Secret, stars).