Package

validate

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

Install

sspur add validate@^0.1.0

or in sspur.toml:

validate = { version = "^0.1.0" }

README

validate

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.

Example

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)

API

Results and records

  • 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.

General

  • 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.

Host names, email, URLs, ports

  • 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 addresses

  • 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).

Identifiers

  • 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.

Dates and times

  • 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.

Check digits, cards and bank accounts

  • 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.

Encodings, colors, phones, JSON

  • 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.

Passwords

  • PasswordPolicy, password_defaults(), password_problems(pw, p) (every broken rule), check_password(pw, p).

Out of scope

  • Deliverability and existence: no DNS or MX lookups, no SMTP checks, no checking that a phone number, card or IBAN account exists (email-validator's network checks, libphonenumber's numbering plans).
  • Internationalized input in Unicode form: IDN host names must be in their xn-- form (no Punycode conversion), SMTPUTF8 local parts and IRIs are rejected.
  • URLs without an authority (mailto:, urn:, data:), URL normalization beyond lower-casing the scheme and host, and the WHATWG URL parser's error recovery.
  • IPv4 shorthand forms (127.1, octal, hex), which inet_aton accepts and most validators reject.
  • Leap seconds, 24:00, and ISO 8601 basic, week and ordinal date forms.
  • Per-country postal codes, tax and national identity numbers, VAT numbers and BIC/SWIFT codes.
  • JSON schema validation; check_json checks syntax only.
  • Password strength estimation by entropy or dictionaries (zxcvbn); the policy check is rule based.

Install

sspur add validate

MIT licensed.

API

Invalid type

pub type Invalid = {code: Str, message: Str} derive Eq, Show

Why a value was rejected: a stable, machine-readable code (such as too_long or bad_char) and a human-readable message.

FieldError type

pub type FieldError = {field: Str, code: Str, message: Str} derive Eq, Show

A rejection tied to the name of the field it came from, as on_field builds it.

on_field fn

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.

errors fn

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.

error_lines fn

fn error_lines(es: List[FieldError]) -> List[Str]

The failures as field: message lines, for showing to a person.

optional fn

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.

reason fn

fn reason[T](r: Res[T, Invalid]) -> Opt[Invalid]

The reason r failed, or none when it passed.

with_message fn

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.

check_length fn

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.

check_range fn

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.

check_int fn

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).

check_not_blank fn

fn check_not_blank(s: Str) -> Res[Str, Invalid]

The trimmed s when it has a character other than white space; code blank.

check_one_of fn

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.

check_matches fn

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.

check_hostname fn

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.

is_hostname fn

fn is_hostname(s: Str) -> Bool

Whether s is a host name (check_hostname).

check_domain fn

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.

is_domain fn

fn is_domain(s: Str) -> Bool

Whether s is a domain with a top-level domain (check_domain(s, true)).

Ip type

pub type Ip = {family: Int, octets: List[Int], zone: Opt[Str]} derive Eq, Show

An 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.

Cidr type

pub type Cidr = {addr: Ip, prefix: Int} derive Eq, Show

An address block: an address and a prefix length (0 to 32 for IPv4, 0 to 128 for IPv6).

IpKind type

pub type IpKind = Unspecified | Loopback | Private | SharedAddress | LinkLocal | Multicast | Broadcast | Documentation | UniqueLocal | Reserved | Global derive Eq, Show

What 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.

check_ipv4 fn

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.

is_ipv4 fn

fn is_ipv4(s: Str) -> Bool

Whether s is an IPv4 address (check_ipv4).

check_ipv6 fn

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.

is_ipv6 fn

fn is_ipv6(s: Str) -> Bool

Whether s is an IPv6 address (check_ipv6).

check_ip fn

fn check_ip(s: Str) -> Res[Ip, Invalid]

An IPv4 or IPv6 address (check_ipv4 when s has no :, else check_ipv6).

is_ip fn

