Newer
Older
ServerGenerator / src / MiddlewareGenerator.ts
import { Description, FileWrite } from "./app";
import { dataAccessModuleName } from "./DataAccessGenerator";
import { modelModuleName } from "./ModelGenerator";
import { Collection, Entity, Property } from "./MySchema";
import { collectionRoute } from "./OpenApiGenerator";

export function middlewareModuleName(collection: Collection): string {
    return `${collection.name}Middleware`;
}

export function generateMiddlewares(description: Description): FileWrite[] {
    return description.collections.map(generateMiddleware);
}

function generateMiddleware(collection: Collection): FileWrite {
    const definitions = [
        defineImports(collection),
        defineCreateMiddlewareFunction(collection),
        defineInputParsers(collection.entities),
    ];
    return {
        location: middlewareModuleName(collection) + ".ts",
        content: definitions.join("\n\n") + "\n",
    };
}

function defineImports(collection: Collection): string {
    return `import express from "express";
import * as DataAccess from "./${dataAccessModuleName(collection)}";
import { append, findSingle, ${collection.entities.name}Input, toPayload } from "./${modelModuleName(collection)}";
import { andThen, compose, parseNotNull, ParseObject, parseObject, parseRequiredMember, parseString, resolve, Result } from "./util";`;
}

function defineCreateMiddlewareFunction(collection: Collection): string {
    const col = collection.name.toLowerCase();
    const route = collectionRoute(collection);
    const entityId = collection.entities.name.toLowerCase() + "Id";
    return `export function ${col}Middleware({ fileName }: { fileName: string }) {
    const ${col} = DataAccess.load(fileName);

    const route = express.Router();

    // TODO post/delete single entity
    route.get("${route}/:${entityId}(\\\\d+)", (req, res) => {
        const id = Number.parseInt(req.params.${entityId}, undefined);
        const item = findSingle(${col}, id);
        if (item == null) {
            res.status(404).send("not found");
            return;
        }
        res.send(toPayload(item));
    });

    route.get("${route}", (req, res) => {
        const items = ${col}.items;
        res.send(items.map(toPayload));
    });

    route.post("${route}", (req, res) => {
        try {
            const parsed = parse${collection.entities.name}Input(req.body);
            if(!parsed.isSuccessful) {
                res.status(400).send(parsed.errorMessage);
                return;
            }
            const item = parsed.value;
            const added = append(${col}, item);
            DataAccess.save(fileName, ${col});
            const payload = toPayload(added);
            res.status(201).send(payload);
        } catch (error) {
            console.error(error);
            res.status(500).send("Internal Server Error.");
            return;
        }
    });

    return route;
}`;
}

function defineInputParsers(entity: Entity): string {
    const propParsers = entity.properties.map(p => definePropertyParser(entity, p)).join("\n");
    return `${propParsers}

function parse${entity.name}Input(data: any): Result<${entity.name}Input> {
    const obj = andThen(parseObject)(parseNotNull(data));
    const members: ParseObject<${entity.name}Input> = {
        ${entity.properties.map(p => `${p.key}: parse${p.key}(obj),`).join("\n\t\t")}
    };
    return resolve(members);
}`;
}

function definePropertyParser(entity: Entity, property: Property): string {
    if(property.type === "string") {
        return defineStringPropertyParser(entity.name, property.key, property.isNullable);
    }
    else {
        throw new Error("Properties other than strings are not implemented yet.");
    }
}

function defineStringPropertyParser(typeName: string, propName: string, isNullable: boolean): string {
    const parser = isNullable ? "parseNullableMember" : "parseRequiredMember";
    return `const parse${propName} = andThen(compose(${parser}<${typeName}Input>("${propName}"), parseString));`;
}