Skip to content

Commit 89f71bb

Browse files
committed
parse isOptional from both optional attribute and conformance
1 parent d785a36 commit 89f71bb

8 files changed

Lines changed: 230 additions & 11 deletions

File tree

docs/api.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20032,6 +20032,7 @@ This module provides utilities for evaluating conformance expressions.
2003220032
* [~evaluateBooleanExpression(expr)](#module_Validation API_ Evaluate conformance expressions..evaluateConformanceExpression..evaluateBooleanExpression)
2003320033
* [~evaluateWithParentheses(expr)](#module_Validation API_ Evaluate conformance expressions..evaluateConformanceExpression..evaluateWithParentheses)
2003420034
* [~checkMissingTerms(expression, elementMap)](#module_Validation API_ Evaluate conformance expressions..checkMissingTerms) ⇒
20035+
* [~checkIfExpressionHasTerm(expression, term)](#module_Validation API_ Evaluate conformance expressions..checkIfExpressionHasTerm) ⇒
2003520036

2003620037
<a name="module_Validation API_ Evaluate conformance expressions..evaluateConformanceExpression"></a>
2003720038

@@ -20092,6 +20093,19 @@ If so, it means the conformance depends on terms with unknown values and changes
2009220093
| expression | <code>\*</code> |
2009320094
| elementMap | <code>\*</code> |
2009420095

20096+
<a name="module_Validation API_ Evaluate conformance expressions..checkIfExpressionHasTerm"></a>
20097+
20098+
### Validation API: Evaluate conformance expressions~checkIfExpressionHasTerm(expression, term) ⇒
20099+
Check if the expression contains a given term.
20100+
20101+
**Kind**: inner method of [<code>Validation API: Evaluate conformance expressions</code>](#module_Validation API_ Evaluate conformance expressions)
20102+
**Returns**: true if the expression contains the term, false otherwise
20103+
20104+
| Param |
20105+
| --- |
20106+
| expression |
20107+
| term |
20108+
2009520109
<a name="module_Validation API_ Parse conformance data from XML"></a>
2009620110

2009720111
## Validation API: Parse conformance data from XML
@@ -20101,6 +20115,7 @@ This module provides utilities for parsing conformance data from XML into expres
2010120115
* [Validation API: Parse conformance data from XML](#module_Validation API_ Parse conformance data from XML)
2010220116
* [~parseConformanceFromXML(operand)](#module_Validation API_ Parse conformance data from XML..parseConformanceFromXML) ⇒
2010320117
* [~parseConformanceRecursively(operand, depth, parentJoinChar)](#module_Validation API_ Parse conformance data from XML..parseConformanceRecursively) ⇒
20118+
* [~getOptionalAttributeFromXML(element)](#module_Validation API_ Parse conformance data from XML..getOptionalAttributeFromXML) ⇒
2010420119

2010520120
<a name="module_Validation API_ Parse conformance data from XML..parseConformanceFromXML"></a>
2010620121

@@ -20162,6 +20177,21 @@ When they appear, stop recursing and return the name inside directly
2016220177
| depth | <code>\*</code> | <code>0</code> |
2016320178
| parentJoinChar | <code>\*</code> | |
2016420179

20180+
<a name="module_Validation API_ Parse conformance data from XML..getOptionalAttributeFromXML"></a>
20181+
20182+
### Validation API: Parse conformance data from XML~getOptionalAttributeFromXML(element) ⇒
20183+
if optional attribute is defined, return its value
20184+
if optional attribute is undefined, check if the element conformance is mandatory
20185+
if both optional attribute and conformance are undefined, return false
20186+
Optional attribute takes precedence over conformance for backward compatibility on certain elements
20187+
20188+
**Kind**: inner method of [<code>Validation API: Parse conformance data from XML</code>](#module_Validation API_ Parse conformance data from XML)
20189+
**Returns**: true if the element is optional, false if the element is mandatory
20190+
20191+
| Param | Type |
20192+
| --- | --- |
20193+
| element | <code>\*</code> |
20194+
2016520195
<a name="module_Validation API_ Validation APIs"></a>
2016620196

2016720197
## Validation API: Validation APIs

src-electron/validation/conformance-expression-evaluator.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,18 @@ function checkMissingTerms(expression, elementMap) {
132132
return missingTerms
133133
}
134134

135+
/**
136+
* Check if the expression contains a given term.
137+
*
138+
* @param expression
139+
* @param term
140+
* @returns true if the expression contains the term, false otherwise
141+
*/
142+
function checkIfExpressionHasTerm(expression, term) {
143+
let terms = expression.match(/[A-Za-z][A-Za-z0-9_]*/g)
144+
return terms && terms.includes(term)
145+
}
146+
135147
exports.evaluateConformanceExpression = evaluateConformanceExpression
136148
exports.checkMissingTerms = checkMissingTerms
149+
exports.checkIfExpressionHasTerm = checkIfExpressionHasTerm

src-electron/validation/conformance-xml-parser.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
* @module Validation API: Parse conformance data from XML
2222
*/
2323

24+
const dbEnum = require('../../src-shared/db-enum')
25+
const conformEvaluator = require('./conformance-expression-evaluator')
26+
2427
/**
2528
* Parses conformance from XML data.
2629
* The conformance could come from features, attributes, commands, or events
@@ -155,4 +158,30 @@ function parseConformanceRecursively(operand, depth = 0, parentJoinChar = '') {
155158
}
156159
}
157160

161+
/**
162+
* if optional attribute is defined, return its value
163+
* if optional attribute is undefined, check if the element conformance is mandatory
164+
* if both optional attribute and conformance are undefined, return false
165+
* Optional attribute takes precedence over conformance for backward compatibility on certain elements
166+
*
167+
* @param {*} element
168+
* @returns true if the element is optional, false if the element is mandatory
169+
*/
170+
function getOptionalAttributeFromXML(element) {
171+
if (element.$.optional) {
172+
return element.$.optional == 'true'
173+
} else {
174+
let conformance = parseConformanceFromXML(element)
175+
if (conformance) {
176+
return !conformEvaluator.checkIfExpressionHasTerm(
177+
conformance,
178+
dbEnum.conformance.mandatory
179+
)
180+
} else {
181+
return false
182+
}
183+
}
184+
}
185+
158186
exports.parseConformanceFromXML = parseConformanceFromXML
187+
exports.getOptionalAttributeFromXML = getOptionalAttributeFromXML

src-electron/zcl/zcl-loader-silabs.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ function prepareCluster(cluster, context, isExtension = false) {
506506
name: command.$.name,
507507
description: command.description ? command.description[0].trim() : '',
508508
source: command.$.source,
509-
isOptional: command.$.optional == 'true' ? true : false,
509+
isOptional: conformParser.getOptionalAttributeFromXML(command),
510510
conformance: conformParser.parseConformanceFromXML(command),
511511
mustUseTimedInvoke: command.$.mustUseTimedInvoke == 'true',
512512
introducedIn: command.$.introducedIn,
@@ -567,7 +567,7 @@ function prepareCluster(cluster, context, isExtension = false) {
567567
conformance: conformParser.parseConformanceFromXML(event),
568568
priority: event.$.priority,
569569
description: event.description ? event.description[0].trim() : '',
570-
isOptional: event.$.optional == 'true',
570+
isOptional: conformParser.getOptionalAttributeFromXML(event),
571571
isFabricSensitive: event.$.isFabricSensitive == 'true'
572572
}
573573
ev.access = extractAccessIntoArray(event)
@@ -685,7 +685,7 @@ function prepareCluster(cluster, context, isExtension = false) {
685685
: null,
686686
isWritable: attribute.$.writable == 'true',
687687
defaultValue: attribute.$.default,
688-
isOptional: attribute.$.optional == 'true',
688+
isOptional: conformParser.getOptionalAttributeFromXML(attribute),
689689
reportingPolicy: reportingPolicy,
690690
storagePolicy: storagePolicy,
691691
isSceneRequired:

src-shared/db-enum.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,3 +247,12 @@ exports.featureMapAttribute = {
247247
name: 'FeatureMap',
248248
code: 65532
249249
}
250+
251+
exports.conformance = {
252+
mandatory: 'M',
253+
optional: 'O',
254+
disallowed: 'X',
255+
deprecated: 'D',
256+
provisional: 'P',
257+
desc: 'desc'
258+
}

test/test-query.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,17 @@ WHERE
255255
.then((rows) => rows.map(dbMapping.map.cluster))
256256
}
257257

258+
/**
259+
*
260+
* @param {*} elements
261+
* @param {*} name
262+
* @returns true if the element is optional, false if the element is mandatory
263+
*/
264+
function checkIfElementIsOptional(elements, name) {
265+
let element = elements.find((element) => element.name == name)
266+
return element ? element.isOptional : false
267+
}
268+
258269
exports.getAllEndpointTypeClusterState = getAllEndpointTypeClusterState
259270
exports.selectCountFrom = selectCountFrom
260271
exports.getEndpointTypeAttributes = getEndpointTypeAttributes
@@ -263,3 +274,4 @@ exports.createSession = createSession
263274
exports.getAllSessionNotifications = getAllSessionNotifications
264275
exports.getAllNotificationMessages = getAllNotificationMessages
265276
exports.getAllSessionClusters = getAllSessionClusters
277+
exports.checkIfElementIsOptional = checkIfElementIsOptional

test/zcl-loader.test.js

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const dbEnum = require('../src-shared/db-enum')
2323
const queryZcl = require('../src-electron/db/query-zcl')
2424
const queryDeviceType = require('../src-electron/db/query-device-type')
2525
const queryCommand = require('../src-electron/db/query-command')
26+
const queryEvent = require('../src-electron/db/query-event')
2627
const queryPackage = require('../src-electron/db/query-package')
2728
const queryPackageNotification = require('../src-electron/db/query-package-notification')
2829
const zclLoader = require('../src-electron/zcl/zcl-loader')
@@ -594,14 +595,14 @@ test(
594595
let ctx = await zclLoader.loadZcl(db, env.builtinMatterZclMetafile())
595596
let packageId = ctx.packageId
596597

597-
let zclCluster = await queryZcl.selectClusterByCode(db, packageId, 0x001f)
598+
let aclCluster = await queryZcl.selectClusterByCode(db, packageId, 0x001f)
598599

599600
/* Verify that the ACL attribute, defined using the list type format `array="true" type="X"` in XML,
600601
is correctly parsed and stored in the database as an array of AccessControlEntryStruct. */
601602
let attributes = await dbApi.dbAll(
602603
db,
603604
"SELECT * FROM ATTRIBUTE WHERE CLUSTER_REF = ? AND CODE = 0x0000 AND NAME = 'ACL'",
604-
[zclCluster.id]
605+
[aclCluster.id]
605606
)
606607
expect(attributes.length).toBe(1)
607608
let aclAttribute = attributes[0]
@@ -623,3 +624,107 @@ test(
623624
},
624625
testUtil.timeout.long()
625626
)
627+
628+
test(
629+
'test loading IS_OPTIONAL column for Matter attributes, commands, and events',
630+
async () => {
631+
let db = await dbApi.initRamDatabase()
632+
try {
633+
await dbApi.loadSchema(db, env.schemaFile(), env.zapVersion())
634+
let ctx = await zclLoader.loadZcl(db, env.builtinMatterZclMetafile())
635+
let packageId = ctx.packageId
636+
637+
let doorLockCluster = await queryZcl.selectClusterByCode(
638+
db,
639+
packageId,
640+
0x0101
641+
)
642+
let doorLockClusterId = doorLockCluster.id
643+
644+
let attributes =
645+
await queryZcl.selectAttributesByClusterIdIncludingGlobal(
646+
db,
647+
doorLockClusterId,
648+
[packageId]
649+
)
650+
// optional attribute undefined, conformance undefined -> mandatory
651+
expect(
652+
testQuery.checkIfElementIsOptional(attributes, 'ActuatorEnabled')
653+
).toBeFalsy()
654+
// optional="true", conformance undefined -> optional
655+
expect(
656+
testQuery.checkIfElementIsOptional(attributes, 'DoorClosedEvents')
657+
).toBeTruthy()
658+
// optional="false", conformance undefined -> mandatory
659+
expect(
660+
testQuery.checkIfElementIsOptional(attributes, 'LockType')
661+
).toBeFalsy()
662+
// mandatory conformance, optional attribute undefined -> mandatory
663+
expect(
664+
testQuery.checkIfElementIsOptional(attributes, 'OperatingMode')
665+
).toBeFalsy()
666+
// optionalConform to feature DPS, optional="true" -> optional
667+
expect(
668+
testQuery.checkIfElementIsOptional(attributes, 'DoorOpenEvents')
669+
).toBeTruthy()
670+
// mandatory conformance, optional="true" -> optional as optional="true" takes precedence
671+
expect(
672+
testQuery.checkIfElementIsOptional(attributes, 'LockState')
673+
).toBeTruthy()
674+
// mandatoryConform to feature DPS, optional="false" -> mandatory as optional="false" takes precedence
675+
expect(
676+
testQuery.checkIfElementIsOptional(attributes, 'DoorState')
677+
).toBeFalsy()
678+
679+
let commands = await queryCommand.selectCommandsByClusterId(
680+
db,
681+
doorLockClusterId,
682+
[packageId]
683+
)
684+
// optional attribute undefined, conformance undefined -> mandatory
685+
expect(
686+
testQuery.checkIfElementIsOptional(commands, 'LockDoor')
687+
).toBeFalsy()
688+
// optional="true", conformance undefined -> optional
689+
expect(
690+
testQuery.checkIfElementIsOptional(commands, 'GetWeekDaySchedule')
691+
).toBeTruthy()
692+
// optional conformance, optional="true" -> optional
693+
expect(
694+
testQuery.checkIfElementIsOptional(commands, 'UnlockWithTimeout')
695+
).toBeTruthy()
696+
// mandatoryConform to feature WDSCH, optional="true" -> optional
697+
expect(
698+
testQuery.checkIfElementIsOptional(commands, 'SetWeekDaySchedule')
699+
).toBeTruthy()
700+
// mandatory conformance, optional="true" -> optional as optional="true" takes precedence
701+
expect(
702+
testQuery.checkIfElementIsOptional(commands, 'UnlockDoor')
703+
).toBeTruthy()
704+
705+
let events = await queryEvent.selectEventsByClusterId(
706+
db,
707+
doorLockClusterId
708+
)
709+
// optional attribute undefined, conformance undefined -> mandatory
710+
expect(
711+
testQuery.checkIfElementIsOptional(events, 'LockUserChange')
712+
).toBeFalsy()
713+
// optional="true", conformance undefined -> optional
714+
expect(
715+
testQuery.checkIfElementIsOptional(events, 'LockOperation')
716+
).toBeTruthy()
717+
// mandatoryConform to feature DPS, optional="true" -> optional
718+
expect(
719+
testQuery.checkIfElementIsOptional(events, 'DoorStateChange')
720+
).toBeTruthy()
721+
// mandatory conformance, optional="true" -> optional as optional="true" takes precedence
722+
expect(
723+
testQuery.checkIfElementIsOptional(events, 'DoorLockAlarm')
724+
).toBeTruthy()
725+
} finally {
726+
await dbApi.closeDatabase(db)
727+
}
728+
},
729+
testUtil.timeout.long()
730+
)

0 commit comments

Comments
 (0)