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:
parent
249e4cf10c
commit
b74721ee76
4 changed files with 66 additions and 6 deletions
|
@ -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>
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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([])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
48
tests/fixtures/issues/gh-19-nullable-value.json
vendored
Normal file
48
tests/fixtures/issues/gh-19-nullable-value.json
vendored
Normal 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]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue