Tolerance & diagnostics

Real-world IIIF is imperfect. iiisight’s core promise is that normalization never raises on a spec-imperfect document — it repairs what it can and records each repair as a Diagnostic. (This is distinct from transport failures like a 404 or non-JSON body, which do raise FetchError.)

Diagnostics

Every normalized document carries a diagnostics tuple:

manifest = iiisight.normalize(document)
for d in manifest.diagnostics:
    print(d.severity, d.code, d.path, d.message, d.recovered)

Each Diagnostic has:

  • code — a stable machine code (e.g. "coerced-language-map", "upgraded-v2-to-v3").

  • severity — a Severity:

    • INFO — spec-sanctioned normalization (a default the spec allows, or a v2→v3 upgrade).

    • WARNING — a deviation repaired without losing information (missing recommended field, coerced type).

    • ERROR — something that could not be represented and was dropped/skipped; the model is lossy at path but still usable — and it still did not raise.

  • path — a JSON pointer to the offending node.

  • recovered — what was assumed, coerced, or skipped.

For example, a manifest whose label is a bare string instead of a language map is coerced ({"none": ["…"]}) and flagged with a coerced-language-map warning — you get a usable label and a record that the source was non-conforming.

The lint

The lint is a reporting view over those diagnostics — “what did a real client have to guess or fix to read this document?” It is client-compatibility feedback, not a strict spec-conformance check.

report = iiisight.lint(manifest)          # accepts a Manifest/Collection or a raw dict

report.ok                                  # True if nothing beyond INFO occurred
report.summary()                           # "1 error(s), 2 warning(s), 3 info"
for problem in report.errors:              # also .warnings, .infos
    print(problem.code, problem.path, problem.message)
report.counts()                            # {Severity.ERROR: 1, ...}

You can lint a document you have not fetched yet by passing a URL through a client first:

async with iiisight.AsyncClient() as client:
    manifest = await client.fetch_manifest(url)
report = iiisight.lint(manifest)
print(report.summary())
with iiisight.Client() as client:
    manifest = client.fetch_manifest(url)
report = iiisight.lint(manifest)
print(report.summary())

Why tolerance matters

Running iiisight against production manifests (Wellcome, the Bodleian) surfaces real deviations — e.g. a search service with a bare-string label where v3 requires a language map. A stricter parser would reject those documents outright; iiisight reads them and tells you what it had to repair.