Skip to content

Validating a bundle

Parsing tells you a model is well formed. It cannot tell you that the schema it points at was actually published, or that a relationship names a model that exists. Those are the failures a consumer meets as a broken integration rather than as a parse error, so they get their own pass.

import { assembleBundle, validateBundle, hasErrors } from "@openhi/ours";
const bundle = assembleBundle({ ontology, models, vocabularies, schemas });
const issues = validateBundle(bundle);
for (const issue of issues) {
console.error(`${issue.level} ${issue.resource} ${issue.message}`);
}
if (hasErrors(issues)) process.exit(1);
interface ValidationIssue {
readonly level: "error" | "warning";
/** The URL of the resource the issue is about. */
readonly resource: string;
readonly message: string;
}

An error is something that will break a consumer. A relationship pointing at a model that is not in the ontology leaves them with a dead link; a $ref that does not resolve makes the schema uncompilable. Do not publish with any.

A warning is something worth knowing. A model with no mapsTo is valid, and is also invisible to the integration OURS exists to enable. An ontology can be published with warnings, and sometimes should be, but each one is a question worth having an answer to.

hasErrors is the predicate to gate a publish on. It ignores warnings.

Level Condition
error Two models share an id.
error schema names a schema that is not in the bundle.
error A relationship.target names no model in the bundle, by name or by id.
warning The model publishes no schema.
warning The model publishes no mapsTo alignment.

Relationships are checked once every model is known, so a model may reference one that appears later in the bundle. Order in the document does not decide whether a forward reference is an error.

Level Condition
error Two vocabularies share an id.
warning The vocabulary has neither codes nor a mapsTo alignment.

An empty mapsTo counts as none. The field being present says only that somebody typed it.

Level Condition
error A $ref does not resolve to a schema in the bundle.

References resolve against the $id in scope where they were written, not against the document they happen to sit in, and both the bundle’s documents and every $id embedded inside them count as targets. A pointer beginning with # is the schema’s own business and is left alone.

Schemas covers the resolution rules and why they matter.

validateBundle(bundle, { warnOnMissingMapsTo: false });
Option Default
warnOnMissingMapsTo true Warn about models that publish no alignment. Turn it off for an ontology that is deliberately all your own.

The check that matters is the one against the files you are about to serve, not against the objects in memory that produced them. Read the built files back:

import { assembleBundle, validateBundle, hasErrors } from "@openhi/ours";
const read = (path: string) => Bun.file(path).json();
const bundle = assembleBundle({
ontology: await read("public/ontology.json"),
models: [await read("public/models.json")],
vocabularies: [await read("public/vocabularies.json")],
schemas: await Promise.all(
[...new Bun.Glob("public/schemas/**/*.json").scanSync(".")].map(read),
),
});
const issues = validateBundle(bundle);
for (const issue of issues) {
console.error(`${issue.level} ${issue.resource} ${issue.message}`);
}
if (hasErrors(issues)) process.exit(1);

assembleBundle throws rather than reporting an issue for the problems that make a bundle impossible to build at all: a duplicate url, a JSON Schema with no $id, or a schema whose $id collides with one derived from a vocabulary. Let those throw. They are bugs in the generator, not conditions to report on.