fn is_ip(s: Str) -> Bool

Whether s is an IPv4 or IPv6 address.

format_ip fn

fn format_ip(ip: Ip) -> Str

The 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.

ip_kind fn

fn ip_kind(ip: Ip) -> IpKind

What ip is for (see IpKind). An IPv4-mapped IPv6 address (::ffff:10.0.0.1) is classified as its IPv4 address.

is_private_ip fn

fn is_private_ip(ip: Ip) -> Bool

Whether 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.

is_loopback_ip fn

fn is_loopback_ip(ip: Ip) -> Bool

Whether ip is a loopback address (127.0.0.0/8, ::1).

is_public_ip fn

fn is_public_ip(ip: Ip) -> Bool

Whether ip is globally routable: not in any special-purpose block.

check_cidr fn

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.

check_cidr_strict fn

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.

is_cidr fn

fn is_cidr(s: Str) -> Bool

Whether s is a CIDR block (check_cidr).

cidr_network fn

fn cidr_network(c: Cidr) -> Ip

The first address of the block (the address with every bit after the prefix cleared).

cidr_last fn

fn cidr_last(c: Cidr) -> Ip

The last address of the block (every bit after the prefix set; the broadcast address for IPv4).

cidr_size fn

fn cidr_size(c: Cidr) -> BigInt

The number of addresses in the block, 2 to the power of the bits after the prefix.

in_cidr fn

fn in_cidr(ip: Ip, c: Cidr) -> Bool

Whether ip is inside the block c. Addresses of the other family are never inside, and the zone is ignored.

format_cidr fn

fn format_cidr(c: Cidr) -> Str

The text of a block, format_ip of its address, / and the prefix length.

EmailOpts type

pub type EmailOpts = {allow_quoted: Bool, allow_ip_literal: Bool, require_tld: Bool} derive Eq, Show

What check_email_with accepts besides plain local@domain.tld addresses.

email_defaults fn

fn email_defaults() -> EmailOpts

The defaults of check_email: no quoted local parts, no IP literals, and a top-level domain.

check_email_with fn

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.

check_email fn

fn check_email(s: Str) -> Res[Str, Invalid]

check_email_with(s, email_defaults()).

is_email fn

fn is_email(s: Str) -> Bool

Whether s is an email address with the defaults (check_email).

check_port fn

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.

is_port fn

fn is_port(s: Str) -> Bool

Whether s is a port number (check_port).

Url type

pub type Url = {scheme: Str, userinfo: Opt[Str], host: Str, port: Opt[Int], path: Str, query: Opt[Str], fragment: Opt[Str]} derive Eq, Show

The 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 #.

UrlOpts type

pub type UrlOpts = {schemes: List[Str], allow_userinfo: Bool, require_tld: Bool, allow_ip: Bool} derive Eq, Show

What 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.

url_defaults fn

fn url_defaults() -> UrlOpts

The defaults of check_url: http and https only, no credentials, a domain with a top-level domain or an IP address.

check_url_with fn

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.

check_url fn

fn check_url(s: Str) -> Res[Url, Invalid]

check_url_with(s, url_defaults()).

is_url fn

fn is_url(s: Str) -> Bool

Whether s is an http or https URL with the defaults (check_url).

check_uuid fn

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.

check_uuid_loose fn

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.

is_uuid fn

fn is_uuid(s: Str) -> Bool

Whether s is a canonical UUID (check_uuid).

uuid_version fn

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).

uuid_variant fn

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).

check_uuid_version fn

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.

is_nil_uuid fn

fn is_nil_uuid(s: Str) -> Bool

Whether s is the nil UUID, all zeros (any form check_uuid_loose accepts).

is_max_uuid fn

fn is_max_uuid(s: Str) -> Bool

Whether s is the max UUID, all f (any form check_uuid_loose accepts).

check_date fn

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.

is_date fn

fn is_date(s: Str) -> Bool

