Newer
Older
ServerGenerator / src / OpenApiGenerator.ts
import { OpenAPIV3 } from "openapi-types";
import { Description } from "./app";
import { Collection, Entity, Property } from "./MySchema";

type APISchemas = {
    [key: string]: OpenAPIV3.ReferenceObject | OpenAPIV3.ArraySchemaObject | OpenAPIV3.NonArraySchemaObject;
};

function generateSchemas(entities: Entity[]): APISchemas {
    return fromEntites(entities.map(generateSchema));
}

function generateSchema(entity: Entity): [string, OpenAPIV3.NonArraySchemaObject] {
    const schema: OpenAPIV3.NonArraySchemaObject = {
        type: "object",
        required: entity.properties.filter(p => !p.isNullable).map(p => p.key).concat(["id", "ref"]),
        properties: fromEntites(addIdProperties(entity.properties.map(generateProperty))),
    };
    return [
        entity.name,
        schema,
    ];
}

function addIdProperties(properties: [string, OpenAPIV3.NonArraySchemaObject][]): [string, OpenAPIV3.NonArraySchemaObject][] {
    return [
        ["id", { type: "number", format: "int32" }],
        ...properties,
        ["ref", { type: "string", format: "uri" }],
    ];
}

function fromEntites<T>(list: [string, T][]): { [key: string]: T; } {
    const obj: { [key: string]: T; } = {};
    for (const mapped of list) {
        obj[mapped[0]] = mapped[1];
    }
    return obj;
}

function generateProperty(property: Property): [string, OpenAPIV3.NonArraySchemaObject] {
    const prop: OpenAPIV3.NonArraySchemaObject = {
        type: property.type,
    }
    return [
        property.key,
        prop,
    ];
}

function generatePaths(collections: Collection[]): OpenAPIV3.PathsObject {
    const paths = [
        ...collections.map(generateCollectionPath),
        ...collections.map(generateEntityPath),
    ];
    return fromEntites(paths);
}

export function collectionRoute(collection: Collection): string {
    return "/" + collection.name.toLowerCase();
}

function generateCollectionPath(collection: Collection): [string, OpenAPIV3.PathItemObject] {
    const route = collectionRoute(collection);
    const path: OpenAPIV3.PathItemObject = {
        get: generateGetAllOperation(collection),
    };
    return [route, path];
}

function generateGetAllOperation(collection: Collection): OpenAPIV3.OperationObject {
    const operation: OpenAPIV3.OperationObject = {
        responses: {
            "200": {
                description: `Lists all ${collection.name}`,
                content: {
                    "application/json": {
                        schema: {
                            type: "array",
                            items: {
                                $ref: "#/components/schemas/" + collection.entities.name
                            }
                        }
                    }
                }
            }
        }
    };
    return operation;
}

function generateEntityPath(collection: Collection): [string, OpenAPIV3.PathItemObject] {
    const route = itemRoute(collection);
    const path: OpenAPIV3.PathItemObject = {
        get: generateGetSpecificOperation(collection.entities),
    };
    return [route, path];
}

function itemRoute(collection: Collection): string {
    return collectionRoute(collection) + "/{" + idParameterName(collection.entities) + "}";
}

function idParameterName(entity: Entity): string {
    return entity.name.toLowerCase() + "Id";
}

function generateGetSpecificOperation(entity: Entity): OpenAPIV3.OperationObject {
    const operation: OpenAPIV3.OperationObject = {
        parameters: [
            {
                in: "path",
                name: idParameterName(entity),
                required: true,
                schema: {
                    type: "integer",
                },
            },
        ],
        responses: {
            "200": {
                description: "Returns one specific " + entity.name,
                content: {
                    "application/json": {
                        schema: {
                            $ref: "#/components/schemas/" + entity.name,
                        }
                    }
                }
            },
            "404": {
                $ref: "#/components/responses/NotFound",
            }
        }
    };
    return operation;
}

const NotFoundResponse: OpenAPIV3.ResponseObject = {
    description: "The specified resource does not exist.",
    content: {
        "text/plain": {
            schema: {
                type: "string",
                example: "not found",
            }
        }
    }
};

export function generateOpenAPI(description: Description): OpenAPIV3.Document {
    const doc: OpenAPIV3.Document = {
        openapi: "3.0.0",
        info: {
            title: "API for " + description.collections.map(c => c.name).join(", "),
            version: "1.0.0",
        },
        servers: [{ url: "http://localhost:8080" }],
        components: {
            schemas: generateSchemas(description.collections.map(c => c.entities)),
            responses: {
                NotFound: NotFoundResponse
            }
        },
        paths: generatePaths(description.collections),
    };
    return doc;
}