Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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/jsonld-schema-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"swagger-typescript-api": minor
---

Add JSON-LD schema support for generating TypeScript types from JSON-LD context and entity schemas
93 changes: 92 additions & 1 deletion src/code-gen-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,66 @@ export class CodeGenProcess {
}
}

// Add JSON-LD output files if enabled and schemas are present
const jsonldOutputFiles: TranslatorIO[] = [];

// Check if we have JSON-LD schemas and options enabled
const hasJsonLdSchemas = configuration.modelTypes?.some?.(
(modelType) =>
modelType.typeData?.schemaType === "jsonld-context" ||
modelType.typeData?.schemaType === "jsonld-entity" ||
modelType.typeData?.schemaType === "jsonld-type",
);

if (hasJsonLdSchemas) {
const { jsonLdOptions } = configuration.config;

// Generate JSON-LD context interfaces if enabled
if (
jsonLdOptions?.generateContext &&
templatesToRender.jsonldContextDataContract
) {
jsonldOutputFiles.push(
...(await this.createOutputFileInfo(
configuration,
fileNames.jsonldContext,
this.templatesWorker.renderTemplate(
templatesToRender.jsonldContextDataContract,
configuration,
),
)),
);
}

// Generate JSON-LD entity interfaces
if (templatesToRender.jsonldEntityDataContract) {
jsonldOutputFiles.push(
...(await this.createOutputFileInfo(
configuration,
fileNames.jsonldEntity,
this.templatesWorker.renderTemplate(
templatesToRender.jsonldEntityDataContract,
configuration,
),
)),
);
}

// Generate JSON-LD utility types if enabled
if (jsonLdOptions?.generateUtils && templatesToRender.jsonldUtils) {
jsonldOutputFiles.push(
...(await this.createOutputFileInfo(
configuration,
fileNames.jsonldUtils,
this.templatesWorker.renderTemplate(
templatesToRender.jsonldUtils,
configuration,
),
)),
);
}
}