Whether s is an ISO 8601 date (check_date).

check_time fn

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.

is_time fn

fn is_time(s: Str) -> Bool

Whether s is a time of day (check_time).

check_datetime fn

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.

is_datetime fn

fn is_datetime(s: Str) -> Bool

Whether s is an ISO 8601 date and time (check_datetime).

check_rfc3339 fn

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.

is_rfc3339 fn

fn is_rfc3339(s: Str) -> Bool

Whether s is an RFC 3339 timestamp (check_rfc3339).

check_mac fn

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.

is_mac fn

fn is_mac(s: Str) -> Bool

Whether s is a MAC address (check_mac).

luhn_valid fn

fn luhn_valid(s: Str) -> Bool

Whether s is two or more ASCII digits whose Luhn (mod 10) checksum is valid, as on payment cards and IMEI numbers.

luhn_check_digit fn

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.

check_gtin fn

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.

is_ean13 fn

fn is_ean13(s: Str) -> Bool

Whether s is an EAN-13 number with a valid check digit.

is_upca fn

fn is_upca(s: Str) -> Bool

Whether s is a UPC-A number (12 digits) with a valid check digit.

check_isbn10 fn

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.

check_isbn13 fn

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.

check_isbn fn

fn check_isbn(s: Str) -> Res[Str, Invalid]

An ISBN-10 or ISBN-13 (by the number of digits), without separators in the result.

is_isbn fn

fn is_isbn(s: Str) -> Bool

Whether s is an ISBN-10 or ISBN-13 (check_isbn).

isbn10_to_13 fn

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).

CardBrand type

pub type CardBrand = Visa | Mastercard | Amex | Discover | DinersClub | Jcb | UnionPay | Maestro | Mir | UnknownBrand derive Eq, Show

A card network, from the number's leading digits (IIN ranges).

Card type

pub type Card = {number: Str, brand: CardBrand} derive Eq, Show

A checked card number (digits only) and its network.

card_brand fn

fn card_brand(digits: Str) -> CardBrand

The 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.

check_card fn

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.

is_card fn

fn is_card(s: Str) -> Bool

Whether s is a payment card number (check_card).

iban_length fn

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.

check_iban fn

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.

is_iban fn

fn is_iban(s: Str) -> Bool

Whether s is an IBAN (check_iban).

format_iban fn

fn format_iban(s: Str) -> Res[Str, Invalid]

The print form of a valid IBAN: groups of four characters separated by spaces.

check_semver fn

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.

is_semver fn

fn is_semver(s: Str) -> Bool

Whether s is a Semantic Versioning 2.0.0 version (check_semver).

check_hex_color fn

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.

is_hex_color fn

fn is_hex_color(s: Str) -> Bool

Whether s is a CSS hex color (check_hex_color).

check_e164 fn

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.

is_e164 fn

fn is_e164(s: Str) -> Bool

Whether s is an E.164 phone number (check_e164).

check_base64 fn

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.

check_base64url fn

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.

is_base64 fn

fn is_base64(s: Str) -> Bool

Whether s is standard base64 (check_base64).

is_base64url fn

fn is_base64url(s: Str) -> Bool

Whether s is URL-safe base64 (check_base64url).

check_hex fn

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.

is_hex fn

fn is_hex(s: Str) -> Bool

Whether s is hex-encoded bytes (check_hex).

check_slug fn

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.

is_slug fn

fn is_slug(s: Str) -> Bool

Whether s is a URL slug (check_slug).

check_json fn

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.

is_json fn

fn is_json(s: Str) -> Bool

Whether s is well-formed JSON (check_json).

PasswordPolicy type

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, Show

A 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.

password_defaults fn

fn password_defaults() -> PasswordPolicy

A 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.

password_problems fn

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.

check_password fn

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).

Versions

VersionPublishedCommitSourceEffects
0.1.0 2026-10-16a80d58411asnapshot