diff --git a/lib/src/aasHelper.ts b/lib/src/aasHelper.ts index 3fcd38b..ce845b5 100644 --- a/lib/src/aasHelper.ts +++ b/lib/src/aasHelper.ts @@ -70,11 +70,36 @@ export default class AASHelper { return current; } - public static findElement(environment: types.Class, checkFunction: (element: types.Class) => boolean): types.Class | null { - for (const element of environment.descend()) { + public static traverseByShortIds(start: types.Class, shortIds: string[]): types.Class | null { + let current: types.Class | null = start; + for (const shortId of shortIds) { + if (current === null) break; + + current = AASHelper.findChildByIdShort(current, shortId); + } + + return current; + } + + public static getElementByIdPath(env: types.Environment, submodelOrIdShort: string | types.Submodel, idShorts: string[]): types.Class | null { + const sm = typeof submodelOrIdShort === "string" ? AASHelper.findSMByIdShort(env, submodelOrIdShort) : submodelOrIdShort; + if (sm === null) return null; + return AASHelper.traverseByShortIds(sm, idShorts); + } + + public static findElement(start: types.Class, checkFunction: (element: types.Class) => boolean): types.Class | null { + for (const element of start.descend()) { if (checkFunction(element)) return element; } return null; } + + public static findChildByIdShort(start: types.Class, idShort: string): types.Class | null { + for (const child of start.descendOnce()) { + if ((child as any).idShort === idShort) return child; + } + + return null; + } } \ No newline at end of file diff --git a/lib/src/example_modules/httpInterfaceServer.ts b/lib/src/example_modules/httpInterfaceServer.ts index a000092..dfb91c8 100644 --- a/lib/src/example_modules/httpInterfaceServer.ts +++ b/lib/src/example_modules/httpInterfaceServer.ts @@ -2,7 +2,8 @@ import type { Express } from "express"; import express from "express"; import { jsonization, types } from "@aas-core-works/aas-core3.0-typescript"; import AASInterfaceServer from "../server"; -import type { OnRequestCallback, Request } from "types/requests"; +import type { OnRequestCallback } from "types/requests"; +import AASHelper from "../aasHelper"; export type Config = { bindAddress: string, @@ -23,9 +24,7 @@ export default class HTTPInterfaceServer extends AASInterfaceServer { public prepare(): void { this.app.use(express.json()); - this.app.get("/aas", (_, res) => { - res.json(jsonization.toJsonable(this.aas)); - }); + this.createRoutes(); } public run(): void { @@ -45,11 +44,154 @@ export default class HTTPInterfaceServer extends AASInterfaceServer { }); } - protected findElementByRequest(request: Request) { - - } + private createRoutes(): void { + const NOT_IMPLEMENTED = (_req: express.Request, res: express.Response) => res.status(501).end(); - private createRoutes()/*: express.Router*/ { - // TODO + const getSM = (id: string) => { + if (!id) return null; + + try { + // This is what the specification says + const decoded = Buffer.from(id, "base64").toString("utf-8"); + let sm = AASHelper.findSMById(this.aas, decoded); + + if (sm === null) { + // Normally, at least in BaSyx just the shortId is used, so just test if we find something like this + sm = AASHelper.findSMByIdShort(this.aas, id); + } + + return sm; + } catch { + return null; + } + } + + // /aas + this.app.get("/aas", (_, res) => res.json(jsonization.toJsonable(this.aas)).end()); + this.app.put("/aas", NOT_IMPLEMENTED); + this.app.delete("/aas", NOT_IMPLEMENTED); + // /aas/$reference + this.app.get("/aas/$reference", NOT_IMPLEMENTED); + // /aas/asset-information + this.app.get("/aas/asset-information", NOT_IMPLEMENTED); + this.app.put("/aas/asset-information", NOT_IMPLEMENTED); + this.app.get("/aas/asset-information/thumbnail", NOT_IMPLEMENTED); + this.app.put("/aas/asset-information/thumbnail", NOT_IMPLEMENTED); + this.app.delete("/aas/asset-information/thumbnail", NOT_IMPLEMENTED); + // /aas/submodel-refs + this.app.get("/aas/submodel-refs", NOT_IMPLEMENTED); + this.app.post("/aas/submodel-refs", NOT_IMPLEMENTED); + this.app.delete("/aas/submodel-refs/:submodelIdentifier", NOT_IMPLEMENTED); + // /aas/submodels + this.app.get("/aas/submodels/:smId", (req, res) => { + const sm = getSM(req.params.smId); + + if (sm === null) return res.status(404).end(); + return res.json(jsonization.toJsonable(sm)).end() + }); + this.app.put("/aas/submodels/:smId", NOT_IMPLEMENTED); + this.app.patch("/aas/submodels/:smId", NOT_IMPLEMENTED); + this.app.delete("/aas/submodels/:smId", NOT_IMPLEMENTED); + // /aas/submodels/:smId/submodel-elements + this.app.use("/aas/submodels/:smId/submodel-elements", (req, res) => { + if (req.method !== "GET") return res.status(501).end(); + + const sm = getSM(req.params.smId); + + if (sm === null || sm.submodelElements === null) return res.status(404).end(); + return res.json(sm.submodelElements.map(element => jsonization.toJsonable(element))).end(); + }); + + // Main function + this.app.use("/aas/submodels/:smId/submodel-elements/*", (req, res) => { + const idShortPathString = (req.params as any)[0]; + console.log(idShortPathString); + if (idShortPathString.endsWith("/")) res.status(400).end(); + + const sm = getSM(req.params.smId); + if (sm === null || sm.submodelElements === null) return res.status(404).end(); + + if (idShortPathString === "") { + if (req.method === "GET") { + const json = sm.submodelElements.map(element => jsonization.toJsonable(element)); + return res.json(json).end(); + } + } + + // Currently we don't support these operations + if (idShortPathString.endsWith("$value") || + idShortPathString.endsWith("$reference") || + idShortPathString.endsWith("$metadata") || + idShortPathString.endsWith("$path")) return res.status(501).end(); + + // If operation ids are changed, you can change the format here + const endingMatchSearch = idShortPathString.match(/(\/attachment$|\/invoke$|\/invoke-async$|\/operation-status\/[a-zA-Z0-9\-]+$|\/operation-result\/[a-zA-Z0-9\-]+$)/) + const endingMatch = endingMatchSearch?.[0]; + + const idShortPath = endingMatch ? idShortPathString.replace(new RegExp(`${endingMatch}$`), "").split("/") : idShortPathString.split("/"); + + + switch (endingMatch) { + case undefined: { + if (req.method !== "GET") return res.status(501).end(); + const prop = AASHelper.getElementByIdPath(this.aas, sm, idShortPath); + + // TODO + //! Check if mapping and live data is available! + + if (prop === null) return res.status(404).end(); + return res.json(jsonization.toJsonable(prop)).end(); + } + case "/attachment": + return res.status(501).end(); + case "/invoke": { + if (req.method !== "POST") return res.status(405).end(); + + // TODO + // Mapping, dann Befehl + // Parameter? + + return res.send(`Operation result for ${idShortPath}`).end(); + } + case "/invoke-async": { + if (req.method !== "POST") return res.status(405).end(); + + // TODO + // Invoke operation and get handle + + const handle = "TODO"; + + const operationPaths = [`${idShortPath.join("/")}/operation-status/${handle}`, `${idShortPath.join("/")}/operation-result/${handle}`]; + return res.status(202).header("Location", operationPaths).end(); + } + default: { + // operation-status, operation-result or anything else + const [_, operation, handle] = endingMatch.split("/"); + + switch (operation) { + case "operation-status": { + if (req.method !== "GET") return res.status(405).end(); + // TODO + return res.json({ + "status": "TODO", + handle + }).end(); + } + case "operation-result": { + if (req.method !== "GET") return res.status(405).end(); + // TODO + return res.json({ + "status": "TODO", + handle + }).end(); + } + default: { + return res.status(404).end(); + } + } + } + } + + }) } } \ No newline at end of file diff --git a/lib/src/server.ts b/lib/src/server.ts index cdd2474..2acfef4 100644 --- a/lib/src/server.ts +++ b/lib/src/server.ts @@ -19,24 +19,4 @@ export default abstract class AASInterfaceServer { public abstract stop(): void; public abstract notify(event: any): void; - protected abstract findElementByRequest(request: Request): any | null; - - protected findElementBySemanticId(id: string): types.Class | null { - for (const element of this.aas.descend()) { - if ((element as any).semanticId === id) { - return element; - } - } - return null; - } - - protected findElementByShortId(id: string): types.Class | null { - for (const element of this.aas.descend()) { - if ((element as any).idShort === id) { - return element; - } - } - return null; - } - } \ No newline at end of file