From 627f3ac7306a33914f9955672a243e574b90f08c Mon Sep 17 00:00:00 2001 From: Daniel Kluge Date: Sat, 30 Dec 2023 10:16:14 +0100 Subject: [PATCH] Correct pathing --- example/src/modules/httpInterfaceServer.ts | 15 ++++--- example/src/modules/mqttConnector.ts | 4 +- lib/src/helper/traverser.ts | 47 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 8 deletions(-) diff --git a/example/src/modules/httpInterfaceServer.ts b/example/src/modules/httpInterfaceServer.ts index b1afaef..d24e672 100644 --- a/example/src/modules/httpInterfaceServer.ts +++ b/example/src/modules/httpInterfaceServer.ts @@ -161,7 +161,7 @@ export default class HTTPInterfaceServer extends AbstractInterfaceServer // Main function this.app.use("/aas/submodels/:smId/submodel-elements/*", (req, res) => { - const idShortPathString = (req.params as any)[0]; + const idShortPathString = (req.params as any)[0] as string; if (idShortPathString.endsWith("/")) res.status(400).end(); const sm = getSM(req.params.smId); @@ -185,18 +185,20 @@ export default class HTTPInterfaceServer extends AbstractInterfaceServer const endingMatch = endingMatchSearch?.[0]; const idShortPath = endingMatch ? idShortPathString.replace(new RegExp(`${endingMatch}$`), "").split("/") : idShortPathString.split("/"); + // We test both options of path, the correct one (map.entry.list[0].property) and the BaSyx one (map/entry/list/listElement/property) + const elem = idShortPath.length <= 1 ? Traverser.getElementByPath(this.aas, sm, idShortPath[0]) : Traverser.getElementByIdPath(this.aas, sm, idShortPath); switch (endingMatch) { case undefined: { if (req.method !== "GET") return res.status(501).end(); - const prop = Traverser.getElementByIdPath(this.aas, sm, idShortPath); + const prop = elem; if (prop === null || !AASCoreTypes.isProperty(prop)) return res.status(404).end(); return res.json(AASCoreJsonization.toJsonable(prop)).end(); } - case "/value": + case "/value": { if (req.method !== "GET" && req.method !== "PUT") return res.status(501).end(); - const prop = Traverser.getElementByIdPath(this.aas, sm, idShortPath); + const prop = elem; if (prop === null || !AASCoreTypes.isProperty(prop)) return res.status(404).end(); const request: Types.RequestTypes.Request = req.method === "GET" ? { type: "readProperty", target: prop } : { type: "writeProperty", target: prop, extraData: { value: req.body } }; @@ -207,11 +209,12 @@ export default class HTTPInterfaceServer extends AbstractInterfaceServer if (value === null) return res.status(500).end(); return res.json(value).end(); + } case "/attachment": return res.status(501).end(); case "/invoke": { if (req.method !== "POST") return res.status(405).end(); - const op = Traverser.getElementByIdPath(this.aas, sm, idShortPath); + const op = elem; if (op === null || !AASCoreTypes.isOperation(op)) return res.status(404).end(); try { @@ -223,7 +226,7 @@ export default class HTTPInterfaceServer extends AbstractInterfaceServer } case "/invoke-async": { if (req.method !== "POST") return res.status(405).end(); - const op = Traverser.getElementByIdPath(this.aas, sm, idShortPath); + const op = elem; if (op === null || !AASCoreTypes.isOperation(op)) return res.status(404).end(); const request: Types.RequestTypes.Request = { type: "invokeAction", target: op, extraData: { args: req.body, async: true } }; diff --git a/example/src/modules/mqttConnector.ts b/example/src/modules/mqttConnector.ts index 305a26a..dcec195 100644 --- a/example/src/modules/mqttConnector.ts +++ b/example/src/modules/mqttConnector.ts @@ -189,7 +189,7 @@ export default class MQTTConnector extends AbstractConnectionObject): any { this.assertConnected(); - const form: Types.AIDTypes.InterfaceFormMQTTAction = mapping.forms[0]; + const form: Types.AIDTypes.InterfaceFormMQTTAction = mapping.forms[0] as Types.AIDTypes.InterfaceFormMQTTAction; const topic = MQTTConnector.getTopicFromForm(form); if (topic === undefined || !mapping.forms) throw new Error("Mapping invalid."); @@ -215,7 +215,7 @@ export default class MQTTConnector extends AbstractConnectionObject): string { this.assertConnected(); - const form: Types.AIDTypes.InterfaceFormMQTTAction = mapping.forms[0]; + const form: Types.AIDTypes.InterfaceFormMQTTAction = mapping.forms[0] as Types.AIDTypes.InterfaceFormMQTTAction; const topic = MQTTConnector.getTopicFromForm(form); if (topic === undefined || !mapping.forms) throw new Error("Mapping invalid."); diff --git a/lib/src/helper/traverser.ts b/lib/src/helper/traverser.ts index 3f9c403..cfe8913 100644 --- a/lib/src/helper/traverser.ts +++ b/lib/src/helper/traverser.ts @@ -110,6 +110,39 @@ export default class Traverser { return current; } + /** + * Traverse donwards from a start element by using a path. + * @param start Start element + * @param path a JS-linke path, eg. map.entry.list[0].property + * @returns Target element at the end of the path or null if not found + * @public + */ + public static traverseByPath(start: types.Class, path: string): types.Class | null { + const ids = path.split("."); + let current: types.Class | null = start; + const regex = /(\[\d+\])+$/i + + for (const idShort of ids) { + if (!idShort) continue; + if (current === null) return null; + + current = Traverser.findChildByIdShort(current, idShort); + const match = regex.exec(idShort)?.[0]; + if (match) { + const indexes = match.split("][").map(index => parseInt(index.replace("[", "").replace("]", ""))); + + for (const index of indexes) { + if (current === null || !types.isSubmodelElementList(current) || current.value === null) return null; + const next = current.value?.at(index); + if (next === undefined) return null; + current = next; + } + } + } + + return current; + } + /** * Get element by using idShorts from an submodel. * @param env Environment to search in @@ -124,6 +157,20 @@ export default class Traverser { return Traverser.traverseByShortIds(sm, idShorts); } + /** + * Get element by using a path from an submodel. + * @param env Environment to search in + * @param submodelOrIdShort Submodel element or Submodel idShort + * @param path path to traverse by + * @returns Target element at the end of the path or null if not found + * @public + */ + public static getElementByPath(env: types.Environment, submodelOrIdShort: string | types.Submodel, path: string): types.Class | null { + const sm = typeof submodelOrIdShort === "string" ? Traverser.findSMByIdShort(env, submodelOrIdShort) : submodelOrIdShort; + if (sm === null) return null; + return Traverser.traverseByPath(sm, path); + } + /** * Find an element using a function to check. * @param start Start element