Skip to content

Commit b18c2d4

Browse files
committed
fix: clean up OpenAPI spec generation code and improve logging for model schema creation
1 parent bd5ce90 commit b18c2d4

3 files changed

Lines changed: 117 additions & 86 deletions

File tree

App/Index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,6 @@ const init: PromiseVoidFunction = async (): Promise<void> => {
100100

101101
// Generate OpenAPI spec (this automatically saves it to cache)
102102
OpenAPIUtil.generateOpenAPISpec();
103-
104103
} catch (err) {
105104
logger.error("App Init Failed:");
106105
logger.error(err);

Common/Server/Utils/OpenAPI.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default class OpenAPIUtil {
1515
// check if the cache is already in LocalCache
1616
const cachedSpec: JSONValue | undefined = LocalCache.getJSON(
1717
"openapi",
18-
"spec"
18+
"spec",
1919
);
2020

2121
if (cachedSpec) {
@@ -38,14 +38,14 @@ export default class OpenAPIUtil {
3838

3939
if (!model.enableDocumentation) {
4040
logger.debug(
41-
`Skipping OpenAPI documentation for model ${modelName} as it is disabled.`
41+
`Skipping OpenAPI documentation for model ${modelName} as it is disabled.`,
4242
);
4343
continue;
4444
}
4545

4646
if (!model.crudApiPath) {
4747
logger.debug(
48-
`Skipping OpenAPI documentation for model ${modelName} as it does not have a CRUD API path defined.`
48+
`Skipping OpenAPI documentation for model ${modelName} as it does not have a CRUD API path defined.`,
4949
);
5050
continue;
5151
}
@@ -81,23 +81,24 @@ export default class OpenAPIUtil {
8181
}
8282

8383
const generator: OpenApiGeneratorV3 = new OpenApiGeneratorV3(
84-
registry.definitions
84+
registry.definitions,
8585
);
8686

8787
const spec: JSONObject = generator.generateDocument({
8888
openapi: "3.0.0",
8989
info: {
9090
title: "OneUptime OpenAPI Specification",
9191
version: "1.0.0",
92-
description: "OpenAPI specification for OneUptime. This document describes the API endpoints, request and response formats, and other details necessary for developers to interact with the OneUptime API.",
92+
description:
93+
"OpenAPI specification for OneUptime. This document describes the API endpoints, request and response formats, and other details necessary for developers to interact with the OneUptime API.",
9394
},
9495
servers: [
9596
{
9697
url: `${HttpProtocol.toString()}${Host.toString()}/api`,
9798
description: "API Server",
9899
},
99100
],
100-
}) as unknown as JSONObject;
101+
}) as unknown as JSONObject;
101102

102103
LocalCache.setJSON("openapi", "spec", spec as JSONObject);
103104

@@ -112,8 +113,6 @@ export default class OpenAPIUtil {
112113
const model: DatabaseBaseModel = new modelType();
113114
const tableName: string = model.tableName || "UnknownModel";
114115

115-
116-
117116
data.registry.registerPath({
118117
method: "get",
119118
path: `${model.crudApiPath}/get-list`,
@@ -404,7 +403,7 @@ export default class OpenAPIUtil {
404403

405404
private static registerModelSchemas(
406405
registry: OpenAPIRegistry,
407-
model: DatabaseBaseModel
406+
model: DatabaseBaseModel,
408407
): void {
409408
const tableName: string = model.tableName || "UnknownModel";
410409
const modelSchema: ModelSchemaType = ModelSchema.getModelSchema({

Common/Utils/Schema/ModelSchema.ts

Lines changed: 109 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -40,95 +40,128 @@ export class ModelSchema {
4040
for (const key in columns) {
4141
const column: TableColumnMetadata | undefined = columns[key];
4242
if (!column) {
43-
continue;
43+
continue;
4444
}
4545
let zodType: any;
4646
if (column.type === TableColumnType.ObjectID) {
47-
zodType = z.string().openapi({ type: "string", example: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' });
47+
zodType = z.string().openapi({
48+
type: "string",
49+
example: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
50+
});
4851
} else if (column.type === TableColumnType.Date) {
49-
zodType = z.date().openapi({ type: "string", format: "date-time", example: '2023-01-15T12:30:00.000Z' });
50-
} else if (
51-
column.type === TableColumnType.Version ||
52-
column.type === TableColumnType.Number ||
53-
column.type === TableColumnType.PositiveNumber
54-
) {
55-
zodType = z.number().openapi({ type: "number", example: 42 });
56-
} else if (column.type === TableColumnType.Email) {
57-
zodType = z.string().email().openapi({ type: "string", format: "email", example: 'user@example.com' });
58-
} else if (column.type === TableColumnType.HashedString) {
59-
zodType = z.string().openapi({ type: "string", example: 'hashed_string_value' });
60-
} else if (column.type === TableColumnType.Slug) {
61-
zodType = z.string().openapi({ type: "string", example: 'example-slug-value' });
62-
} else if (column.type === TableColumnType.ShortText) {
63-
zodType = z.string().openapi({ type: "string", example: 'Example short text' });
64-
} else if (column.type === TableColumnType.LongText) {
65-
zodType = z.string().openapi({ type: "string", example: 'This is an example of longer text content that might be stored in this field.' });
66-
} else if (column.type === TableColumnType.Phone) {
67-
zodType = z.string().openapi({ type: "string", example: '+1-555-123-4567' });
68-
} else if (column.type === TableColumnType.Boolean) {
69-
zodType = z.boolean().openapi({ type: "boolean", example: true });
70-
} else if (column.type === TableColumnType.JSON) {
71-
zodType = z.any().openapi({ type: "object", example: { key: 'value', nested: { data: 123 } } });
72-
} else if (column.type === TableColumnType.EntityArray) {
73-
const entityArrayType: (new () => DatabaseBaseModel) | undefined =
74-
column.modelType;
75-
if (!entityArrayType) {
76-
logger.debug(`Entity type is not defined for column ${key}`);
77-
continue;
78-
}
79-
const schemaArray: ModelSchemaType = ModelSchema.getModelSchema({
80-
modelType: entityArrayType,
81-
});
82-
zodType = z.array(
83-
z.lazy(() => {
84-
return schemaArray;
85-
}),
86-
).openapi({ type: "array", example: [{ id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' }] });
87-
} else if (column.type === TableColumnType.Entity) {
88-
const entityType: (new () => DatabaseBaseModel) | undefined =
89-
column.modelType;
90-
91-
if (!entityType) {
92-
logger.debug(`Entity type is not defined for column ${key}`);
93-
continue;
94-
}
95-
96-
const schema: ModelSchemaType = ModelSchema.getModelSchema({
97-
modelType: entityType,
98-
});
99-
zodType = z.lazy(() => {
100-
return schema;
101-
}).openapi({ type: "object", example: { id: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' } });
102-
} else {
103-
zodType = z.any().openapi({ type: "null", example: null });
52+
zodType = z.date().openapi({
53+
type: "string",
54+
format: "date-time",
55+
example: "2023-01-15T12:30:00.000Z",
56+
});
57+
} else if (
58+
column.type === TableColumnType.Version ||
59+
column.type === TableColumnType.Number ||
60+
column.type === TableColumnType.PositiveNumber
61+
) {
62+
zodType = z.number().openapi({ type: "number", example: 42 });
63+
} else if (column.type === TableColumnType.Email) {
64+
zodType = z.string().email().openapi({
65+
type: "string",
66+
format: "email",
67+
example: "user@example.com",
68+
});
69+
} else if (column.type === TableColumnType.HashedString) {
70+
zodType = z
71+
.string()
72+
.openapi({ type: "string", example: "hashed_string_value" });
73+
} else if (column.type === TableColumnType.Slug) {
74+
zodType = z
75+
.string()
76+
.openapi({ type: "string", example: "example-slug-value" });
77+
} else if (column.type === TableColumnType.ShortText) {
78+
zodType = z
79+
.string()
80+
.openapi({ type: "string", example: "Example short text" });
81+
} else if (column.type === TableColumnType.LongText) {
82+
zodType = z.string().openapi({
83+
type: "string",
84+
example:
85+
"This is an example of longer text content that might be stored in this field.",
86+
});
87+
} else if (column.type === TableColumnType.Phone) {
88+
zodType = z
89+
.string()
90+
.openapi({ type: "string", example: "+1-555-123-4567" });
91+
} else if (column.type === TableColumnType.Boolean) {
92+
zodType = z.boolean().openapi({ type: "boolean", example: true });
93+
} else if (column.type === TableColumnType.JSON) {
94+
zodType = z.any().openapi({
95+
type: "object",
96+
example: { key: "value", nested: { data: 123 } },
97+
});
98+
} else if (column.type === TableColumnType.EntityArray) {
99+
const entityArrayType: (new () => DatabaseBaseModel) | undefined =
100+
column.modelType;
101+
if (!entityArrayType) {
102+
logger.debug(`Entity type is not defined for column ${key}`);
103+
continue;
104104
}
105-
106-
if (column.required) {
107-
// leave as is
108-
} else {
109-
zodType = zodType.optional();
105+
const schemaArray: ModelSchemaType = ModelSchema.getModelSchema({
106+
modelType: entityArrayType,
107+
});
108+
zodType = z
109+
.array(
110+
z.lazy(() => {
111+
return schemaArray;
112+
}),
113+
)
114+
.openapi({
115+
type: "array",
116+
example: [{ id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }],
117+
});
118+
} else if (column.type === TableColumnType.Entity) {
119+
const entityType: (new () => DatabaseBaseModel) | undefined =
120+
column.modelType;
121+
122+
if (!entityType) {
123+
logger.debug(`Entity type is not defined for column ${key}`);
124+
continue;
110125
}
111126

112-
// add title and description to the schema
113-
if (column.title) {
114-
zodType = zodType.describe(column.title);
115-
}
127+
const schema: ModelSchemaType = ModelSchema.getModelSchema({
128+
modelType: entityType,
129+
});
130+
zodType = z
131+
.lazy(() => {
132+
return schema;
133+
})
134+
.openapi({
135+
type: "object",
136+
example: { id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" },
137+
});
138+
} else {
139+
zodType = z.any().openapi({ type: "null", example: null });
140+
}
116141

142+
if (column.required) {
143+
// leave as is
144+
} else {
145+
zodType = zodType.optional();
146+
}
117147

148+
// add title and description to the schema
149+
if (column.title) {
150+
zodType = zodType.describe(column.title);
151+
}
118152

119153
shape[key] = zodType;
120154
}
121155

122-
const schema: ModelSchemaType = z
123-
.object(shape);
124-
125-
logger.debug(
126-
`Model schema for ${model.tableName} created with shape: ${JSON.stringify(
127-
shape,
128-
null,
129-
2,
130-
)}`,
131-
);
156+
const schema: ModelSchemaType = z.object(shape);
157+
158+
logger.debug(
159+
`Model schema for ${model.tableName} created with shape: ${JSON.stringify(
160+
shape,
161+
null,
162+
2,
163+
)}`,
164+
);
132165

133166
return schema;
134167
}

0 commit comments

Comments
 (0)