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

Phone numbers and postal codes

On this page

A phone number is kept in its international form and a postal code in its country's form, but people type both in their own way. These types read what people type, given the country they typed it in, and show a phone number as the reader's region dials it.

Here a shop takes a customer's phone number and postcode as they type them in their own country, and shows the number to its staff as their region dials it:

example
type TypedText = Text {}type PhoneUnreadable = Failure {    kind: FailureKind.invalid_value}type PostcodeUnreadable = Failure {    kind: FailureKind.invalid_value}/// A customer's phone number as they typed it in their country: national/// or international, with any spaces and brackets.function typed_phone(raw: TypedText, typed_in: Country) fails PhoneUnreadable -> PhoneNumber {    var phone: PhoneNumber = attempt text.parse(raw, as: PhoneNumber, country: typed_in) {        malformed: PhoneUnreadable    }    return phone}/// A customer's postcode as they typed it, in their country's own form.function typed_postcode(raw: TypedText, typed_in: Country) fails PostcodeUnreadable -> PostalCode {    var postcode: PostalCode = attempt text.parse(raw, as: PostalCode, country: typed_in) {        malformed: PostcodeUnreadable    }    return postcode}/// A number as someone in the staff_locale's region dials it.function dialled_as(phone: PhoneNumber, staff_locale: Locale) -> PresentationText {    return text.format(phone, locale: staff_locale)}
example
test "a phone number is read as it is typed and kept in E.164" {    var mobile: PhoneNumber = attempt typed_phone("07400 123456", Country.gb)    assert mobile == PhoneNumber("+447400123456")    var from_abroad: PhoneNumber = attempt typed_phone("+44 (0)7400 123456", Country.fr)    assert from_abroad == mobile    assert typed_phone("07400 12345", Country.gb) fails PhoneUnreadable    assert typed_phone("07400 123456", Country.us) fails PhoneUnreadable}test "a phone number is shown as the staff_locale's region dials it" {    var mobile: PhoneNumber = PhoneNumber("+447400123456")    assert dialled_as(mobile, Locale.en_gb) == "07400 123456"    assert dialled_as(mobile, Locale.fr_fr) == "+44 7400 123456"    assert dialled_as(PhoneNumber("+12015550123"), Locale.en_us) == "(201) 555-0123"}test "a postcode is in its country's form" {    var royal_code: PostalCode = attempt typed_postcode("sw1a1aa", Country.gb)    assert royal_code == PostalCode("SW1A 1AA")    var us_code: PostalCode = attempt typed_postcode("221621010", Country.us)    assert us_code == PostalCode("22162-1010")    assert typed_postcode("75004", Country.gb) fails PostcodeUnreadable    assert typed_postcode("2A004", Country.fr) fails PostcodeUnreadable}

PhoneNumber, CallingCode, PostalCode and UnLocode are held as text, as the payment numbers are. Each is a pattern and, where it needs one, a named validator, and its canonical text is the value. None is ordered.

Phone numbers

A PhoneNumber is its E.164 text, the international form with no spaces: +447400123456. It is valid when libphonenumber's isValidNumber, the stricter of its two checks, says so: the national number matches one of its region's number types (fixed line, mobile, freephone and so on) with that type's lengths, not merely a length the region uses.

Its data is libphonenumber's PhoneNumberMetadata.xml, release v9.0.41 (2026-10-08, Apache-2.0), generated once into jadpo_std::standard::phones_data. A range a regulator opens later is refused until a release brings newer data, as a new currency is. A phone number is contact personal data.

text.parse reads E.164 only, as it reads only canonical text everywhere. To read a number as people write it, pass country:, a Country value, the country it was typed in. Then text.parse reads:

  • a national number with its prefix, 07400 123456;
  • an international number after + or the region's international prefix, 00 44 ... or 011 44 ...;
  • a national prefix written after the country code, +44 (0)7400 ..., which it leaves out;
  • spaces, dashes, dots, slashes and brackets anywhere.

A number valid in another region with the same calling code is taken, such as a Jersey number typed in Britain. An extension belongs in a PhoneExtension, so text with one is malformed. country: on a type other than PhoneNumber and PostalCode is type.temporal_option_unused, as an option that means nothing for a type is.

text.format picks a phone number's format from the locale, with no option to set. A reader whose region has the number's calling code gets the national format, 07400 123456. The region is the locale's, or its likeliest one, as for an address's reader. Any other reader gets the international format, +44 7400 123456. Both use libphonenumber's formats for the calling code's main region.

A CallingCode is a code ITU-T assigned, with its +: +44, +1 for the whole North American plan, +800 and the other non-geographic codes. The list is the one libphonenumber's data holds. It isn't personal data.

Postal codes

A PostalCode on its own is text any country's postal code could be: 1 to 12 capitals, digits, spaces and dashes, with a letter or digit at each end. Its country's rule applies where the country is known: in a PostalAddress, and in text.parse(raw, as: PostalCode, country:).

The patterns are libaddressinput's, from the commit the address formats come from (Apache-2.0), in postal_codes_data. Where a pattern leaves a separator optional but the country's own examples all write it one way, the separator is fixed: a space in SW1A 1AA, a dash in 154-0023 and 22162-1010, none in 75004. So each postal code has one text, and == holds.

With country:, a postal code is read as people type it: in capitals, with one space between words and the separators where the country writes them, so sw1a1aa is SW1A 1AA. Text that could be read two ways is refused. A country libaddressinput has no pattern for, such as Ireland, whose Eircodes it doesn't hold, takes any postal code. A postal code is contact personal data, as an address is.

A PostalAddress's postal code must fit its country. The rule is postal_code_of_country. An object literal never fails (Objects), so the rule is proved while compiling where a literal writes both the postal code and the country (type.invalid_literal). An address built in code from other values is built with attempt. JSON, in a request or through json.read, breaks the rule by name at the address's path. Stored addresses are read as they were written.

UN/LOCODEs

A UnLocode is a UN code for a trade or transport location: five characters with no space, GBLON, its first two a current ISO 3166-1 country code. It is printed with a space, GB LON.

The UN/LOCODE list isn't shipped, so a code isn't checked against it. UNECE's site terms allow only personal, non-commercial use without redistribution, while its footer says UN/CEFACT standards are under CC BY 4.0, and it doesn't say which covers the code list. The datasets/un-locode package records this.

No shapes: type.standard_exists matches none of these four types, because each is a single value.

Markdown for agents: /docs/phone-numbers-and-postal-codes.md.Built from Jadpo main at a707efb.