Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/okf/src/ossie/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export function detectModelFormat(input: { fileName?: string; text: string }): M
const first = (lines[i] ?? "").trimStart();
const looksOssie = first.startsWith("{") || OSSIE_KEY.test(first);
// Frontmatter in OKF docs can also start with `name:` / `version:`, so also
// require a top-level `datasets` / `semantic_model` key.
const hasModelKey = /^(datasets|semantic_model)\s*:/m.test(lines.join("\n")) || /"(datasets|semantic_model)"\s*:/.test(text);
// require a top-level Ossie key. Ontology and mapping documents count too:
// the Ossie reader explains that they aren't semantic models.
const hasModelKey = /^(datasets|semantic_model|ontology|concept_mappings)\s*:/m.test(lines.join("\n")) || /"(datasets|semantic_model|ontology|concept_mappings)"\s*:/.test(text);
return looksOssie && hasModelKey ? "ossie" : "okf";
}
19 changes: 18 additions & 1 deletion packages/okf/src/ossie/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ export function parseOssie(text: string): OssieImport {
}
}

const USE_SEMANTIC_MODEL = 'The canvas imports semantic models: import the file with a top-level "datasets" list instead.';

// Ossie also has ontology documents (business concepts) and mapping documents
// (ontology ↔ semantic model). Neither describes datasets, so say what the file
// is instead of a bare "not an Ossie model".
function notSemanticModelMessage(obj: Record<string, unknown>): string {
if (Array.isArray(obj.concept_mappings)) {
const ref = isObj(obj.semantic_model_ref) ? obj.semantic_model_ref.iri : undefined;
const iri = typeof ref === "string" ? ref.trim().slice(0, 200) : "";
return iri
? `This is an Apache Ossie mapping document, not a semantic model. Import the semantic model it points to instead: ${iri}`
: `This is an Apache Ossie mapping document, not a semantic model. ${USE_SEMANTIC_MODEL}`;
}
if (Array.isArray(obj.ontology)) return `This is an Apache Ossie ontology (business concepts), not a semantic model. ${USE_SEMANTIC_MODEL}`;
return "This file isn't an Ossie model";
}

function convert(text: string): OssieImport {
let raw: unknown;
try { raw = YAML.parse(text, { maxAliasCount: 100 }); }
Expand All @@ -60,7 +77,7 @@ function convert(text: string): OssieImport {
} else if (Array.isArray(obj.datasets)) {
doc = obj as unknown as OssieDoc;
} else {
throw new Error("This file isn't an Ossie model");
throw new Error(notSemanticModelMessage(obj));
}

const vendors: string[] = [];
Expand Down
4 changes: 4 additions & 0 deletions packages/okf/test/ossie-detect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ describe("detectModelFormat", () => {
expect(detectModelFormat({ text: "---\nname: Orders\ntitle: Orders\n---\n# Orders" })).toBe("okf");
expect(detectModelFormat({ text: "<!-- shop/a.md -->\n---\ntitle: A\n---" })).toBe("okf");
});
it("routes pasted Ossie ontologies and mappings to the Ossie reader (which explains them)", () => {
expect(detectModelFormat({ text: "version: 0.2.0.dev0\nname: Flights\nontology:\n- concept: Airport" })).toBe("ossie");
expect(detectModelFormat({ text: "version: 0.2.0.dev0\nname: m\nconcept_mappings:\n- concept: Runway" })).toBe("ossie");
});
});
22 changes: 22 additions & 0 deletions packages/okf/test/ossie-parse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,25 @@ describe("parseOssie — malformed but parseable input", () => {
expect(r.graph.edges[0].keys).toEqual([{ left: "a", right: "c" }]);
});
});

describe("parseOssie — Ossie documents that aren't semantic models", () => {
const ontology = "version: 0.2.0.dev0\nname: Flights\nontology:\n- concept: Airport\n type: EntityType\n";
const mapping = "version: 0.2.0.dev0\nname: flights_mapping\nontology_ref:\n name: Flights\n iri: ./flights.ontology.yaml\nsemantic_model_ref:\n name: Flights semantic model\n iri: ./flights.semantic_model.yaml\nconcept_mappings:\n- concept: Runway\n";
it("explains that an ontology isn't a semantic model", () => {
expect(() => parseOssie(ontology)).toThrow(/Apache Ossie ontology/);
expect(() => parseOssie(ontology)).toThrow(/top-level "datasets" list/);
});
it("treats the legacy combined file (ontology + ontology_mappings) as an ontology", () => {
expect(() => parseOssie(`${ontology}ontology_mappings:\n- name: m\n`)).toThrow(/Apache Ossie ontology/);
});
it("points a mapping document at the semantic model it references", () => {
expect(() => parseOssie(mapping)).toThrow(/Apache Ossie mapping/);
expect(() => parseOssie(mapping)).toThrow(/flights\.semantic_model\.yaml/);
});
it("still explains a mapping without a usable semantic model reference", () => {
expect(() => parseOssie("concept_mappings: []\nsemantic_model_ref: 5\n")).toThrow(/Apache Ossie mapping.*top-level "datasets" list/);
});
it("keeps the generic error for anything else", () => {
expect(() => parseOssie("ontology: nope")).toThrow("This file isn't an Ossie model");
});
});
2 changes: 2 additions & 0 deletions packages/web/public/ossie-format.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

The canvas reads the flat `0.2.x` document and the legacy `semantic_model: [ ... ]` wrapper (YAML or JSON). With several models in one file, it imports the first and tells you how many it skipped.

**Ontologies and mappings are not imported yet.** Ossie also defines ontology documents (`ontology:` — business concepts, see [ontology.md](https://github.com/apache/ossie/blob/main/ontology/ontology.md)) and mapping documents (`concept_mappings:` — ontology ↔ semantic model). They describe no datasets, so the import dialog says what the file is and asks for the semantic model instead. For a mapping document it names the semantic model the mapping points to (`semantic_model_ref.iri`). In the Ossie examples, `flights.semantic_model.yaml` imports; `flights.ontology.yaml`, `flights.mapping.yaml` and `flights.yaml` do not.

---

## What the canvas reads
Expand Down
5 changes: 5 additions & 0 deletions packages/web/src/okf/io.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ describe("loadModel*", () => {
await expect(loadModelFiles({ "m.yaml": OSSIE, "a.md": "---\ntitle: A\n---" })).rejects.toThrow("Import one format at a time");
await expect(loadModelFiles({ "a.json": '{"a":1}' })).rejects.toThrow("This file isn't an Ossie model");
});
it("explains an Ossie ontology from a file or pasted text", async () => {
const onto = "version: 0.2.0.dev0\nname: Flights\nontology:\n- concept: Airport\n type: EntityType\n";
await expect(loadModelFiles({ "flights.ontology.yaml": onto })).rejects.toThrow(/Apache Ossie ontology/);
await expect(loadModelText(onto)).rejects.toThrow(/Apache Ossie ontology/);
});
});

describe("size cap", () => {
Expand Down