HTTP Interface
This commit is contained in:
+27
-2
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Config> {
|
||||
|
||||
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<Config> {
|
||||
});
|
||||
}
|
||||
|
||||
protected findElementByRequest(request: Request) {
|
||||
private createRoutes(): void {
|
||||
const NOT_IMPLEMENTED = (_req: express.Request, res: express.Response) => res.status(501).end();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private createRoutes()/*: express.Router*/ {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -19,24 +19,4 @@ export default abstract class AASInterfaceServer<ConfigInterface> {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user