Skip to main content

Module iri

Module iri 

Source
Expand description

IRI validity — what may appear between the angle brackets of an N-Triples / N-Quads IRIREF, and the one lossy repair the exporter offers.

rete’s line-based N-Triples/N-Quads reader is deliberately tolerant: it takes whatever sits between < and the next > and stores it as a dictionary key. That makes the file a faithful container of what it was given, but it also means a .rete can hold IRIs that no strict parser accepts — and rete export --format nq then names a grammar it does not emit. Oxigraph, Jena and rapper reject such a dump; a bulk loader rejects the chunk, not the line, so one bad IRI can cost a hundred thousand good statements.

This module is the single definition of “invalid” used by the build-time audit (rete build, rete validate) and the export-time repair (rete export --sanitize-iris).

§Validity is decided by a parser; the classes only say how to repair

Two documents apply, and both must hold:

  • the N-Triples/N-Quads IRIREF production ('<' ([^#x00-#x20<>"{}|^] | UCHAR)* ‘>’`), which excludes a fixed set of characters outright, and
  • RFC 3987, which the same grammar requires the content to satisfy as an absolute IRI (with an optional fragment).

The first is a character set, so a one-pass scan settles it. The second is a grammar, and it is not settled by enumerating bad shapes: a taxonomy of known defects can only recognise what has already been seen, so making it the arbiter of validity guarantees that the next unseen shape is published as valid. That is not hypothetical — <https://::1> (an IPv6 literal in the authority without the brackets 3987 requires) matched none of the five classes, and a dump carrying it passed the export gate and was rejected by Oxigraph.

So the two questions are separated:

  • Is it valid? oxiri answers, and nothing else does. That is the crate Oxigraph’s own N-Triples reader validates with — oxttl’s lexer tokenises <…> per the IRIREF production and hands the content to oxiri::Iri::parse — so rete’s verdict agrees with the reference loader’s by construction rather than by our keeping a list up to date. oxiri is already in this crate’s dependency tree via oxrdf/oxttl, so deciding it properly costs no new dependency and no new wasm surface.
  • Can we repair it, and how? The classes below answer, and only that. Each names a shape percent-encoding can fix without inventing information.
classexamplerepairable by escaping
IriDefect::NotAbsolute<noscheme/path>no
IriDefect::ForbiddenChar<http://ex/a b>, <http://ex/a"b>yes
IriDefect::Bracket<http://ex/a[b]>yes
IriDefect::ExtraHash<http://ex/c#d#e>yes
IriDefect::BadPercent<http://ex/%x>yes
IriDefect::Unclassified<https://::1>no

IriDefect::Unclassified is the load-bearing one: an IRI the parser rejects that no class recognises. It is still counted and it is still unrepairable, so it still blocks. A gap in the taxonomy is now benign — we failed to repair something we might have — instead of harmful, which is a published dump that does not load. A class that is added later only moves occurrences out of this bucket; it never changes a verdict from invalid to valid.

A repairable class is only reported when escaping actually lands a valid IRI: <https://::1/a[b]> has a bracket, but encoding it leaves an IRI the parser still rejects, so it is reported as IriDefect::Unclassified rather than as a Bracket the sanitizer would claim to have fixed.

§UCHAR escapes are syntax, not content

<http://ex/café> is a legal IRIREF whose IRI is http://ex/café — the backslash belongs to N-Triples, not to the IRI. The escapes are therefore resolved before the parser sees the string, exactly as oxttl does. Handing the raw bracket content to an RFC 3987 parser would reject \ and make every escaped dump a false positive.

§What this deliberately does not judge

  • Scheme semantics. <nonsense://x> is syntactically a fine IRI and is accepted. Whether the scheme is registered, resolvable or meaningful is not this module’s question.
  • Whether the IRI names anything. A 404, a typo’d host and a dead DOI are all valid IRIs.
  • Normalisation. <HTTP://EX/a/../b> is valid and is left exactly as it was given; rete never case-folds a scheme or removes a dot segment, because both change the dictionary key and break the round-trip.
  • Anything escaping cannot fix. A relative IRI (IriDefect::NotAbsolute) and an IriDefect::Unclassified one are reported and left alone — sanitize_iri_content returns None and the term is emitted verbatim. A dump containing one is still not valid N-Quads, and --sanitize-iris says so rather than implying a fix it did not make.

Non-ASCII used to be on this list and no longer is. RFC 3987 admits ucschar, so <http://ex/café> is valid — but the narrow sub-ranges 3987 excludes (surrogates, iprivate outside the query) were previously not policed at all. The parser polices them now, because it is the same parser the loader uses: if it accepts a character, so does the dump’s reader.

Structs§

IriReport
A bounded tally of invalid IRIs — the shape both the build-time audit and the export-time sanitizer report.

Enums§

IriDefect
Why an IRI is not one — a repair classification, not the validity verdict. iri_content_defect decides validity with an RFC 3987 parser and uses these only to say what, if anything, can be done about it.

Constants§

DEFECT_CLASSES
Number of IriDefect classes — the width of a report’s counter array.

Functions§

iri_content_defect
Classify the content of an IRIREF — what sits between < and > — returning None when it is a valid absolute IRI, and otherwise the class that says what can be done about it.
sanitize_iri_content
Percent-encode the offending characters of an IRIREF’s content.
sanitize_term
Percent-encode every repairable IRI inside a canonical term token, returning None when nothing changed. The token-level counterpart of sanitize_iri_content; it rebuilds quoted triples and literal datatypes around the repaired IRI so the result is still a canonical token.
term_defect
Classify a canonical N-Triples term token — <iri>, _:b, "lit", "lit"^^<dt>, "lit"@en, or an RDF-star <<s p o>> — returning the first IRI defect it carries, or None.
term_defects
Every defect a term carries, not only the first.