Invalid type
pub type Invalid = {code: Str, message: Str} derive Eq, ShowWhy a value was rejected: a stable, machine-readable code (such as too_long or bad_char) and a human-readable message.
Package
Validators that say why: email, URL, UUID, dates, IPv4/IPv6 and CIDR, host names, cards, IBAN, ISBN, semver, JSON and more
0.1.0 MIT
sspur add validate@^0.1.0or in sspur.toml:
validate = { version = "^0.1.0" }Validators for the input formats services check every day, each of which says why a value was rejected. check_x(s) returns ok with the value in a normal form (a lower-cased domain, a canonical UUID, the parsed parts of a URL, the instant of a timestamp, the decoded bytes of base64) or err(Invalid{code, message}), where code is a stable snake_case identifier for programs (too_long, leading_zero, check_digit) and message is a sentence for people. is_x(s) is the same check as a Bool. on_field and errors collect the failures of every field of a record at once. Everything is pure: no effects, no network lookups.
The rules follow the standards (RFC 1123, 3339, 3986, 4291, 4648, 5321/5322, 5952, 8259, SemVer 2.0.0, ISO 13616, ISO 7064, GS1) and each function's doc comment states exactly what it accepts, including the cases where a practical subset was chosen.
use validate.{check_email, check_range, check_url, check_ip, ip_kind, on_field, errors, error_lines}
type Signup = {email: Str, age: Int, site: Str}
fn problems(s: Signup) -> List[Str]
= error_lines(errors([on_field("email", check_email(s.email)), on_field("age", check_range(s.age, 13, 130)), on_field("site", check_url(s.site))]))
fn main() -> Unit ! log
= do
log(problems(Signup{email: "ana@Example.com", age: 34, site: "https://example.com/ana"}).str)
log(problems(Signup{email: "ana@example", age: 7, site: "ftp://example.com"}).str)
log(check_email("Ana.Lopez+news@Example.COM").str)
log(check_url("https://example.com:8443/a?b=1#c").map(u => (u.host, u.port, u.path)).str)
log(check_ip("::ffff:10.0.0.1").map(ip_kind).str)
prints (natively and with --interp):
[]
["email: the domain needs a top-level domain, as in example.com", "age: must be at least 13", "site: the scheme must be http or https"]
ok("Ana.Lopez+news@example.com")
ok(("example.com", some(8443), "/a"))
ok(Private)
Invalid{code, message}: why a value was rejected.FieldError{field, code, message}: a rejection tied to a field name.on_field(name, r): the failure of r as [FieldError], or [].errors(groups): every failure of several fields, in order.error_lines(es): field: message lines.optional(o, check): none passes, some(s) is checked.reason(r): the Invalid of a failed result, or none.with_message(r, msg): replace the message, keep the code.check_length(s, lo, hi): length in characters within bounds.check_range(n, lo, hi): an integer within bounds.check_int(s): a signed decimal integer that fits 64 bits.check_not_blank(s): not only white space (gives the trimmed text).check_one_of(s, options): one of a fixed set.check_matches(s, re): matches a Regex.check_hostname(s), is_hostname(s): RFC 1123 host names (labels of 1 to 63, 253 in all, a trailing dot allowed, xn-- labels).check_domain(s, require_tld), is_domain(s): a host name with a non-numeric top-level label.EmailOpts, email_defaults(), check_email_with(s, o), check_email(s), is_email(s): practical RFC 5321/5322 addresses (dot-atom local parts, optional quoted local parts and address literals).Url, UrlOpts, url_defaults(), check_url_with(s, o), check_url(s), is_url(s): RFC 3986 URLs with an authority, parsed into scheme, userinfo, host, port, path, query and fragment, with a scheme allowlist.check_port(s), is_port(s): 0 to 65535.Ip{family, octets, zone}, Cidr{addr, prefix}, IpKind.check_ipv4(s), is_ipv4(s): dotted decimal, no leading zeros.check_ipv6(s), is_ipv6(s): every RFC 4291 form, embedded IPv4 and zones.check_ip(s), is_ip(s): either family.format_ip(ip): dotted decimal or RFC 5952 canonical text.ip_kind(ip), is_private_ip(ip), is_loopback_ip(ip), is_public_ip(ip): classification from the IANA special-purpose registries.check_cidr(s), check_cidr_strict(s), is_cidr(s): address blocks (strict rejects host bits).cidr_network(c), cidr_last(c), cidr_size(c), in_cidr(ip, c), format_cidr(c).check_uuid(s), is_uuid(s): canonical 8-4-4-4-12 form.check_uuid_loose(s): also braces, urn:uuid: and 32 bare hex digits.uuid_version(s), uuid_variant(s), check_uuid_version(s, v), is_nil_uuid(s), is_max_uuid(s).check_semver(s), is_semver(s): Semantic Versioning 2.0.0.check_mac(s), is_mac(s): colon, hyphen or dot forms.check_slug(s), is_slug(s): lower-case-words.check_date(s), is_date(s): YYYY-MM-DD that exists.check_time(s), is_time(s): HH:MM[:SS[.fff]], as milliseconds after midnight.check_datetime(s), is_datetime(s): ISO 8601 date and time with an optional offset.check_rfc3339(s), is_rfc3339(s): the strict internet profile.luhn_valid(s), luhn_check_digit(s).check_gtin(s), is_ean13(s), is_upca(s): EAN-8, UPC-A, EAN-13, GTIN-14.check_isbn10(s), check_isbn13(s), check_isbn(s), is_isbn(s), isbn10_to_13(s).CardBrand, Card{number, brand}, card_brand(digits), check_card(s), is_card(s): Luhn, network by IIN range, and the network's lengths.iban_length(country), check_iban(s), is_iban(s), format_iban(s): country lengths from the SWIFT registry and the mod 97 check.check_base64(s), is_base64(s), check_base64url(s), is_base64url(s), check_hex(s), is_hex(s): give the decoded bytes.check_hex_color(s), is_hex_color(s): #rgb to #rrggbbaa, expanded to the long form.check_e164(s), is_e164(s): + and 7 to 15 digits.check_json(s), is_json(s): RFC 8259 well-formedness, with the position of the first error.PasswordPolicy, password_defaults(), password_problems(pw, p) (every broken rule), check_password(pw, p).xn-- form (no Punycode conversion), SMTPUTF8 local parts and IRIs are rejected.mailto:, urn:, data:), URL normalization beyond lower-casing the scheme and host, and the WHATWG URL parser's error recovery.127.1, octal, hex), which inet_aton accepts and most validators reject.24:00, and ISO 8601 basic, week and ordinal date forms.check_json checks syntax only.sspur add validate
MIT licensed.
pub type Invalid = {code: Str, message: Str} derive Eq, ShowWhy a value was rejected: a stable, machine-readable code (such as too_long or bad_char) and a human-readable message.
pub type FieldError = {field: Str, code: Str, message: Str} derive Eq, ShowA rejection tied to the name of the field it came from, as on_field builds it.
fn on_field[T](name: Str, r: Res[T, Invalid]) -> List[FieldError]The failure of r, if any, as a list of one FieldError named name; an ok gives []. Concatenate the lists of several fields, or pass them to errors.
fn errors(groups: List[List[FieldError]]) -> List[FieldError]Every failure of several fields, in order: errors([on_field("email", check_email(f.email)), on_field("age", check_range(f.age, 0, 150))]). An empty result means the record is valid.
fn error_lines(es: List[FieldError]) -> List[Str]The failures as field: message lines, for showing to a person.
fn optional[T](o: Opt[Str], check: Str -> Res[T, Invalid]) -> Res[Opt[T], Invalid]Checks an optional value: none is valid and stays none, some(s) is checked with check.
fn reason[T](r: Res[T, Invalid]) -> Opt[Invalid]The reason r failed, or none when it passed.
fn with_message[T](r: Res[T, Invalid], msg: Str) -> Res[T, Invalid]r with its failure message replaced by msg (the code stays), for messages in your own words.
fn check_length(s: Str, lo: Int where _ >= 0, hi: Int) -> Res[Str, Invalid]s when its length in characters is within lo to hi (both inclusive); codes too_short and too_long.
fn check_range(n: Int, lo: Int, hi: Int) -> Res[Int, Invalid]n when it is within lo to hi (both inclusive); codes too_small and too_large.
fn check_int(s: Str) -> Res[Int, Invalid]The integer s spells: an optional + or - and ASCII digits, nothing else (no spaces, no _). Codes empty, not_integer and out_of_range (outside the 64-bit range).
fn check_not_blank(s: Str) -> Res[Str, Invalid]The trimmed s when it has a character other than white space; code blank.
fn check_one_of(s: Str, options: List[Str]) -> Res[Str, Invalid]s when it is one of options (exact, case-sensitive); code not_allowed.
fn check_matches(s: Str, re: Regex) -> Res[Str, Invalid]s when re matches somewhere in it (anchor the pattern with ^ and $ to match the whole text); code no_match.
fn check_hostname(s: Str) -> Res[Str, Invalid]A host name by RFC 1123: dot-separated labels of 1 to 63 ASCII letters, digits and hyphens, not starting or ending with a hyphen, at most 253 characters in all. One trailing dot (the root) is allowed and removed. Internationalized names are accepted in their ASCII (xn--) form only. The result is lower-cased. Codes empty, too_long, empty_label, label_too_long, bad_char, hyphen_edge.
fn is_hostname(s: Str) -> BoolWhether s is a host name (check_hostname).
fn check_domain(s: Str, require_tld: Bool) -> Res[Str, Invalid]A domain name: a host name (check_hostname) whose last label is not all digits, so an IPv4 address is never a domain. With require_tld it needs at least two labels and a top-level label of 2 or more letters or an xn-- label (example.com, not localhost). Codes of check_hostname, and no_tld, bad_tld.
fn is_domain(s: Str) -> BoolWhether s is a domain with a top-level domain (check_domain(s, true)).
pub type Ip = {family: Int, octets: List[Int], zone: Opt[Str]} derive Eq, ShowAn IP address: family 4 or 6, its 4 or 16 octets (0 to 255) in network order, and the zone (scope) of an IPv6 address such as fe80::1%eth0.
pub type Cidr = {addr: Ip, prefix: Int} derive Eq, ShowAn address block: an address and a prefix length (0 to 32 for IPv4, 0 to 128 for IPv6).
pub type IpKind = Unspecified | Loopback | Private | SharedAddress | LinkLocal | Multicast | Broadcast | Documentation | UniqueLocal | Reserved | Global derive Eq, ShowWhat an address is for, from the IANA special-purpose registries. SharedAddress is the carrier-grade NAT block 100.64.0.0/10, UniqueLocal is IPv6 fc00::/7, Reserved covers the remaining special blocks (0.0.0.0/8, 192.0.0.0/24, 198.18.0.0/15, 240.0.0.0/4, ::/8, 100::/64, 2001::/23), and Global is everything else.
fn check_ipv4(s: Str) -> Res[Ip, Invalid]An IPv4 address in dotted-decimal form: exactly four parts from 0 to 255, decimal digits only, no leading zeros (010.0.0.1 is rejected, since other parsers read it as octal), no other shorthand (127.1, hex). Codes ipv4_parts, ipv4_number, leading_zero, ipv4_range.
fn is_ipv4(s: Str) -> BoolWhether s is an IPv4 address (check_ipv4).
fn check_ipv6(s: Str) -> Res[Ip, Invalid]An IPv6 address in any RFC 4291 text form: eight groups of 1 to 4 hex digits, :: once for one or more zero groups, an embedded IPv4 address in the last 32 bits (::ffff:192.0.2.1), and an optional zone after % (letters, digits and -._~, as in fe80::1%eth0). Codes ipv6_group, double_colon, ipv6_groups, zone, and the IPv4 codes for an embedded address.
fn is_ipv6(s: Str) -> BoolWhether s is an IPv6 address (check_ipv6).
fn check_ip(s: Str) -> Res[Ip, Invalid]An IPv4 or IPv6 address (check_ipv4 when s has no :, else check_ipv6).
fn is_ip(s: Str) -> BoolWhether s is an IPv4 or IPv6 address.
fn format_ip(ip: Ip) -> StrThe canonical text of an address: dotted decimal for IPv4, and RFC 5952 for IPv6 (lower-case hex, no leading zeros, the longest run of two or more zero groups as ::, the first on a tie, and ::ffff:a.b.c.d for IPv4-mapped addresses), then %zone if there is one.
fn ip_kind(ip: Ip) -> IpKindWhat ip is for (see IpKind). An IPv4-mapped IPv6 address (::ffff:10.0.0.1) is classified as its IPv4 address.
fn is_private_ip(ip: Ip) -> BoolWhether ip is in a private block: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 or IPv6 unique local fc00::/7.
fn is_loopback_ip(ip: Ip) -> BoolWhether ip is a loopback address (127.0.0.0/8, ::1).
fn is_public_ip(ip: Ip) -> BoolWhether ip is globally routable: not in any special-purpose block.
fn check_cidr(s: Str) -> Res[Cidr, Invalid]A CIDR block such as 192.168.0.0/16 or 2001:db8::/32. The address may have bits set after the prefix (192.168.1.7/16, as in interface configuration); cidr_network clears them. Codes no_prefix, prefix, zone and the address codes.
fn check_cidr_strict(s: Str) -> Res[Cidr, Invalid]A CIDR block whose address is its network address, so 192.168.1.7/16 is rejected with host_bits.
fn is_cidr(s: Str) -> BoolWhether s is a CIDR block (check_cidr).
fn cidr_network(c: Cidr) -> IpThe first address of the block (the address with every bit after the prefix cleared).
fn cidr_last(c: Cidr) -> IpThe last address of the block (every bit after the prefix set; the broadcast address for IPv4).
fn cidr_size(c: Cidr) -> BigIntThe number of addresses in the block, 2 to the power of the bits after the prefix.
fn in_cidr(ip: Ip, c: Cidr) -> BoolWhether ip is inside the block c. Addresses of the other family are never inside, and the zone is ignored.
fn format_cidr(c: Cidr) -> StrThe text of a block, format_ip of its address, / and the prefix length.
pub type EmailOpts = {allow_quoted: Bool, allow_ip_literal: Bool, require_tld: Bool} derive Eq, ShowWhat check_email_with accepts besides plain local@domain.tld addresses.
fn email_defaults() -> EmailOptsThe defaults of check_email: no quoted local parts, no IP literals, and a top-level domain.
fn check_email_with(s: Str, o: EmailOpts) -> Res[Str, Invalid]An email address by the practical subset of RFC 5321 and 5322 that mail systems accept: at most 254 characters; a local part (before the last @) of 1 to 64 characters, a dot-atom of ASCII letters, digits and !#$%&'*+-/=?^_{|}~ with single dots not at either end, or with allow_quoted a quoted string ("john doe"@example.com, with \ escapes); and a domain by check_domain (with require_tld), or with allow_ip_literal an address literal such as [192.0.2.1] or [IPv6:2001:db8::1]. Comments, folding white space, obsolete syntax and non-ASCII (SMTPUTF8) addresses are rejected. The result has the domain lower-cased and the local part unchanged. It says nothing about whether mail can be delivered. Codes too_long, no_at, empty_local, local_too_long, local_char, local_dot, quoted_local, ip_literal`, and the domain and address codes.
fn check_email(s: Str) -> Res[Str, Invalid]check_email_with(s, email_defaults()).
fn is_email(s: Str) -> BoolWhether s is an email address with the defaults (check_email).
fn check_port(s: Str) -> Res[Int, Invalid]A TCP or UDP port number, 0 to 65535, in decimal with no sign and no leading zeros. Codes port.
fn is_port(s: Str) -> BoolWhether s is a port number (check_port).
pub type Url = {scheme: Str, userinfo: Opt[Str], host: Str, port: Opt[Int], path: Str, query: Opt[Str], fragment: Opt[Str]} derive Eq, ShowThe parts of a URL. host is lower-cased, and an IPv6 host is written without its brackets; path is "" when the URL has none; query and fragment exclude the ? and #.
pub type UrlOpts = {schemes: List[Str], allow_userinfo: Bool, require_tld: Bool, allow_ip: Bool} derive Eq, ShowWhat check_url_with accepts: schemes (lower case; [] allows any scheme), credentials before the host, single-label hosts such as localhost (when require_tld is false), and IP address hosts.
fn url_defaults() -> UrlOptsThe defaults of check_url: http and https only, no credentials, a domain with a top-level domain or an IP address.
fn check_url_with(s: Str, o: UrlOpts) -> Res[Url, Invalid]A URL with an authority, scheme://[userinfo@]host[:port][/path][?query][#fragment], by RFC 3986: the scheme is a letter then letters, digits, +, - or .; the host a domain (check_domain), an IPv4 address or a bracketed IPv6 address without a zone; the port 0 to 65535; path, query and fragment only RFC 3986 characters with valid %XX escapes. Spaces, control characters and non-ASCII text (IRIs) are rejected, and so are URLs without // (mailto:, urn:). The result is the parsed Url. Codes empty, bad_char, no_scheme, scheme, scheme_not_allowed, no_authority, userinfo, empty_host, host, ip_host, zone, port, path, query, fragment, and the host codes.
fn check_url(s: Str) -> Res[Url, Invalid]check_url_with(s, url_defaults()).
fn is_url(s: Str) -> BoolWhether s is an http or https URL with the defaults (check_url).
fn check_uuid(s: Str) -> Res[Str, Invalid]A UUID in the canonical 8-4-4-4-12 hex form, either case, of any version and variant (including the nil and max UUIDs). The result is lower case. Code uuid.
fn check_uuid_loose(s: Str) -> Res[Str, Invalid]A UUID in the canonical form or one of the other common forms: in braces ({...}), with a urn:uuid: prefix (any case), or as 32 hex digits without hyphens. The result is the canonical lower-case form. Code uuid.
fn is_uuid(s: Str) -> BoolWhether s is a canonical UUID (check_uuid).
fn uuid_version(s: Str) -> Opt[Int]The version of a UUID in any form check_uuid_loose accepts: the hex digit that starts the third group (4 for random, 7 for time-ordered, 0 for nil, 15 for max).
fn uuid_variant(s: Str) -> Opt[Str]The variant of a UUID, from the first hex digit of its fourth group: ncs (0 to 7), rfc9562 (8 to b, the RFC 4122 and 9562 layout), microsoft (c, d) or future (e, f).
fn check_uuid_version(s: Str, v: Int where _ >= 1 and _ <= 8) -> Res[Str, Invalid]A canonical UUID of version v with the RFC 9562 variant, as generators of that version write it. Codes uuid, uuid_version, uuid_variant.
fn is_nil_uuid(s: Str) -> BoolWhether s is the nil UUID, all zeros (any form check_uuid_loose accepts).
fn is_max_uuid(s: Str) -> BoolWhether s is the max UUID, all f (any form check_uuid_loose accepts).
fn check_date(s: Str) -> Res[Time, Invalid]A calendar date in ISO 8601 extended form, YYYY-MM-DD, that exists (2024-02-29 but not 2023-02-29); the result is midnight UTC of that day. Years are 0000 to 9999; basic (20240229), week and ordinal forms are rejected. Codes date_form, month, day.
fn is_date(s: Str) -> BoolWhether s is an ISO 8601 date (check_date).
fn check_time(s: Str) -> Res[Int, Invalid]A time of day, HH:MM, HH:MM:SS or HH:MM:SS.fff (any number of fraction digits, kept to the millisecond), hours 00 to 23; the result is milliseconds after midnight. 24:00 and leap seconds are rejected. Codes time_form, hour, minute, second.
fn is_time(s: Str) -> BoolWhether s is a time of day (check_time).
fn check_datetime(s: Str) -> Res[Time, Invalid]An ISO 8601 date and time: a date, T (or t, or a space), a time as check_time reads it, and an optional offset (Z, +HH:MM, +HHMM or +HH); a time without an offset is read as UTC. The result is the instant. Codes of check_date and check_time, datetime_form and offset.
fn is_datetime(s: Str) -> BoolWhether s is an ISO 8601 date and time (check_datetime).
fn check_rfc3339(s: Str) -> Res[Time, Invalid]An RFC 3339 timestamp, the strict profile of ISO 8601 used on the internet: T (or t) between date and time, seconds required, and an offset required (Z, z or +HH:MM), as in 2026-10-16T09:30:00.250+05:30. The result is the instant. Codes as check_datetime.
fn is_rfc3339(s: Str) -> BoolWhether s is an RFC 3339 timestamp (check_rfc3339).
fn check_mac(s: Str) -> Res[Str, Invalid]A 48-bit MAC address as six pairs of hex digits separated by : or - (one kind throughout), or as three groups of four separated by . (0123.4567.89ab). The result is lower case with colons, 01:23:45:67:89:ab. Code mac.
fn is_mac(s: Str) -> BoolWhether s is a MAC address (check_mac).
fn luhn_valid(s: Str) -> BoolWhether s is two or more ASCII digits whose Luhn (mod 10) checksum is valid, as on payment cards and IMEI numbers.
fn luhn_check_digit(s: Str) -> Opt[Int]The Luhn check digit to append to the digits s, or none unless s is one or more digits.
fn check_gtin(s: Str) -> Res[Str, Invalid]A GTIN: an EAN-8, UPC-A (12 digits), EAN-13 or GTIN-14 number with a valid check digit. Digits only, no separators. Codes gtin_form, check_digit.
fn is_ean13(s: Str) -> BoolWhether s is an EAN-13 number with a valid check digit.
fn is_upca(s: Str) -> BoolWhether s is a UPC-A number (12 digits) with a valid check digit.
fn check_isbn10(s: Str) -> Res[Str, Invalid]An ISBN-10: nine digits and a check digit or X, with optional hyphens or spaces between them. The result is the ten characters without separators (X upper case). Codes isbn_form, check_digit.
fn check_isbn13(s: Str) -> Res[Str, Invalid]An ISBN-13: 13 digits starting 978 or 979 with a valid check digit, with optional hyphens or spaces. The result has no separators. Codes isbn_form, check_digit.
fn check_isbn(s: Str) -> Res[Str, Invalid]An ISBN-10 or ISBN-13 (by the number of digits), without separators in the result.
fn is_isbn(s: Str) -> BoolWhether s is an ISBN-10 or ISBN-13 (check_isbn).
fn isbn10_to_13(s: Str) -> Opt[Str]The ISBN-13 of a valid ISBN-10 (978, its first nine digits and a new check digit).
pub type CardBrand = Visa | Mastercard | Amex | Discover | DinersClub | Jcb | UnionPay | Maestro | Mir | UnknownBrand derive Eq, ShowA card network, from the number's leading digits (IIN ranges).
pub type Card = {number: Str, brand: CardBrand} derive Eq, ShowA checked card number (digits only) and its network.
fn card_brand(digits: Str) -> CardBrandThe network of a card number (digits only) by its leading digits: Visa 4; Mastercard 51 to 55 and 2221 to 2720; American Express 34, 37; Discover 6011, 644 to 649, 65, 622126 to 622925; UnionPay other 62; Diners Club 300 to 305, 36, 38, 39; JCB 3528 to 3589; Maestro 50, 56 to 69; Mir 2200 to 2204; otherwise UnknownBrand.
fn check_card(s: Str) -> Res[Card, Invalid]A payment card number: digits with optional spaces or hyphens, 12 to 19 digits, a valid Luhn checksum, and a length its network uses (16 for Mastercard, 15 for American Express, 13, 16 or 19 for Visa). Numbers of unknown networks are accepted with UnknownBrand. Codes card_form, card_length, check_digit.
fn is_card(s: Str) -> BoolWhether s is a payment card number (check_card).
fn iban_length(country: Str) -> Opt[Int]The IBAN length of a country (ISO 3166 alpha-2, upper case) from the SWIFT IBAN registry, or none for a country without IBANs.
fn check_iban(s: Str) -> Res[Str, Invalid]An IBAN: a country code, two check digits and the account number (BBAN) of the length the registry gives for that country, upper-case letters and digits, with the ISO 7064 mod 97 checksum equal to 1. Spaces are ignored and letters may be lower case. The result is the electronic form, upper case without spaces. Codes iban_form, iban_country, iban_length, check_digit.
fn is_iban(s: Str) -> BoolWhether s is an IBAN (check_iban).
fn format_iban(s: Str) -> Res[Str, Invalid]The print form of a valid IBAN: groups of four characters separated by spaces.
fn check_semver(s: Str) -> Res[Str, Invalid]A Semantic Versioning 2.0.0 version, exactly as the specification's grammar: MAJOR.MINOR.PATCH without leading zeros, an optional - and dot-separated pre-release identifiers (ASCII letters, digits and hyphens; numeric ones without leading zeros), and an optional + and build identifiers (the same characters, leading zeros allowed). No v prefix. Codes semver, prerelease, build.
fn is_semver(s: Str) -> BoolWhether s is a Semantic Versioning 2.0.0 version (check_semver).
fn check_hex_color(s: Str) -> Res[Str, Invalid]A CSS hex color: # and 3, 4, 6 or 8 hex digits (#rgb, #rgba, #rrggbb, #rrggbbaa). The result is the long lower-case form, #rrggbb or #rrggbbaa. Code hex_color.
fn is_hex_color(s: Str) -> BoolWhether s is a CSS hex color (check_hex_color).
fn check_e164(s: Str) -> Res[Str, Invalid]A phone number in E.164 form: +, then 7 to 15 digits starting with 1 to 9 (the country code first), with no spaces or punctuation. Whether the number exists is out of scope. Code e164.
fn is_e164(s: Str) -> BoolWhether s is an E.164 phone number (check_e164).
fn check_base64(s: Str) -> Res[Bytes, Invalid]Standard base64 (RFC 4648 section 4: A-Z a-z 0-9 + /) with the = padding that makes the length a multiple of 4, and unused bits zero; "" is the empty value. The result is the decoded bytes. Codes base64.
fn check_base64url(s: Str) -> Res[Bytes, Invalid]URL-safe base64 (RFC 4648 section 5: - and _ in place of + and /), with or without padding. The result is the decoded bytes. Code base64.
fn is_base64(s: Str) -> BoolWhether s is standard base64 (check_base64).
fn is_base64url(s: Str) -> BoolWhether s is URL-safe base64 (check_base64url).
fn check_hex(s: Str) -> Res[Bytes, Invalid]Hex-encoded bytes: an even number of hex digits in either case; "" is the empty value. The result is the decoded bytes. Code hex.
fn is_hex(s: Str) -> BoolWhether s is hex-encoded bytes (check_hex).
fn check_slug(s: Str) -> Res[Str, Invalid]A URL slug: lower-case ASCII letters and digits in groups joined by single hyphens, as in my-first-post. Code slug.
fn is_slug(s: Str) -> BoolWhether s is a URL slug (check_slug).
fn check_json(s: Str) -> Res[Unit, Invalid]Whether s is one well-formed JSON text by RFC 8259: any value at the top, white space around it, strings with valid escapes and no raw control characters, numbers in JSON's grammar (no leading zeros, +, NaN or hex), and nesting at most 512 deep. Duplicate member names are allowed, as the RFC allows. On failure the message names the character position. Code json.
fn is_json(s: Str) -> BoolWhether s is well-formed JSON (check_json).
pub type PasswordPolicy = {min_len: Int, max_len: Int, need_lower: Bool, need_upper: Bool, need_digit: Bool, need_symbol: Bool, allow_spaces: Bool, max_run: Int, banned: List[Str]} derive Eq, ShowA password policy. max_run limits how often one character may repeat in a row (0 for no limit), and banned lists passwords that are refused whatever their shape (compared ignoring case), such as a list of common passwords.
fn password_defaults() -> PasswordPolicyA common policy: 12 to 128 characters, a lower-case and an upper-case letter, a digit and a symbol, spaces allowed, at most 3 equal characters in a row, nothing banned. NIST SP 800-63B recommends length and a banned list over composition rules: set the need_* fields to false and fill banned to follow it.
fn password_problems(pw: Str, p: PasswordPolicy) -> List[Invalid]Every rule of p that pw breaks, in the order too_short, too_long, need_lower, need_upper, need_digit, need_symbol, spaces, repeated, banned; [] means it passes. Letters are Unicode letters with case (é is lower case), digits are ASCII digits, and a symbol is any other non-space character.
fn check_password(pw: Str, p: PasswordPolicy) -> Res[Str, Invalid]pw when it passes p, else the first rule it breaks (password_problems lists them all).
| Version | Published | Commit | Source | Effects |
|---|---|---|---|---|
| 0.1.0 | 2026-10-16 | a80d58411a | snapshot |