diff --git a/packages/td-tools/src/td-parser.ts b/packages/td-tools/src/td-parser.ts index feac00361..6c9d2d2f7 100644 --- a/packages/td-tools/src/td-parser.ts +++ b/packages/td-tools/src/td-parser.ts @@ -29,6 +29,32 @@ const logWarn = debug(`${namespace}:warn`); type AffordanceElement = PropertyElement | ActionElement | EventElement; +/** + * Initializes the affordances field of a thing with an empty object if its + * type should be incorrect or undefined. + * + * This avoids potential errors that could occur due to an undefined + * affordance field. + * + * @param thing The Thing whose affordance field is being adjusted. + * @param affordanceKey The key of the affordance field. + */ +function adjustAffordanceField(thing: Thing, affordanceKey: string) { + const affordance = thing[affordanceKey]; + + if (typeof affordance !== "object" || affordance == null) { + thing[affordanceKey] = {}; + } +} + +function adjustBooleanField(affordance: AffordanceElement, key: string) { + const currentValue = affordance[key]; + + if (currentValue === undefined || typeof currentValue !== "boolean") { + affordance[key] = false; + } +} + /** Parses a TD into a Thing object */ export function parseTD(td: string, normalize?: boolean): Thing { logDebug(`parseTD() parsing\n\`\`\`\n${td}\n\`\`\``); @@ -100,14 +126,6 @@ export function parseTD(td: string, normalize?: boolean): Thing { thing["@type"] = [TD.DEFAULT_THING_TYPE, semType]; } - const adjustBooleanField = (affordance: AffordanceElement, key: string) => { - const currentValue = affordance[key]; - - if (currentValue === undefined || typeof currentValue !== "boolean") { - affordance[key] = false; - } - }; - for (const property of Object.values(thing.properties ?? {})) { for (const key of ["readOnly", "writeOnly", "observable"]) { adjustBooleanField(property, key); @@ -120,10 +138,9 @@ export function parseTD(td: string, normalize?: boolean): Thing { } } - // avoid errors due to 'undefined' - thing.properties ??= {}; - thing.actions ??= {}; - thing.events ??= {}; + for (const affordanceKey of ["properties", "actions", "events"]) { + adjustAffordanceField(thing, affordanceKey); + } if (thing.security === undefined) { logWarn("parseTD() found no security metadata"); @@ -136,7 +153,7 @@ export function parseTD(td: string, normalize?: boolean): Thing { // collect all forms for normalization and use iterations also for checking const allForms = []; // properties - for (const [propName, prop] of Object.entries(thing.properties)) { + for (const [propName, prop] of Object.entries(thing.properties ?? {})) { // ensure forms mandatory forms field if (prop.forms == null) { throw new Error(`Property '${propName}' has no forms field`); @@ -153,7 +170,7 @@ export function parseTD(td: string, normalize?: boolean): Thing { } } // actions - for (const [actName, act] of Object.entries(thing.actions)) { + for (const [actName, act] of Object.entries(thing.actions ?? {})) { // ensure forms mandatory forms field if (act.forms == null) { throw new Error(`Action '${actName}' has no forms field`); @@ -170,7 +187,7 @@ export function parseTD(td: string, normalize?: boolean): Thing { } } // events - for (const [evtName, evt] of Object.entries(thing.events)) { + for (const [evtName, evt] of Object.entries(thing.events ?? {})) { // ensure forms mandatory forms field if (evt.forms == null) { throw new Error(`Event '${evtName}' has no forms field`);