Jadpo is experimental and not ready for production. These docs follow the compiler's main branch as of 30 Oct 2026. What's built · Install 0.1.0
jadpo
Reference: Standard library

Standard library

Web and software

On this page

The web types are the values a web app and its network pass around: URLs and paths, domain names, HTTP statuses and headers, cookies, IP ranges, and the problem documents an API reports failures with. Each follows its standard, and, as every standard type is, each is read only in its canonical text.

Here a shop builds its search links, follows the links on its pages, lets its office's network in, reads the domain a seller types, retries a call the server failed, and reports a sold-out item as RFC 9457 has it:

example
type ShopLink = Url {}type SearchTerm = Text {    min_length: 1    max_length: 100}type CallerAddress = IpAddress {}type FormText = Text {}type YesNo = Bool {}type NotADomain = Failure {    kind: FailureKind.invalid_value}/// A search page's link for a term, on its first page.function search_link(catalogue_page: ShopLink, tea_term: SearchTerm) -> ShopLink {    var none_yet: Map<Text, Text> = []    var tea_query: Map<Text, Text> = map.with(map.with(none_yet, "q", tea_term), "page", "1")    return ShopLink(web.with_query(catalogue_page, tea_query))}/// Where a link on `page` leads.function link_target(page: ShopLink, page_link: RelativeUrl) -> ShopLink {    return ShopLink(web.resolve(page, page_link))}/// The basket's address from any page: a path is a relative URL too.function basket_from(page: ShopLink, basket: UrlPath) -> ShopLink {    return ShopLink(web.resolve(page, basket))}/// Whether a caller is on the office's network.function from_office(office_network: Cidr, caller: CallerAddress) -> YesNo {    return YesNo(web.cidr_contains(office_network, caller))}/// The domain a seller typed for their shop.function shop_domain(raw: FormText) fails NotADomain -> DomainName {    var typed_domain: DomainName = attempt text.parse(raw, as: DomainName) {        malformed: NotADomain    }    return typed_domain}/// Whether a failed call is worth trying again: the server's fault, not/// the request's.function worth_retrying(status: HttpStatus) -> YesNo {    return YesNo(status >= 500)}/// The problem a sold-out item is reported with.function sold_out(item_page: ShopLink) -> ProblemDetails {    return ProblemDetails {        type_: "https://tea.example/problems/sold-out"        status: 409        title: "Sold out"        detail: "Nothing is left of this item."        instance: item_page    }}/// A problem as a person reads it.function problem_shown(problem: ProblemDetails, customer_locale: Locale) -> PresentationText {    return text.format(problem, locale: customer_locale)}/// A problem as it is sent.function problem_sent(problem: ProblemDetails) -> FormText {    return FormText(json.write(problem))}
example
test "links follow RFC 3986" {    var search_page: ShopLink = "https://tea.example/search"    var searched: ShopLink = "https://tea.example/search?page=1&q=tea%20%26%20cake"    assert search_link(search_page, "tea & cake") == searched    var sencha: ShopLink = "https://tea.example/teas/green/sencha"    assert link_target(sencha, "../black/assam") == "https://tea.example/teas/black/assam"    assert link_target(sencha, "#reviews") == "https://tea.example/teas/green/sencha#reviews"    assert basket_from(sencha, "/basket") == "https://tea.example/basket"}test "an address is inside a range of its own family only" {    assert from_office("192.0.2.0/24", "192.0.2.77")    assert not from_office("192.0.2.0/24", "198.51.100.1")    assert from_office("2001:db8::/32", "2001:db8:1::5")    assert not from_office("::/0", "192.0.2.77")}test "a domain name is read in its ASCII form only" {    var typed_domain: DomainName = attempt shop_domain("xn--mnchen-3ya.de")    assert typed_domain == "xn--mnchen-3ya.de"    assert shop_domain("München.de") fails NotADomain    assert shop_domain("example.com.") fails NotADomain}test "a status's class is a comparison, and a problem is sent as RFC 9457 has it" {    assert worth_retrying(503)    assert not worth_retrying(404)    var problem: ProblemDetails = sold_out("https://tea.example/items/assam")    assert problem_shown(problem, Locale.en_gb) == "Sold out\nNothing is left of this item."    var sent: FormText = "{\"type\":\"https://tea.example/problems/sold-out\",\"status\":409,\"title\":\"Sold out\",\"detail\":\"Nothing is left of this item.\",\"instance\":\"https://tea.example/items/assam\"}"    assert problem_sent(problem) == sent}

