Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change representation of ont as code interface link types from both sides to just one #442

Merged
merged 11 commits into from
Jul 9, 2024
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: 5 additions & 0 deletions .changeset/purple-pots-fold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@osdk/maker": minor
---

Update ontology as code interface link types to reflect internal expectations of ontology integration
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"editor.defaultFormatter": "dprint.dprint"
},
"[typescript]": {
"editor.defaultFormatter": "dprint.dprint"
"editor.defaultFormatter": "vscode.typescript-language-features"
},
"[json]": {
"editor.defaultFormatter": "dprint.dprint"
Expand Down
70 changes: 27 additions & 43 deletions packages/maker/src/api/defineInterfaceLinkConstraint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,68 +18,52 @@ import invariant from "tiny-invariant";
import type { InterfaceType } from "./types.js";

type Meta = { apiName: string; displayName?: string; description?: string };
type ApiNameOrMeta = string | Meta;
type ApiNameOrInterfaceType = string | InterfaceType;

type Many = { type: InterfaceType; many: ApiNameOrMeta; one?: never };
type One = { type: InterfaceType; one: ApiNameOrMeta; many?: never };
type Many = {
apiName: string;
from: InterfaceType;
toMany: ApiNameOrInterfaceType;
toOne?: never;
displayName?: string;
description?: string;
};
type One = {
apiName: string;
from: InterfaceType;
toOne: ApiNameOrInterfaceType;
toMany?: never;
displayName?: string;
description?: string;
};

export function defineInterfaceLinkConstraint(
{ from, to }: {
from: Many;
to: One;
} | {
from: One;
to: One | Many;
},
linkDef: One | Many,
) {
invariant(
(from.one == null && from.many) || (from.one && from.many == null),
"from should have either one or many, not both",
);

invariant(
(to.one == null && to.many) || (to.one && to.many == null),
"to should have either one or many, not both",
);

invariant(!(from.many && to.many), "many to many is not supported");

const fromLinkMeta = getLinkMeta(from.one ?? from.many);
const toLinkMeta = getLinkMeta(to.one ?? to.many);
const fromLinkMeta = getLinkMeta(linkDef);

invariant(
from.type.links.find(a => a.metadata.apiName === fromLinkMeta.apiName)
linkDef.from.links.find(a => a.metadata.apiName === fromLinkMeta.apiName)
== null,
`Link with apiName ${fromLinkMeta.apiName} already exists on ${from.type.apiName}`,
);
invariant(
to.type.links.find(a => a.metadata.apiName === toLinkMeta.apiName) == null,
`Link with apiName ${toLinkMeta.apiName} already exists on ${to.type.apiName}`,
`Link with apiName ${fromLinkMeta.apiName} already exists on ${linkDef.apiName}`,
);

from.type.links.push({
cardinality: from.many ? "MANY" : "SINGLE",
linkedEntityTypeId: getLinkedType(to.type),
linkDef.from.links.push({
cardinality: linkDef.toMany ? "MANY" : "SINGLE",
linkedEntityTypeId: getLinkedType(linkDef.toMany ?? linkDef.toOne),
metadata: fromLinkMeta,
required: true, // TODO: expose this?
});

to.type.links.push({
cardinality: to.one ? "SINGLE" : "MANY",
linkedEntityTypeId: getLinkedType(from.type),
metadata: toLinkMeta,
required: true, // TODO: expose this?
});
}

function getLinkedType(t: InterfaceType) {
function getLinkedType(t: string | InterfaceType) {
return {
type: "interfaceType" as const,
interfaceType: t.apiName,
interfaceType: typeof t === "string" ? t : t.apiName,
};
}

function getLinkMeta(meta: ApiNameOrMeta) {
function getLinkMeta(meta: One | Many) {
return typeof meta === "string"
? withDefaults({ apiName: meta })
: withDefaults(meta);
Expand Down
174 changes: 13 additions & 161 deletions packages/maker/src/api/overall.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,45 +217,12 @@ describe("Ontology Defining", () => {
b = defineInterface({ apiName: "B" });
});

it("many to many fails", () => {
expect(() => {
// @ts-expect-error
defineInterfaceLinkConstraint({
from: { type: a, many: "manyLinks" }, // need the any to be sure it fails
to: { type: b, many: "manyLinks" },
});
}).toThrowErrorMatchingInlineSnapshot(
`[Error: Invariant failed: many to many is not supported]`,
);
});

it("does not allow passing both one and many", () => {
expect(() => {
defineInterfaceLinkConstraint({
// @ts-expect-error
from: { type: a, one: "singleLink", many: "manyLinks" }, // need the any to be sure it fails
to: { type: b, one: "singleLink" },
});
}).toThrowErrorMatchingInlineSnapshot(
`[Error: Invariant failed: from should have either one or many, not both]`,
);

expect(() => {
defineInterfaceLinkConstraint({
from: { type: a, one: "singleLink" }, // need the any to be sure it fails
// @ts-expect-error
to: { type: b, one: "singleLink", many: "manyLinks" },
});
}).toThrowErrorMatchingInlineSnapshot(
`[Error: Invariant failed: to should have either one or many, not both]`,
);
});

it("single to single works", () => {
it("single link works", () => {
expect(a).not.toBeUndefined();
defineInterfaceLinkConstraint({
from: { type: a, one: "singleLink" },
to: { type: b, one: "singleLink" },
from: a,
toOne: b,
apiName: "singleLink",
});

expect(dumpOntologyFullMetadata()).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -312,109 +279,7 @@ describe("Ontology Defining", () => {
"icon": undefined,
},
"extendsInterfaces": [],
"links": [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the syntax is not creating anything for the links block?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, expanded diff to see its just the one side.

{
"cardinality": "SINGLE",
"linkedEntityTypeId": {
"interfaceType": "A",
"type": "interfaceType",
},
"metadata": {
"apiName": "singleLink",
"description": "singleLink",
"displayName": "singleLink",
},
"required": true,
},
],
"properties": [],
"status": {
"active": {},
"type": "active",
},
},
},
},
"sharedPropertyTypes": {},
}
`);
});

it("many to single works", () => {
defineInterfaceLinkConstraint({
from: { type: a, many: "manyLinks" },
to: { type: b, one: "singleLink" },
});

expect(dumpOntologyFullMetadata()).toMatchInlineSnapshot(`
{
"blockPermissionInformation": {
"actionTypes": {},
"linkTypes": {},
"objectTypes": {},
},
"interfaceTypes": {
"A": {
"interfaceType": {
"allExtendsInterfaces": [],
"allLinks": [],
"allProperties": [],
"apiName": "A",
"displayMetadata": {
"description": "A",
"displayName": "A",
"icon": undefined,
},
"extendsInterfaces": [],
"links": [
{
"cardinality": "MANY",
"linkedEntityTypeId": {
"interfaceType": "B",
"type": "interfaceType",
},
"metadata": {
"apiName": "manyLinks",
"description": "manyLinks",
"displayName": "manyLinks",
},
"required": true,
},
],
"properties": [],
"status": {
"active": {},
"type": "active",
},
},
},
"B": {
"interfaceType": {
"allExtendsInterfaces": [],
"allLinks": [],
"allProperties": [],
"apiName": "B",
"displayMetadata": {
"description": "B",
"displayName": "B",
"icon": undefined,
},
"extendsInterfaces": [],
"links": [
{
"cardinality": "SINGLE",
"linkedEntityTypeId": {
"interfaceType": "A",
"type": "interfaceType",
},
"metadata": {
"apiName": "singleLink",
"description": "singleLink",
"displayName": "singleLink",
},
"required": true,
},
],
"links": [],
"properties": [],
"status": {
"active": {},
Expand All @@ -428,10 +293,11 @@ describe("Ontology Defining", () => {
`);
});

it("single to many works", () => {
it("many link works", () => {
defineInterfaceLinkConstraint({
from: { type: a, one: "singleLink" },
to: { type: b, many: "manyLinks" },
from: a,
toMany: b,
apiName: "manyLink",
});

expect(dumpOntologyFullMetadata()).toMatchInlineSnapshot(`
Expand Down Expand Up @@ -462,9 +328,9 @@ describe("Ontology Defining", () => {
"type": "interfaceType",
},
"metadata": {
"apiName": "singleLink",
"description": "singleLink",
"displayName": "singleLink",
"apiName": "manyLink",
"description": "manyLink",
"displayName": "manyLink",
},
"required": true,
},
Expand All @@ -488,21 +354,7 @@ describe("Ontology Defining", () => {
"icon": undefined,
},
"extendsInterfaces": [],
"links": [
{
"cardinality": "MANY",
"linkedEntityTypeId": {
"interfaceType": "A",
"type": "interfaceType",
},
"metadata": {
"apiName": "manyLinks",
"description": "manyLinks",
"displayName": "manyLinks",
},
"required": true,
},
],
"links": [],
"properties": [],
"status": {
"active": {},
Expand Down