Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AID] Handling of semantic annotations #54

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion node/aas-aid/src/asset-interfaces-description.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1213,12 +1213,15 @@ export class AssetInterfacesDescription {
for (const propertyKey in td.properties) {
const property: PropertyElement = td.properties[propertyKey];

// we get all the property keys and remove known keys by the TD spec at each if clause
let propertyKeys: any = Object.keys(property);
egekorkan marked this conversation as resolved.
Show resolved Hide resolved
// check whether form exists for a given protocol (prefix)
const formElementPicked = this.getFormForProtocol(property, protocol);
if (formElementPicked === undefined) {
// do not add this property, since there will be no href of interest
continue;
}
propertyKeys.splice(propertyKeys.indexOf("forms"), 1);

const propertyValues: Array<unknown> = [];
// type
Expand All @@ -1230,6 +1233,7 @@ export class AssetInterfacesDescription {
value: property.type,
modelType: "Property",
});
propertyKeys.splice(propertyKeys.indexOf("type"), 1);
// special AID treatment
if (property.minimum != null || property.maximum != null) {
const minMax: { [k: string]: unknown } = {
Expand All @@ -1246,12 +1250,14 @@ export class AssetInterfacesDescription {
(minMax.supplementalSemanticIds as Array<unknown>).push(
this.createSemanticId("https://www.w3.org/2019/wot/json-schema#minimum")
);
propertyKeys.splice(propertyKeys.indexOf("minimum"), 1);
}
if (property.maximum != null) {
minMax.max = property.maximum.toString();
(minMax.supplementalSemanticIds as Array<unknown>).push(
this.createSemanticId("https://www.w3.org/2019/wot/json-schema#maximum")
);
propertyKeys.splice(propertyKeys.indexOf("maximum"), 1);
}
propertyValues.push(minMax);
}
Expand All @@ -1270,12 +1276,14 @@ export class AssetInterfacesDescription {
(itemsRange.supplementalSemanticIds as Array<unknown>).push(
this.createSemanticId("https://www.w3.org/2019/wot/json-schema#minItems")
);
propertyKeys.splice(propertyKeys.indexOf("minItems"), 1);
}
if (property.maxItems != null) {
itemsRange.max = property.maxItems.toString();
(itemsRange.supplementalSemanticIds as Array<unknown>).push(
this.createSemanticId("https://www.w3.org/2019/wot/json-schema#maxItems")
);
propertyKeys.splice(propertyKeys.indexOf("maxItems"), 1);
}
propertyValues.push(itemsRange);
}
Expand All @@ -1294,12 +1302,14 @@ export class AssetInterfacesDescription {
(lengthRange.supplementalSemanticIds as Array<unknown>).push(
this.createSemanticId("https://www.w3.org/2019/wot/json-schema#minLength")
);
propertyKeys.splice(propertyKeys.indexOf("minLength"), 1);
}
if (property.maxLength != null) {
lengthRange.max = property.maxLength.toString();
(lengthRange.supplementalSemanticIds as Array<unknown>).push(
this.createSemanticId("https://www.w3.org/2019/wot/json-schema#maxLength")
);
propertyKeys.splice(propertyKeys.indexOf("maxLength"), 1);
}
propertyValues.push(lengthRange);
}
Expand All @@ -1313,10 +1323,12 @@ export class AssetInterfacesDescription {
value: property.title,
modelType: "Property",
});
propertyKeys.splice(propertyKeys.indexOf("title"), 1);
}
// description
if (property.description != null) {
// AID deals with description in level above
propertyKeys.splice(propertyKeys.indexOf("description"), 1);
}
// observable (if it deviates from the default == false only)
if (property.observable != null && property.observable === true) {
Expand All @@ -1327,6 +1339,7 @@ export class AssetInterfacesDescription {
value: `${property.observable}`, // in AID represented as string
modelType: "Property",
});
propertyKeys.splice(propertyKeys.indexOf("observable"), 1);
}
// contentMediaType
if (property.contentMediaType != null) {
Expand All @@ -1339,6 +1352,7 @@ export class AssetInterfacesDescription {
value: property.contentMediaType,
modelType: "Property",
});
propertyKeys.splice(propertyKeys.indexOf("contentMediaType"), 1);
}
// TODO enum
// const
Expand All @@ -1349,6 +1363,7 @@ export class AssetInterfacesDescription {
value: property.const,
modelType: "Property",
});
propertyKeys.splice(propertyKeys.indexOf("const"), 1);
}
// default
if (property.default != null) {
Expand All @@ -1359,19 +1374,31 @@ export class AssetInterfacesDescription {
value: property.default,
modelType: "Property",
});
propertyKeys.splice(propertyKeys.indexOf("default"), 1);
}
// unit
if (property.unit != null) {
// TODO: handle if value has : in it
propertyValues.push({
idShort: "unit",
valueType: "xs:string",
value: property.unit,
modelType: "Property",
});
propertyKeys.splice(propertyKeys.indexOf("unit"), 1);
}
// TODO items

// readOnly and writeOnly marked as EXTERNAL in AID spec
if (property.readOnly != null) {
propertyKeys.splice(propertyKeys.indexOf("readOnly"), 1);
// TODO: readOnly marked as EXTERNAL in AID spec
}

if (property.writeOnly != null) {
propertyKeys.splice(propertyKeys.indexOf("writeOnly"), 1);
egekorkan marked this conversation as resolved.
Show resolved Hide resolved
// TODO: writeOnly marked as EXTERNAL in AID spec
}