Web operations

Operation Gives Fails SQL form
web.with_query(url, entries) Url with each of entries (a Map<Text, Text>) added to its query, percent-encoded, in key order none
web.resolve(base, relative) Url: a RelativeUrl or UrlPath resolved against base by RFC 3986 none
web.cidr_contains(range, address) Bool: the IpAddress is inside the Cidr x >>= a
semver.satisfies(version, range) Bool: a SemanticVersion is inside a VersionRange none

web's operations never fail, because each takes values that were checked when they were built.

  • web.with_query's map is called entries, because query is a reserved word. Its entries are added in key order, by code point, after any query the URL already has and before its fragment. An empty map leaves the URL as it was.
  • web.resolve is RFC 3986 §5.2's strict resolution. It normalises nothing but dot segments (. and ..).
  • web.cidr_contains never matches across address families, just as PostgreSQL's >>= doesn't. So an IPv4-mapped IPv6 address isn't inside an IPv4 range.

semver.satisfies is under Software types.

Which standard each type follows

Type Standard
RelativeUrl, UrlPath RFC 3986's relative reference and absolute path
DomainName UTS 46's ASCII form, xn--mnchen-3ya.de
HttpStatus RFC 9110's three digits
ProblemDetails RFC 9457's object
JsonPointer RFC 6901
Cookie RFC 6265's Set-Cookie value
Cidr RFC 4632's range, with its host bits zero
MacAddress IEEE 802's form, AC-DE-48-00-00-80
Imei 3GPP's 15 digits, with their Luhn check digit
Emoji one of Unicode's fully-qualified emoji

Every UrlPath is a RelativeUrl, and every DomainName is a Hostname. So the first of each pair is built on the second (public type UrlPath = RelativeUrl {}), and a value of the first passes where the second is taken: web.resolve's relative takes a path. It never works the other way round.

Types held as text

UrlPath, RelativeUrl, Slug, DomainName, Hostname, WebOrigin, HttpHeaderName, UserAgent, JsonPointer, Cookie, Cidr, MacAddress, Imei, Emoji and PushToken are held as text, as the payment numbers are. Each is a pattern and, where a pattern can't say everything, a named validator. For each of them:

  • only the canonical text is read;
  • none is ordered;
  • text.from is type.mismatch, because each is text already;
  • text.parse fails only as malformed.

Hostname, WebOrigin and Cidr are text, not an enum and objects, because no operation reads their parts and each value has one text. In detail:

  • A RelativeUrl is RFC 3986's relative reference (§4.2), and is never empty. It is a path of any of the RFC's three kinds, a query and a fragment, or an authority after // followed by those. The authority's host is a DomainName or an IP address in its canonical text, IPv6 in brackets, and its port is from 1 to 65535. User information before the host is refused, because §3.2.1 deprecates it. A percent-encoding is in upper case (§2.1).
  • A RelativeUrl is ASCII. An IRI, a URL with characters beyond ASCII (RFC 3987), is percent-encoded first, as encoding.to_url_component does.
  • A UrlPath is RFC 3986's path-absolute, a path starting with /, with no query or fragment.
  • A DomainName is what UTS 46's strict processing gives: IDNA 2008 with STD3's rules, CheckHyphens and the DNS lengths, as Servo's idna 1.1.0 does it on the pinned ICU4X. That means lower case; letters, digits and hyphens; each label beyond ASCII as its A-label (xn--...); no dot at the end; and a last label that isn't all digits.
  • It refuses what IDNA 2008 refuses: -- in the third and fourth places of a label other than an A-label's xn--, as some CDNs' host names have, and _, as DNS records' names (_dmarc) have. localhost, a single label, is a domain name.
  • A domain name is shown as its A-label. Showing the Unicode form safely needs UTS 39's rules for characters that look alike, which aren't decided.
  • A WebOrigin is http or https only. The HTML standard's other tuple origins, ws, wss and ftp, aren't what an Origin header carries from a page. The default port is left out, as the standard writes an origin.
  • An HttpHeaderName is a header name in lower case, as HTTP/2 and HTTP/3 send field names, so == ignores case without doing anything special.
  • A UserAgent is RFC 9110's grammar in ASCII, with one space between products and comments.
  • A JsonPointer is RFC 6901's string form, not its URI fragment form. The empty pointer, which points at the whole document, is one.
  • A Cookie is a server's Set-Cookie value in RFC 6265's strict syntax (§4.1.1), with RFC 6265bis's SameSite and name prefixes. Its attributes come after ; , in the RFC's order and spelling, each at most once. An attribute the RFCs don't define, such as Partitioned or Priority, is refused.
  • A cookie's Max-Age is never 0, as the strict syntax has it. To remove a cookie, give it an Expires in the past, whose weekday must be its date's. SameSite=None needs Secure, and the __Secure- and __Host- prefixes need their rules, compared ignoring case.
  • A Cidr, a range of IP addresses, writes its address as IpAddress would write it canonically, RFC 5952's form for IPv6, with every host bit zero.
  • A MacAddress is IEEE 802's own form, upper-case pairs joined by -, for EUI-48 only.
  • An Imei, a phone's identity, is 15 digits with 3GPP TS 23.003's Luhn check digit. An IMEISV, the form with a software version, isn't one.
  • An Emoji is one of Emoji 18.0's 3,963 fully-qualified emoji, from Unicode 18.0.0's final emoji-test.txt under the Unicode License v3, generated once into jadpo_std::standard::emoji_data. An unqualified or minimally-qualified form is refused, and so is a skin tone on its own.
  • A PushToken is the token a phone gives an app for push notifications: opaque text of the characters Apple's and Google's tokens use, up to 4096 of them. It has no part saying which platform it is for, which would add an enum to the language. A Web Push subscription isn't a token.

