Handle null values in characteristics (#20)

Apparently `characteristic.value` can also be null. Let’s handle it
properly. Fixes #19
This commit is contained in:
Lars Strojny 2022-11-13 11:13:13 +01:00 committed by GitHub
parent 249e4cf10c
commit b74721ee76
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 66 additions and 6 deletions

View file

@ -1,4 +1,4 @@
import z from 'zod' import z, { type ZodNull, type ZodOptional, type ZodType, type ZodUnion } from 'zod'
const NumericTypesBoundary = z.union([ const NumericTypesBoundary = z.union([
z.literal('bool'), z.literal('bool'),
@ -11,17 +11,21 @@ const NumericTypesBoundary = z.union([
]) ])
export type NumericTypes = z.infer<typeof NumericTypesBoundary> export type NumericTypes = z.infer<typeof NumericTypesBoundary>
function optionalNullable<T extends ZodType>(type: T): ZodOptional<ZodUnion<[ZodNull, T]>> {
return z.optional(z.union([z.null(), type]))
}
export const CharacteristicBoundary = z.intersection( export const CharacteristicBoundary = z.intersection(
z.object({ type: z.string(), description: z.string() }), z.object({ type: z.string(), description: z.string() }),
z.union([ z.union([
z.object({ z.object({
format: NumericTypesBoundary, format: NumericTypesBoundary,
value: z.optional(z.number()),
unit: z.optional(z.string()), unit: z.optional(z.string()),
value: optionalNullable(z.number()),
}), }),
z.object({ format: z.literal('string'), value: z.string() }), z.object({ format: z.literal('string'), value: optionalNullable(z.string()) }),
z.object({ format: z.literal('data'), value: z.optional(z.string()) }), z.object({ format: z.literal('data'), value: optionalNullable(z.string()) }),
z.object({ format: z.literal('tlv8'), value: z.string(z.string()) }), z.object({ format: z.literal('tlv8'), value: optionalNullable(z.string()) }),
]), ]),
) )
export type Characteristic = z.infer<typeof CharacteristicBoundary> export type Characteristic = z.infer<typeof CharacteristicBoundary>

View file

@ -42,7 +42,7 @@ export function aggregate(devices: Device[], timestamp: Date): Metric[] {
case 'uint16': case 'uint16':
case 'uint32': case 'uint32':
case 'uint64': case 'uint64':
if (typeof characteristic.value !== 'undefined') { if (characteristic.value != null) {
if (METRICS_FILTER.includes(characteristic.description)) { if (METRICS_FILTER.includes(characteristic.description)) {
break break
} }
@ -108,6 +108,7 @@ function getServiceLabels(service: Service): Record<string, string> {
for (const characteristic of service.characteristics) { for (const characteristic of service.characteristics) {
if ( if (
characteristic.value != null &&
characteristic.format === 'string' && characteristic.format === 'string' &&
[ [
'Name', 'Name',

View file

@ -6,6 +6,7 @@ import emptyData from './fixtures/empty.json'
import tpLinkData from './fixtures/tp-link.json' import tpLinkData from './fixtures/tp-link.json'
import harmonyData from './fixtures/harmony.json' import harmonyData from './fixtures/harmony.json'
import unknownUuidData from './fixtures/issues/gh-9-unknown-uuid.json' import unknownUuidData from './fixtures/issues/gh-9-unknown-uuid.json'
import nullableValueData from './fixtures/issues/gh-19-nullable-value.json'
describe('Metrics aggregator', () => { describe('Metrics aggregator', () => {
const timestamp = new Date('2000-01-01 00:00:00 UTC') const timestamp = new Date('2000-01-01 00:00:00 UTC')
@ -171,4 +172,10 @@ describe('Metrics aggregator', () => {
new Metric('custom_restart', 0, timestamp, expectedLabels), new Metric('custom_restart', 0, timestamp, expectedLabels),
]) ])
}) })
test('Aggregates metrics with nullable values', () => {
const unknowmnValue = DeviceBoundary.parse(nullableValueData)
expect(aggregate([unknowmnValue], timestamp)).toEqual([])
})
}) })

View file

@ -0,0 +1,48 @@
{
"ipAddress": "192.168.0.1",
"instance": {
"host": "192.168.0.1",
"port": 51826,
"url": "http://192.168.0.1:51826",
"deviceID": "AA:AA:AA:AA:AA:AA",
"txt": {
"c#": "5",
"ff": "0",
"id": "AA:AA:AA:AA:AA:AA",
"md": "homebridge",
"pv": "1.1",
"s#": "1",
"sf": "0",
"ci": "2",
"sh": "Hv0v9A=="
},
"name": "Test bridge"
},
"accessories": {
"accessories": [
{
"aid": 3,
"services": [
{
"type": "00000012-0000-1000-8000-656261617577",
"iid": 8,
"characteristics": [
{
"type": "00000073-0000-1000-8000-0026BB765291",
"iid": 11,
"value": null,
"perms": ["ev", "pr"],
"description": "Programmable Switch Event",
"format": "uint8",
"minValue": 0,
"maxValue": 2,
"minStep": 1,
"valid-values": [0]
}
]
}
]
}
]
}
}