// range and others? Simply add them as is?

// forms
Expand Down Expand Up @@ -1498,6 +1525,59 @@ export class AssetInterfacesDescription {
});
}

// Handling of any key that has : in it by creating semantic ids for each
// no need to bother if there is no object in the context or it is a string
const tdContext = td["@context"];
let prefixObject: any = {};

if (Array.isArray(tdContext)) {
// iterate through the array and add key-value pairs of each object to a single one
tdContext.forEach((arrayItem) => {
if (typeof arrayItem === "object") {
// insert every pair into the prefixObject
for (const key in arrayItem) {
prefixObject[key] = arrayItem[key as keyof typeof arrayItem];
}
} else if (typeof arrayItem === "string") {
// it is either the standard context or another non-prefix term.
// in second case, it is not useful without full rdf processing
} else {
//probably a json-ld error?
}
});
} else {
// then it is a simple string
}

// iterate through the remaining property keys to add extended semantic ids
propertyKeys.forEach((element: string) => {
if (element.includes(":")) {
// find the prefix
const colonLoc = element.indexOf(":");
const prefix = element.slice(0, colonLoc);

// check if the prefix is in the @context
if (prefixObject.hasOwnProperty(prefix)) {
const baseUri = prefixObject[prefix];

const semanticId = baseUri + element.slice(colonLoc + 1);

propertyValues.push({
idShort: this.sanitizeIdShort(element),
semanticId: this.createSemanticId(semanticId),
valueType: this.getSimpleValueTypeXsd(property.element),
value: property[element],
modelType: "Property",
});
} else {
// TODO: What to do for unknown keys by the TD spec, have a : but not present in the @context?
// Is this a JSON-LD bug? No error in JSON-LD playground
}
} else {
// TODO: What to do for unknown keys by the TD spec and do not have a :, thus not in the context with a prefix. Should we do full json-ld processing?
}
});

properties.push({
idShort: propertyKey,
description,
Expand Down
86 changes: 86 additions & 0 deletions node/aas-aid/test/asset-interfaces-description-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1327,4 +1327,90 @@ class AssetInterfaceDescriptionTest {
expect(isValid.valid, isValid.errors).to.equal(true);
expect(submodel).to.have.property("submodelElements").to.be.an("array").to.have.lengthOf(1);
}

td4: ThingDescription = {
"@context": [
"https://www.w3.org/2022/wot/td/v1.1",
{
myPrefix1: "https://example.com/myContext1",
myPrefix2: "https://example.com/myContext2",
},
],
title: "test thing",
"@type": ["myPrefix1:suffix1", "myPrefix2:suffix2"],
securityDefinitions: {
nosec_sc: {
scheme: "nosec",
},
},
base: "http://example.com:3003",
security: ["nosec_sc"],
"myPrefix2:suffix3": "value1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not part of the test?

properties: {
myProperty1: {
type: "number",
title: "Property Title",
"myPrefix1:suffix4": "myPrefix2:value4",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really understand the idea/system/schema behind the attached numbers but it is fine by me

unit: "myPrefix2:value2",
forms: [
{
href: "relative/value",
contentType: "application/json",
"htv:methodName": "GET",
},
],
},
},
};

@test async "should correctly include semantic annotations into AAS from TD"() {
const sm = this.assetInterfacesDescription.transformTD2AID(JSON.stringify(this.td4));

const smObj = JSON.parse(sm);
console.log("###\n\n" + JSON.stringify(smObj) + "\n\n###");
// const isValid = this.validateAID(smObj);
// expect(isValid.valid, isValid.errors).to.equal(true);
Comment on lines +1370 to +1372
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove console log and commented code?

expect(smObj).to.have.property("idShort").that.equals("AssetInterfacesDescription");
const smInterface = smObj.submodelElements[0];

// InteractionMetadata with properties etc
let hasInteractionMetadata = false;
for (const smValue of smInterface.value) {
if (smValue.idShort === "InteractionMetadata") {
hasInteractionMetadata = true;
expect(smValue).to.have.property("value").to.be.an("array").to.have.lengthOf.greaterThan(0);
let hasProperties = false;
for (const interactionValues of smValue.value) {
if (interactionValues.idShort === "properties") {
hasProperties = true;
expect(interactionValues)
.to.have.property("value")
.to.be.an("array")
.to.have.lengthOf.greaterThan(0);
let hasProperty1 = false;
for (const propertyValue of interactionValues.value) {
if (propertyValue.idShort === "myProperty1") {
hasProperty1 = true;
expect(propertyValue)
.to.have.property("value")
.to.be.an("array")
.to.have.lengthOf.greaterThan(0);
let hasMyPrefix1_suffix4 = false;
for (const propProperty of propertyValue.value) {
if (propProperty.idShort === "myPrefix1_suffix4") {
hasMyPrefix1_suffix4 = true;
expect(propProperty.value).to.equal("myPrefix2:value4");
}
}
expect(hasMyPrefix1_suffix4).to.equal(true);
}
}
expect(hasProperty1).to.equal(true);
}
}
expect(hasProperties).to.equal(true);
}
}
expect(hasInteractionMetadata, "No InteractionMetadata").to.equal(true);
}
}
2 changes: 1 addition & 1 deletion node/aas-aid/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@
"strictNullChecks": true
},
"include": ["*.ts", "**/*.ts", "../src/**/*.ts", "**/AIDSchema.json"],
"exclude": []
"exclude": ["./dist/**/*"]
egekorkan marked this conversation as resolved.
Show resolved Hide resolved
}
Loading