# Software types

The software types are the values tools and build systems pass around:
version numbers and ranges, git refs, package and container names,
licences, trace ids, file names and paths. Each follows its standard, as
the web types do.

Here a shop's plugin registry reads the version a release form names,
offers a release only to the shops whose range it fits, and tells a newer
release from an older one by SemVer's rules, not by its text:

```jadpo
type FormText = Text {}

type YesNo = Bool {}

type NotAVersion = Failure {
    kind: FailureKind.invalid_value
}

/// The version a plugin's release form names.
function plugin_version(raw: FormText) fails NotAVersion -> SemanticVersion {
    var typed_version: SemanticVersion = attempt text.parse(raw, as: SemanticVersion) {
        malformed: NotAVersion
    }
    return typed_version
}

/// Whether a plugin's release fits the range a shop asks for.
function fits_shop(plugin_release: SemanticVersion, shop_wants: VersionRange) -> YesNo {
    return YesNo(semver.satisfies(plugin_release, shop_wants))
}

/// Whether an offered release is newer than the one installed.
function newer_than(installed_release: SemanticVersion, offered_release: SemanticVersion) -> YesNo {
    return YesNo(offered_release > installed_release)
}

/// Whether a release is offered to every shop: it has no pre-release.
function generally_available(plugin_release: SemanticVersion) -> YesNo {
    return YesNo(plugin_release.pre_release == none)
}

/// A release's version as its tag writes it.
function release_tag(plugin_release: SemanticVersion) -> FormText {
    return FormText(text.from(plugin_release))
}
```

```jadpo
test "versions follow SemVer's precedence, not their text" {
    var nine: SemanticVersion = attempt plugin_version("1.9.0")
    var ten: SemanticVersion = attempt plugin_version("1.10.0")
    var release_candidate: SemanticVersion = attempt plugin_version("1.10.0-rc.1")
    assert newer_than(nine, ten)
    assert newer_than(release_candidate, ten)
    assert not generally_available(release_candidate)
    assert release_tag(release_candidate) == "1.10.0-rc.1"
    assert plugin_version("v1.10.0") fails NotAVersion
}

test "a range takes a pre-release only where it names one" {
    var patch_release: SemanticVersion = attempt plugin_version("1.4.2")
    var beta_release: SemanticVersion = attempt plugin_version("2.0.0-beta.1")
    assert fits_shop(patch_release, "^1.2.3")
    assert not fits_shop(patch_release, "~1.2.3")
    assert not fits_shop(beta_release, "^1.2.3")
    assert not fits_shop(beta_release, ">=1.0.0")
    assert fits_shop(beta_release, ">=2.0.0-beta.0 <3.0.0")
}
```

## Which standard each type follows

| Type | Standard |
|---|---|
| `SemanticVersion` | SemVer 2.0.0, `1.2.3-alpha.1+build.5` |
| `VersionRange` | npm's range grammar, written one way: `^1.2.3`, `>=1.0.0 <2.0.0`, `1.2.x \|\| >=2.5.0-beta.1` |
| `GitRef` | what `git check-ref-format` accepts |
| `PackageUrl` | ECMA-427's canonical `pkg:` URL |
| `Cve` | the CVE programme's id |
| `SpdxLicence` | an SPDX expression over the SPDX licence list |
| `TraceId`, `SpanId`, `TraceParent` | W3C Trace Context |
| `LogLevel` | OpenTelemetry's six severities |
| `FileName`, `FilePath` | names and paths every common file system takes, written with `/` |
| `RegularExpression` | a pattern that `pattern:`'s linear-time engine takes |
| `ContainerImage` | the OCI distribution reference, with its registry and a tag or a digest |
| `Digest` | the OCI image specification |
| `Ulid` | the ULID specification's 26 characters |
| `IdempotencyKey` | what the `Idempotency-Key` header carries |
| `Doi` | the DOI Handbook's name |

None of them is personal data. `type.standard_exists` matches none of
them, so your own `{ major, minor, patch }` type stays your own for now:
adding a shape later would report every such type at once.

## Versions

A `SemanticVersion` is an object of `major`, `minor` and `patch`, each a
whole number from 0 up, and `pre_release` and `build`, each text or
`none`. So code reads its parts: `release.pre_release == none`.

Its canonical text has no `v` before it, and a number too large for an
`Int` is `malformed`. It is sent as that text and shown as its text. It is
stored as a column per part: `bigint` for the numbers, and `text` or
`NULL` for the rest.

`<` is SemVer 2.0.0's precedence (§11), so `1.10.0` comes after `1.9.0`
and every pre-release comes before its release:

- the numbers compare numerically;
- a pre-release is below its release;
- pre-release identifiers compare numerically when they are numbers and
  in ASCII order when they aren't, numbers first.

Build metadata is ignored, as §10 says, so neither of `1.0.0+a` and
`1.0.0+b` is below the other. They still aren't equal, because `==`
compares every part. Sorting and a set's order go by precedence, then by
build metadata as text, so they stay total and agree with `==`.

A query can't order by a version, because its columns don't sort by
precedence.

## Version ranges