return [
...(await this.createOutputFileInfo(
configuration,
Expand All @@ -460,6 +520,7 @@ export class CodeGenProcess {
),
)
: []),
...jsonldOutputFiles,
...modularApiFileInfos,
];
};
Expand All @@ -468,7 +529,16 @@ export class CodeGenProcess {
templatesToRender,
configuration,
): Promise<TranslatorIO[]> => {
const { generateRouteTypes, generateClient } = configuration.config;
const { generateRouteTypes, generateClient, jsonLdOptions } =
configuration.config;

// Check if we have JSON-LD schemas
const hasJsonLdSchemas = configuration.modelTypes?.some?.(
(modelType) =>
modelType.typeData?.schemaType === "jsonld-context" ||
modelType.typeData?.schemaType === "jsonld-entity" ||
modelType.typeData?.schemaType === "jsonld-type",
);

return await this.createOutputFileInfo(
configuration,
Expand All @@ -479,6 +549,27 @@ export class CodeGenProcess {
templatesToRender.dataContracts,
configuration,
),
// Include JSON-LD templates in single file output if present
hasJsonLdSchemas &&
jsonLdOptions?.generateContext &&
templatesToRender.jsonldContextDataContract &&
this.templatesWorker.renderTemplate(
templatesToRender.jsonldContextDataContract,
configuration,
),
hasJsonLdSchemas &&
templatesToRender.jsonldEntityDataContract &&
this.templatesWorker.renderTemplate(
templatesToRender.jsonldEntityDataContract,
configuration,
),
hasJsonLdSchemas &&
jsonLdOptions?.generateUtils &&
templatesToRender.jsonldUtils &&
this.templatesWorker.renderTemplate(
templatesToRender.jsonldUtils,
configuration,
),
generateRouteTypes &&
this.templatesWorker.renderTemplate(
templatesToRender.routeTypes,
Expand Down
86 changes: 86 additions & 0 deletions src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ export class CodeGenConfig {
routeTypes: "route-types",
httpClient: "http-client",
outOfModuleApi: "Common",
jsonldContext: "jsonld-context",
jsonldEntity: "jsonld-entity",
jsonldUtils: "jsonld-utils",
};
routeNameDuplicatesMap = new Map();
hooks: Hooks = {
Expand Down Expand Up @@ -141,6 +144,9 @@ export class CodeGenConfig {
httpClient: "",
routeTypes: "",
routeName: "",
jsonldContextDataContract: "",
jsonldEntityDataContract: "",
jsonldUtils: "",
};
schemaParsers: Record<string, (...args: unknown[]) => MonoSchemaParser> = {};
toJS = false;
Expand Down Expand Up @@ -180,6 +186,20 @@ export class CodeGenConfig {

successResponseStatusRange = [200, 299];

/** JSON-LD specific configuration options */
jsonLdOptions = {
/** Generate context interfaces */
generateContext: true,
/** Enforce strict JSON-LD typing */
strictTyping: false,
/** Prefix for entity interfaces */
entityPrefix: "",
/** Suffix for context interfaces */
contextSuffix: "Context",
/** Generate utility types for JSON-LD */
generateUtils: true,
};

extractingOptions: Partial<ExtractingOptions> = {
requestBodySuffix: ["Payload", "Body", "Input"],
requestParamsSuffix: ["Params"],
Expand Down Expand Up @@ -390,6 +410,18 @@ export class CodeGenConfig {
"relative-json-pointer": () => this.Ts.Keyword.String,
regex: () => this.Ts.Keyword.String,
},
// JSON-LD specific types
"jsonld-iri": () => this.Ts.Keyword.String,
"jsonld-literal": (schema) => this.getJsonLdLiteralType(schema),
"jsonld-node": () => "JsonLdNode",
"jsonld-context": () =>
this.Ts.UnionType([
this.Ts.Keyword.String,
this.Ts.Keyword.Object,
this.Ts.ArrayType(
this.Ts.UnionType([this.Ts.Keyword.String, this.Ts.Keyword.Object]),
),
]),
};

templateInfos = [
Expand All @@ -403,6 +435,15 @@ export class CodeGenConfig {
{ name: "httpClient", fileName: "http-client" },
{ name: "routeTypes", fileName: "route-types" },
{ name: "routeName", fileName: "route-name" },
{
name: "jsonldContextDataContract",
fileName: "jsonld-context-data-contract",
},
{
name: "jsonldEntityDataContract",
fileName: "jsonld-entity-data-contract",
},
{ name: "jsonldUtils", fileName: "jsonld-utils" },
];

templateExtensions = [".eta", ".ejs"];
Expand Down Expand Up @@ -439,6 +480,51 @@ export class CodeGenConfig {
this.componentTypeNameResolver = new ComponentTypeNameResolver(this, []);
}

/** Helper method to determine JSON-LD literal type */
getJsonLdLiteralType = (schema: any): string => {
if (schema && typeof schema === "object") {
// Check for @type in schema to determine literal type
if (schema["@type"]) {
const type = schema["@type"];
switch (type) {
case "xsd:string":
case "http://www.w3.org/2001/XMLSchema#string":
return this.Ts.Keyword.String;
case "xsd:integer":
case "xsd:int":
case "http://www.w3.org/2001/XMLSchema#integer":
case "http://www.w3.org/2001/XMLSchema#int":
return this.Ts.Keyword.Number;
case "xsd:boolean":
case "http://www.w3.org/2001/XMLSchema#boolean":
return this.Ts.Keyword.Boolean;
case "xsd:dateTime":
case "http://www.w3.org/2001/XMLSchema#dateTime":
return this.Ts.Keyword.String; // or Date if preferred
default:
return this.Ts.Keyword.String;
}
}

// Fallback to primitive type detection
if (schema.type) {
switch (schema.type) {
case "string":
return this.Ts.Keyword.String;
case "number":
case "integer":
return this.Ts.Keyword.Number;
case "boolean":
return this.Ts.Keyword.Boolean;
default:
return this.Ts.Keyword.String;
}
}
}

return this.Ts.Keyword.String;
};

update = (update: Partial<GenerateApiConfiguration["config"]>) => {
objectAssign(this, update);
};
Expand Down
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,7 @@ export const SCHEMA_TYPES = {
COMPLEX_ALL_OF: "allOf",
COMPLEX_NOT: "not",
COMPLEX_UNKNOWN: "__unknown",
JSONLD_CONTEXT: "jsonld-context",
JSONLD_ENTITY: "jsonld-entity",
JSONLD_TYPE: "jsonld-type",
} as const;
Loading