fortify-schema
Version:
A modern TypeScript validation library designed around familiar interface syntax and powerful conditional validation. Experience schema validation that feels natural to TypeScript developers while unlocking advanced runtime validation capabilities.
176 lines (152 loc) • 5.64 kB
text/typescript
import { OpenAPIOptions, OpenAPISpec } from "../../../../types/extension.type";
import { SchemaInterface } from "../../../../types/SchemaValidator.type";
/**
* OpenAPI generator
*/
export class OpenAPIGenerator {
constructor(
private schema: SchemaInterface,
private options: OpenAPIOptions
) {}
generate(): OpenAPISpec {
return {
openapi: "3.0.0",
info: {
title: this.options.title,
version: this.options.version
},
servers: this.options.servers?.map(url => ({ url })) || [],
components: {
schemas: {
[this.options.title]: this.convertSchema()
}
}
};
}
private convertSchema(): any {
const properties: any = {};
const required: string[] = [];
Object.entries(this.schema).forEach(([fieldName, fieldType]) => {
const openApiProperty = this.convertFieldToOpenAPIProperty(fieldName, fieldType);
properties[fieldName] = openApiProperty.schema;
if (openApiProperty.required) {
required.push(fieldName);
}
});
return {
type: "object",
properties,
required: required.length > 0 ? required : undefined
};
}
private convertFieldToOpenAPIProperty(_fieldName: string, fieldType: any): { schema: any; required: boolean } {
if (typeof fieldType === "string") {
const isOptional = fieldType.includes("?");
const cleanType = fieldType.replace("?", "");
// Handle string types with constraints
if (cleanType.startsWith("string")) {
const constraints = this.parseStringConstraints(cleanType);
return {
schema: {
type: "string",
...constraints
},
required: !isOptional
};
}
// Handle specific string formats
if (cleanType === "email") {
return {
schema: { type: "string", format: "email" },
required: !isOptional
};
}
if (cleanType === "url") {
return {
schema: { type: "string", format: "uri" },
required: !isOptional
};
}
if (cleanType === "uuid") {
return {
schema: { type: "string", format: "uuid" },
required: !isOptional
};
}
if (cleanType === "date") {
return {
schema: { type: "string", format: "date-time" },
required: !isOptional
};
}
// Handle number types
if (cleanType === "number" || cleanType === "positive" || cleanType === "int") {
const schema: any = { type: cleanType === "int" ? "integer" : "number" };
if (cleanType === "positive") {
schema.minimum = 0;
schema.exclusiveMinimum = true;
}
return { schema, required: !isOptional };
}
// Handle boolean
if (cleanType === "boolean") {
return {
schema: { type: "boolean" },
required: !isOptional
};
}
// Handle arrays
if (cleanType.includes("[]")) {
const itemType = cleanType.replace("[]", "");
const itemSchema = this.convertFieldToOpenAPIProperty("item", itemType).schema;
return {
schema: {
type: "array",
items: itemSchema
},
required: !isOptional
};
}
// Default to string
return {
schema: { type: "string" },
required: !isOptional
};
}
// Handle object types
if (typeof fieldType === "object" && !Array.isArray(fieldType)) {
const nestedProperties: any = {};
const nestedRequired: string[] = [];
Object.entries(fieldType).forEach(([nestedField, nestedType]) => {
const nestedProperty = this.convertFieldToOpenAPIProperty(nestedField, nestedType);
nestedProperties[nestedField] = nestedProperty.schema;
if (nestedProperty.required) {
nestedRequired.push(nestedField);
}
});
return {
schema: {
type: "object",
properties: nestedProperties,
required: nestedRequired.length > 0 ? nestedRequired : undefined
},
required: true
};
}
// Fallback
return {
schema: { type: "string" },
required: true
};
}
private parseStringConstraints(stringType: string): any {
const constraints: any = {};
// Parse string(min,max) format
const match = stringType.match(/string\((\d+)?,?(\d+)?\)/);
if (match) {
if (match[1]) constraints.minLength = parseInt(match[1]);
if (match[2]) constraints.maxLength = parseInt(match[2]);
}
return constraints;
}
}