-module(sift@string). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sift/string.gleam"). -export([min_length/2, max_length/2, length/2, non_empty/1, matches/2, starts_with/2, ends_with/2, contains/2, email/1, url/1, uuid/1, numeric/1, alpha/1, alphanumeric/1, trimmed/1, one_of/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(" String validators — length, pattern matching, format checks (email, url, uuid).\n"). -file("src/sift/string.gleam", 7). ?DOC(" String must have at least n graphemes\n"). -spec min_length(integer(), EFM) -> fun((binary()) -> {ok, binary()} | {error, EFM}). min_length(N, Msg) -> fun(Value) -> case string:length(Value) >= N of true -> {ok, Value}; false -> {error, Msg} end end. -file("src/sift/string.gleam", 17). ?DOC(" String must have at most n graphemes\n"). -spec max_length(integer(), EFP) -> fun((binary()) -> {ok, binary()} | {error, EFP}). max_length(N, Msg) -> fun(Value) -> case string:length(Value) =< N of true -> {ok, Value}; false -> {error, Msg} end end. -file("src/sift/string.gleam", 27). ?DOC(" String must have exactly n graphemes\n"). -spec length(integer(), EFS) -> fun((binary()) -> {ok, binary()} | {error, EFS}). length(N, Msg) -> fun(Value) -> case string:length(Value) =:= N of true -> {ok, Value}; false -> {error, Msg} end end. -file("src/sift/string.gleam", 37). ?DOC(" String must not be empty (shorthand for min_length(1))\n"). -spec non_empty(EFV) -> fun((binary()) -> {ok, binary()} | {error, EFV}). non_empty(Msg) -> min_length(1, Msg). -file("src/sift/string.gleam", 48). ?DOC( " String must match the given regex pattern.\n" "\n" " ```gleam\n" " let validator = string.matches(\"^[A-Z]{3}$\", \"must be 3 uppercase letters\")\n" " validator(\"ABC\") // -> Ok(\"ABC\")\n" " validator(\"abc\") // -> Error(\"must be 3 uppercase letters\")\n" " ```\n" ). -spec matches(binary(), EFY) -> fun((binary()) -> {ok, binary()} | {error, EFY}). matches(Pattern, Msg) -> fun(Value) -> case gleam@regexp:from_string(Pattern) of {ok, Re} -> case gleam@regexp:check(Re, Value) of true -> {ok, Value}; false -> {error, Msg} end; {error, _} -> {error, Msg} end end. -file("src/sift/string.gleam", 78). ?DOC(" String must start with the given prefix\n"). -spec starts_with(binary(), EGF) -> fun((binary()) -> {ok, binary()} | {error, EGF}). starts_with(Prefix, Msg) -> fun(Value) -> case gleam_stdlib:string_starts_with(Value, Prefix) of true -> {ok, Value}; false -> {error, Msg} end end. -file("src/sift/string.gleam", 88). ?DOC(" String must end with the given suffix\n"). -spec ends_with(binary(), EGI) -> fun((binary()) -> {ok, binary()} | {error, EGI}). ends_with(Suffix, Msg) -> fun(Value) -> case gleam_stdlib:string_ends_with(Value, Suffix) of true -> {ok, Value}; false -> {error, Msg} end end. -file("src/sift/string.gleam", 98). ?DOC(" String must contain the given substring\n"). -spec contains(binary(), EGL) -> fun((binary()) -> {ok, binary()} | {error, EGL}). contains(Substring, Msg) -> fun(Value) -> case gleam_stdlib:contains_string(Value, Substring) of true -> {ok, Value}; false -> {error, Msg} end end. -file("src/sift/string.gleam", 114). ?DOC( " Must look like an email (contains exactly one @, something before and after).\n" "\n" " ```gleam\n" " let validator = string.email(\"invalid email\")\n" " validator(\"jo@example.com\") // -> Ok(\"jo@example.com\")\n" " validator(\"nope\") // -> Error(\"invalid email\")\n" " ```\n" ). -spec email(EGO) -> fun((binary()) -> {ok, binary()} | {error, EGO}). email(Msg) -> matches(<<"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$"/utf8>>, Msg). -file("src/sift/string.gleam", 125). ?DOC( " Must look like a URL (http:// or https://).\n" "\n" " ```gleam\n" " let validator = string.url(\"invalid url\")\n" " validator(\"https://example.com\") // -> Ok(\"https://example.com\")\n" " validator(\"example.com\") // -> Error(\"invalid url\")\n" " ```\n" ). -spec url(EGR) -> fun((binary()) -> {ok, binary()} | {error, EGR}). url(Msg) -> matches(<<"^https?://[^\\s]+$"/utf8>>, Msg). -file("src/sift/string.gleam", 136). ?DOC( " Must be a valid UUID v4 format.\n" "\n" " ```gleam\n" " let validator = string.uuid(\"invalid uuid\")\n" " validator(\"550e8400-e29b-41d4-a716-446655440000\") // -> Ok(..)\n" " validator(\"not-a-uuid\") // -> Error(\"invalid uuid\")\n" " ```\n" ). -spec uuid(EGU) -> fun((binary()) -> {ok, binary()} | {error, EGU}). uuid(Msg) -> matches( <<"^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$"/utf8>>, Msg ). -file("src/sift/string.gleam", 150). ?DOC( " All characters must be digits (0-9).\n" "\n" " ```gleam\n" " let validator = string.numeric(\"digits only\")\n" " validator(\"123\") // -> Ok(\"123\")\n" " validator(\"12a\") // -> Error(\"digits only\")\n" " ```\n" ). -spec numeric(EGX) -> fun((binary()) -> {ok, binary()} | {error, EGX}). numeric(Msg) -> matches(<<"^[0-9]+$"/utf8>>, Msg). -file("src/sift/string.gleam", 161). ?DOC( " All characters must be letters (a-zA-Z).\n" "\n" " ```gleam\n" " let validator = string.alpha(\"letters only\")\n" " validator(\"abc\") // -> Ok(\"abc\")\n" " validator(\"abc1\") // -> Error(\"letters only\")\n" " ```\n" ). -spec alpha(EHA) -> fun((binary()) -> {ok, binary()} | {error, EHA}). alpha(Msg) -> matches(<<"^[a-zA-Z]+$"/utf8>>, Msg). -file("src/sift/string.gleam", 172). ?DOC( " All characters must be letters or digits.\n" "\n" " ```gleam\n" " let validator = string.alphanumeric(\"alphanumeric only\")\n" " validator(\"abc123\") // -> Ok(\"abc123\")\n" " validator(\"abc 123\") // -> Error(\"alphanumeric only\")\n" " ```\n" ). -spec alphanumeric(EHD) -> fun((binary()) -> {ok, binary()} | {error, EHD}). alphanumeric(Msg) -> matches(<<"^[a-zA-Z0-9]+$"/utf8>>, Msg). -file("src/sift/string.gleam", 183). ?DOC( " String must have no leading or trailing whitespace.\n" "\n" " ```gleam\n" " let validator = string.trimmed(\"no surrounding spaces\")\n" " validator(\"hello\") // -> Ok(\"hello\")\n" " validator(\" hello\") // -> Error(\"no surrounding spaces\")\n" " ```\n" ). -spec trimmed(EHG) -> fun((binary()) -> {ok, binary()} | {error, EHG}). trimmed(Msg) -> fun(Value) -> case gleam@string:trim(Value) =:= Value of true -> {ok, Value}; false -> {error, Msg} end end. -file("src/sift/string.gleam", 192). -spec list_contains(list(binary()), binary()) -> boolean(). list_contains(Items, Target) -> case Items of [] -> false; [First | Rest] -> case First =:= Target of true -> true; false -> list_contains(Rest, Target) end end. -file("src/sift/string.gleam", 68). ?DOC( " String must be one of the given values.\n" "\n" " ```gleam\n" " let validator = string.one_of([\"admin\", \"user\", \"guest\"], \"invalid role\")\n" " validator(\"admin\") // -> Ok(\"admin\")\n" " validator(\"root\") // -> Error(\"invalid role\")\n" " ```\n" ). -spec one_of(list(binary()), EGC) -> fun((binary()) -> {ok, binary()} | {error, EGC}). one_of(Values, Msg) -> fun(Value) -> case list_contains(Values, Value) of true -> {ok, Value}; false -> {error, Msg} end end.