fortify-schema
Version:
TypeScript interface-like schema validation system that's easier to use than Zod
228 lines (225 loc) • 7.05 kB
JavaScript
;
/**
* OpenAPI Converter - Convert schemas to OpenAPI specifications
*
* This module provides utilities to convert Fortify Schema definitions
* to OpenAPI 3.0 specifications for API documentation.
*/
/**
* OpenAPI converter for schema definitions
*/
class OpenAPIConverter {
/**
* Convert a schema to OpenAPI format
*/
static convertSchema(schema) {
const properties = {};
const required = [];
Object.entries(schema).forEach(([fieldName, fieldType]) => {
const openApiProperty = this.convertField(fieldType);
properties[fieldName] = openApiProperty.schema;
if (openApiProperty.required) {
required.push(fieldName);
}
});
return {
type: "object",
properties,
required: required.length > 0 ? required : undefined
};
}
/**
* Convert a single field to OpenAPI property
*/
static convertField(fieldType) {
if (typeof fieldType === "string") {
return this.convertStringField(fieldType);
}
if (typeof fieldType === "object" && !Array.isArray(fieldType)) {
return this.convertObjectField(fieldType);
}
// Fallback
return {
schema: { type: "string" },
required: true
};
}
/**
* Convert string-based field definitions
*/
static convertStringField(fieldType) {
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
const formatMappings = {
email: { type: "string", format: "email" },
url: { type: "string", format: "uri" },
uuid: { type: "string", format: "uuid" },
date: { type: "string", format: "date-time" },
phone: { type: "string", pattern: "^\\+?[1-9]\\d{1,14}$" }
};
if (formatMappings[cleanType]) {
return {
schema: formatMappings[cleanType],
required: !isOptional
};
}
// Handle number types
if (cleanType === "number") {
return {
schema: { type: "number" },
required: !isOptional
};
}
if (cleanType === "positive") {
return {
schema: {
type: "number",
minimum: 0,
exclusiveMinimum: true
},
required: !isOptional
};
}
if (cleanType === "int") {
return {
schema: { type: "integer" },
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.convertField(itemType).schema;
return {
schema: {
type: "array",
items: itemSchema
},
required: !isOptional
};
}
// Default to string
return {
schema: { type: "string" },
required: !isOptional
};
}
/**
* Convert object field definitions
*/
static convertObjectField(fieldType) {
const nestedProperties = {};
const nestedRequired = [];
Object.entries(fieldType).forEach(([nestedField, nestedType]) => {
const nestedProperty = this.convertField(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
};
}
/**
* Parse string constraints from string(min,max) format
*/
static parseStringConstraints(stringType) {
const constraints = {};
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;
}
/**
* Generate complete OpenAPI specification
*/
static generateOpenAPISpec(schema, options) {
const schemaObject = this.convertSchema(schema);
return {
openapi: "3.0.0",
info: {
title: options.title,
version: options.version,
description: options.description
},
servers: options.servers?.map(url => ({ url })) || [],
components: {
schemas: {
[options.schemaName || options.title]: schemaObject
}
},
paths: options.paths || {}
};
}
/**
* Generate schema reference for use in OpenAPI paths
*/
static generateSchemaReference(schemaName) {
return {
$ref: `#/components/schemas/${schemaName}`
};
}
/**
* Generate request body specification
*/
static generateRequestBody(schema, options = {}) {
const schemaObject = this.convertSchema(schema);
return {
description: options.description || "Request body",
required: options.required !== false,
content: {
"application/json": {
schema: schemaObject,
examples: options.examples
}
}
};
}
/**
* Generate response specification
*/
static generateResponse(schema, options = {}) {
const schemaObject = this.convertSchema(schema);
return {
description: options.description || "Successful response",
content: {
"application/json": {
schema: schemaObject,
examples: options.examples
}
},
headers: options.headers
};
}
}
exports.OpenAPIConverter = OpenAPIConverter;
//# sourceMappingURL=openapi-converter.js.map