Types held as numbers, enums and objects

  • An HttpStatus is a whole number from 100 to 599, RFC 9110 §15's three digits, with its registry open. A NetworkPort is one from 1 to 65535; IANA reserves 0, so it is refused.
  • Each is a type built on Int with its range. So < orders it, and status >= 500 asks for a class of statuses. JSON sends it as a number, an integer column stores it, and text.format shows its digits, never grouped: 8080, not 8,080.
  • text.parse reads only digits within the range, with no sign and no leading zero. Anything else is malformed. A type of your own with a narrower range, such as type HighPort = NetworkPort { min: 1024 }, breaks min or max by name.
  • HttpMethod is the enum that HTTP, External services and Testing use. Its five variants are the methods routes are written with: get, post, put, patch and delete. Its codes are RFC 9110's tokens, such as GET, read ignoring case at a boundary as every standard enum's are, and it is stored by its code.
  • RFC 9110's HEAD, OPTIONS, CONNECT and TRACE aren't variants. The runtime answers HEAD and OPTIONS itself, and no route is written with any of them. Adding them later breaks nothing. A proxy that must name the others holds the method as text.
  • A ProblemDetails is RFC 9457's object for reporting an API failure: its five members, each of which may be none, as the RFC allows. type is a reserved word, so code writes type_, and the wire has type. That is the trailing _ a closed list's code takes when it is a reserved word, used here for a standard object's part (jadpo_std::standard::wire_name).
  • Its type and instance are absolute Urls. The RFC allows relative references; resolve one with web.resolve first. Its title is a line and its detail a paragraph.
  • A member the RFC doesn't define is refused, as any object refuses an unknown field. An application with extension members declares its own type.
  • A problem is sent with its none members left out, and read with missing members as none. It is stored as a column per member. In a field that may be none, a row whose columns are all NULL reads back as none; in a field that can't be, as a problem that says nothing.
  • It has no single text form. text.format shows its title and detail on their own lines; with neither, its status; with no status, its type, or about:blank.
  • A UtmParameters is the campaign tags a marketing link carries: an object of source, medium and campaign, each a line, and term and content, which may be none, as Google Analytics' campaign builder has them. GA4's utm_id and the other later tags aren't parts.
  • Its canonical text is the query that carries them, utm_source=mailing&utm_medium=email&utm_campaign=autumn_sale, in that order, each value percent-encoded as encoding.to_url_component encodes it. Its JSON is its parts, and it is stored as a column per part.

Personal data and shapes

A user agent, a cookie, a MAC address, an IMEI and a push token are online personal data. The other web types aren't personal data.

type.standard_exists matches none of these types. An application's object can't have a field called type, as ProblemDetails does, and UtmParameters' part names are too common to be a sign that a type is one.

Markdown for agents: /docs/web-and-software.md.Built from Jadpo main at a707efb.