A `VersionRange` is the range a package asks for, in npm's range grammar
(node-semver's), because SemVer defines none. It is one or more sets
joined by ` || `. Each set is made of comparisons (`<`, `<=`, `>`, `>=`),
`~`, `^`, partial versions (`1.2`, `1.2.x`) and hyphen ranges
(`1.0.0 - 1.4.2`), with `*` for any version.

A range is read in one spelling only:

- no `v` or `=` before a version;
- no space after an operator, and one space between comparisons;
- the wildcard `x` inside a version, and `*` only on its own;
- no build metadata, which a range can't use.

Each form means what node-semver expands it to: `^0.2.3` is
`>=0.2.3 <0.3.0-0`. A range is held as its text, so two spellings of one
set, such as `1.2` and `1.2.x`, are different values.

`semver.satisfies(version, range)` says whether a version is inside a
range, and never fails. It keeps node-semver's rule for pre-releases,
which Cargo shares: a version with a pre-release is inside a range only
where a comparison in the same set names a pre-release of the same major,
minor and patch. So `^1.2.3` never takes `2.0.0-beta.1`, and `*` takes no
pre-release.

## Types held as text

The other software types are held as text:

- A **`GitRef`** is any name `git check-ref-format --allow-onelevel`
  accepts: a branch (`main`), a tag, a full ref (`refs/heads/main`) or a
  commit's full hash. It isn't normalised with `--normalize`, so a leading
  `/` or a `//` is refused. Its length isn't limited, as git's isn't.
- A **`PackageUrl`** is ECMA-427's canonical form: `pkg:` with no `//`, a
  lower-case type, and each component percent-encoded only where §5.4
  says, in upper-case hex, with none of `A-Z a-z 0-9 . - _ ~ :` encoded.
  Qualifier keys are lower case, sorted, and each appears once with a
  value. A subpath has no `.`, `..` or empty segment. A registered type's
  own rules, such as npm's lower-case names or PyPI's normalisation,
  aren't checked; checking them would refuse more, never accept more.
- A **`Cve`** is `CVE-`, a year from 1999, a `-` and a sequence number of
  four digits, or of five to nineteen digits with no leading zero, in
  upper case. That is the CVE programme's 2014 syntax, with its JSON
  schema's limit of nineteen digits.
- A **`SpdxLicence`** is an SPDX 2.3 expression (Annex D) over the SPDX
  License List 3.29.0, bundled by Embark's `spdx` crate 0.13.6 (CC0 data,
  Apache-2.0 code). It holds each identifier as the list spells it,
  deprecated ones included since they still name a licence; `+`; `WITH`
  an exception on the list; `AND`, `OR` and parentheses; and
  `LicenseRef-` and `DocumentRef-` references. Operators are upper case,
  words are one space apart, and parentheses have no space inside.
  `NONE` and `NOASSERTION` are values a document uses, not expressions,
  and are refused.
- A **`TraceId`** and a **`SpanId`** are W3C Trace Context's ids, in
  lower-case hexadecimal, never all zeros.
- A **`TraceParent`**, the header that carries a trace from one service
  to the next, is text, as `Cidr` is, because no operation reads its
  parts. Only version `00` is read: it is the one version defined, and
  its length is fixed. Its flags are any two hex digits.
- A **`FileName`** is a name every common file system takes:
  - 1 to 255 bytes of UTF-8;
  - no `/`, `\`, NUL or other control character, and none of Windows'
    `< > : " | ? *`;
  - not `.` or `..`, and no space or dot at the end, which Windows drops;
  - not a Windows device name (`CON`, `PRN`, `AUX`, `NUL`, `COM0` to
    `COM9`, `LPT0` to `LPT9`, with `¹²³`), in any case and with any
    extension, so `nul.txt` is refused.
- A **`FilePath`** is written with `/`, as POSIX systems and URLs write
  one, so a Windows path such as `C:\Users` is refused. It is `/` alone,
  or names joined by `/`, absolute after a leading `/`, each a `FileName`.
  It is in its shortest form: no `.` segment, `..` only at the start of a
  relative path, no empty segment, and no `/` at the end. It is at most
  4096 bytes, Linux's `PATH_MAX`. It says nothing about where files are
  kept: the [file stores](files.md) never make a key
  from a name.
- A **`RegularExpression`** is a pattern that a type's `pattern:` takes
  ([Constraints](named-types.md#constraints)), compiled by
  `jadpo_std::compile_pattern` with the `regex` crate. So backreferences
  and lookaround are refused, as they are there. It is matched against
  the whole text, and it isn't empty.
- A **`ContainerImage`** is an OCI distribution reference, written in
  full: a registry first (a domain in lower case with a `.` or a port, an
  IP address, or `localhost`), then the repository's components in lower
  case, then a tag, a digest or both. So `nginx:1.27`, Docker Hub's short
  form of `docker.io/library/nginx:1.27`, is refused, and so is a
  reference with neither a tag nor a digest (an implied `latest`). Each
  image has one text.
- A **`Digest`** follows the OCI image specification's grammar. Its
  registered algorithms are held to their own forms: `sha256` and
  `blake3` are 64 lower-case hex digits, `sha512` 128. An unregistered
  algorithm passes when it meets the grammar, as the specification asks.
- A **`Ulid`** is in upper case only, its first character at most `7`. It
  isn't ordered, as no type held as text is.
- An **`IdempotencyKey`**, the key a client sends so that a repeated
  request isn't carried out twice, is 1 to 255 printable ASCII characters
  with no `"` or `\` and no space at either end. That lets a structured
  header field carry it unescaped. The draft standard recommends a UUID
  but doesn't require one.
- A **`Doi`** is `10.`, a registrant code of four to nine digits with any
  subdivisions, `/`, and a suffix of printable characters, its ASCII
  letters in lower case because the DOI Handbook compares them ignoring
  case. `doi:` and resolver URLs are refused.

## Log levels

A **`LogLevel`** is an enum of OpenTelemetry's six severities: `trace`,
`debug`, `info`, `warn`, `error` and `fatal`. All six are there, so a
later addition doesn't break a `match`. Its codes are their short names,
such as `INFO`, read ignoring case, and it is stored by its code, as
`HttpMethod` is.

A `LogLevel` isn't ordered, because no enum is yet. So when
[Telemetry](telemetry.md) keeps "logs from
`LogLevel.warn`", the runtime reads that order itself.
