-module(cowl). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/cowl.gleam"). -export([secret/1, string/1, new/2, labeled/2, with_label/2, remove_label/1, get_label/1, mask/1, to_string/1, token/1, mask_with/2, equal/2, with_secret/2, map/2, and_then/2, map_label/2, tap_masked/2, from_result/1, from_option/1, labeled_from_option/2, labeled_from_result/2, field/1, field_with/2]). -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()), gleam@option:option(fun((DQR) -> 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", 61). ?DOC( " Wrap `value` as a secret with no label and no custom masker.\n" " `mask` falls back to `\"***\"`.\n" ). -spec secret(DQS) -> secret(DQS). secret(Value) -> {secret, fun() -> Value end, none, none}. -file("src/cowl.gleam", 67). ?DOC( " Wrap a `String` value as a secret. Equivalent to `secret` but\n" " constrains the type to `String` at the call site.\n" ). -spec string(binary()) -> secret(binary()). string(Value) -> {secret, fun() -> Value end, none, none}. -file("src/cowl.gleam", 92). ?DOC( " Wrap `value` with an explicit `masker` function used by `mask`.\n" "\n" " The masker receives the raw value and must return a safe string\n" " representation. Never use a logging function as the masker.\n" ). -spec new(DQW, fun((DQW) -> binary())) -> secret(DQW). new(Value, Masker) -> {secret, fun() -> Value end, none, {some, Masker}}. -file("src/cowl.gleam", 97). ?DOC(" Wrap `value` as a secret with a `label`.\n"). -spec labeled(DQY, binary()) -> secret(DQY). labeled(Value, Label) -> {secret, fun() -> Value end, {some, Label}, none}. -file("src/cowl.gleam", 102). ?DOC(" Set or replace the label on a secret. The value and masker are untouched.\n"). -spec with_label(secret(DRA), binary()) -> secret(DRA). with_label(Secret, Label) -> {secret, erlang:element(2, Secret), {some, Label}, erlang:element(4, Secret)}. -file("src/cowl.gleam", 111). ?DOC(" Remove the label from a secret. The value and masker are untouched.\n"). -spec remove_label(secret(DRD)) -> secret(DRD). remove_label(Secret) -> {secret, erlang:element(2, Secret), none, erlang:element(4, Secret)}. -file("src/cowl.gleam", 120). ?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", 134). ?DOC( " Render the secret as a safe string using its default masker.\n" "\n" " - If a `default_masker` was set (e.g. via `token` or `new`), it is called.\n" " - Otherwise returns `\"***\"`.\n" "\n" " Use `mask_with` to apply an explicit `Strategy` at the call site.\n" ). -spec mask(secret(any())) -> binary(). mask(Secret) -> case erlang:element(4, Secret) of {some, F} -> F((erlang:element(2, Secret))()); none -> <<"***"/utf8>> end. -file("src/cowl.gleam", 158). ?DOC( " A safe debug string — always `\"Secret(***)\"` or `\"Secret()\"`.\n" " Never reveals the actual value.\n" ). -spec to_string(secret(any())) -> binary(). to_string(Secret) -> <<<<"Secret("/utf8, (mask(Secret))/binary>>/binary, ")"/utf8>>. -file("src/cowl.gleam", 192). -spec peek_part(integer(), integer(), binary(), binary(), fun(() -> binary())) -> binary(). peek_part(N, Len, Value, Filler, Build) -> case N =< 0 of true -> Filler; false -> case N >= Len of true -> Value; false -> Build() end end. -file("src/cowl.gleam", 162). -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} -> peek_part( N, Len, Value, Filler, fun() -> <<(gleam@string:slice(Value, 0, N))/binary, Filler/binary>> end ); {last, N@1} -> peek_part( N@1, Len, Value, Filler, fun() -> <> 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", 80). ?DOC( " Wrap an API token with a smart partial-reveal masker.\n" "\n" " `mask` will show the first 4 and last 4 characters with `\"...\"` in\n" " between — enough to identify a token without exposing it.\n" "\n" " ```gleam\n" " cowl.token(\"sk-abc123xyz789\") |> cowl.mask\n" " // \"sk-a...y789\" (if len > 8)\n" " ```\n" ). -spec token(binary()) -> secret(binary()). token(Value) -> {secret, fun() -> Value end, none, {some, fun(V) -> apply_peek(V, {both, 4, 4}, <<"..."/utf8>>) end}}. -file("src/cowl.gleam", 142). ?DOC(" Render a string secret using the given strategy, ignoring any default masker.\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", 214). ?DOC(" Compare two secrets by value — labels and maskers are ignored.\n"). -spec equal(secret(DRO), secret(DRO)) -> boolean(). equal(A, B) -> (erlang:element(2, A))() =:= (erlang:element(2, B))(). -file("src/cowl.gleam", 230). ?DOC( " Pass the raw value to `f` without letting it escape the return type.\n" "\n" " This is the preferred way to consume a secret. The raw value is confined\n" " to the callback scope and cannot propagate further through your code.\n" "\n" " ```gleam\n" " cowl.with_secret(api_key, fn(raw) { send_request(raw) })\n" " ```\n" ). -spec with_secret(secret(DRR), fun((DRR) -> DRT)) -> DRT. with_secret(Secret, F) -> F((erlang:element(2, Secret))()). -file("src/cowl.gleam", 243). ?DOC( " Transform the wrapped value while keeping it secret.\n" "\n" " The label is preserved. The default masker is **not** transferred because\n" " the masker type `fn(a) -> String` cannot apply to the new type `b`.\n" " Attach a new masker via `new` or `token` if needed.\n" ). -spec map(secret(DRU), fun((DRU) -> DRW)) -> secret(DRW). map(Secret, F) -> {secret, fun() -> F((erlang:element(2, Secret))()) end, erlang:element(3, Secret), none}. -file("src/cowl.gleam", 261). ?DOC( " Chain a transformation that itself returns a `Secret`, avoiding `Secret(Secret(b))`.\n" "\n" " The outer secret's label is preserved. The inner secret's default masker\n" " is carried forward (since it already knows how to display `b`).\n" " The inner secret's label is discarded.\n" "\n" " ```gleam\n" " cowl.secret(\"hunter2\")\n" " |> cowl.and_then(fn(pw) { hash_password(pw) |> cowl.secret })\n" " ```\n" ). -spec and_then(secret(DRY), fun((DRY) -> secret(DSA))) -> secret(DSA). and_then(Secret, F) -> Inner = F((erlang:element(2, Secret))()), {secret, erlang:element(2, Inner), erlang:element(3, Secret), erlang:element(4, Inner)}. -file("src/cowl.gleam", 271). ?DOC(" Transform only the label, leaving the value and masker untouched.\n"). -spec map_label(secret(DSD), fun((binary()) -> binary())) -> secret(DSD). map_label(Secret, F) -> {secret, erlang:element(2, Secret), case erlang:element(3, Secret) of {some, L} -> {some, F(L)}; none -> none end, erlang:element(4, Secret)}. -file("src/cowl.gleam", 290). ?DOC( " Run `f` on the masked representation for its side effects; return the\n" " original secret unchanged. Safe to use with logging functions.\n" "\n" " ```gleam\n" " api_key\n" " |> cowl.tap_masked(fn(masked) { logger.info(\"key in use: \" <> masked) })\n" " |> make_request\n" " ```\n" ). -spec tap_masked(secret(DSG), fun((binary()) -> any())) -> secret(DSG). tap_masked(Secret, F) -> _ = F(mask(Secret)), Secret. -file("src/cowl.gleam", 300). ?DOC(" Wrap the `Ok` value of a `Result` as a secret, passing errors through.\n"). -spec from_result({ok, DSK} | {error, DSL}) -> {ok, secret(DSK)} | {error, DSL}. from_result(Res) -> case Res of {ok, V} -> {ok, secret(V)}; {error, E} -> {error, E} end. -file("src/cowl.gleam", 308). ?DOC(" Wrap the `Some` value of an `Option` as a secret, returning `None` unchanged.\n"). -spec from_option(gleam@option:option(DSR)) -> gleam@option:option(secret(DSR)). from_option(Opt) -> case Opt of {some, V} -> {some, secret(V)}; none -> none end. -file("src/cowl.gleam", 316). ?DOC(" Like `from_option`, but also attaches a label.\n"). -spec labeled_from_option(gleam@option:option(DSV), binary()) -> gleam@option:option(secret(DSV)). labeled_from_option(Opt, Label) -> case Opt of {some, V} -> {some, labeled(V, Label)}; none -> none end. -file("src/cowl.gleam", 324). ?DOC(" Like `from_result`, but also attaches a label.\n"). -spec labeled_from_result({ok, DSZ} | {error, DTA}, binary()) -> {ok, secret(DSZ)} | {error, DTA}. labeled_from_result(Res, Label) -> case Res of {ok, V} -> {ok, labeled(V, Label)}; {error, E} -> {error, E} end. -file("src/cowl.gleam", 351). -spec field_with_string(secret(any()), binary()) -> {binary(), binary()}. field_with_string(Secret, Masked) -> Key = case erlang:element(3, Secret) of {some, L} -> L; none -> <<"secret"/utf8>> end, {Key, Masked}. -file("src/cowl.gleam", 339). ?DOC(" Return `#(label, \"***\")` for structured logging. Falls back to `\"secret\"` if unlabeled.\n"). -spec field(secret(any())) -> {binary(), binary()}. field(Secret) -> field_with_string(Secret, mask(Secret)). -file("src/cowl.gleam", 344). ?DOC(" Like `field`, with an explicit masking strategy. Requires `Secret(String)`.\n"). -spec field_with(secret(binary()), strategy()) -> {binary(), binary()}. field_with(Secret, Strategy) -> field_with_string(Secret, mask_with(Secret, Strategy)).