From 855b3ba5c2f66d088198e2e2fb0b9265b786f9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Jim=C3=A9nez=20Pascual?= Date: Fri, 14 Apr 2023 00:09:30 +0200 Subject: [PATCH] as: Abstract percentages and concentration parsing These kind of of validation checks are so common and might appear even more as the schema grows. In the same way that definitions are used in the JSON schema for defining these types, here I've abstracted them to separate functions to facilitate the maintenance down the road. I've also added some test to increase coverage and explicitly test the latest additions. --- .../normalizedpayload/uplink.go | 42 +++++++------ .../normalizedpayload/uplink_test.go | 62 +++++++++++++++++++ 2 files changed, 86 insertions(+), 18 deletions(-) diff --git a/pkg/messageprocessors/normalizedpayload/uplink.go b/pkg/messageprocessors/normalizedpayload/uplink.go index 1cf083fda0c..2bd121322d6 100644 --- a/pkg/messageprocessors/normalizedpayload/uplink.go +++ b/pkg/messageprocessors/normalizedpayload/uplink.go @@ -141,6 +141,24 @@ func parseNumber(selector func(dst *Measurement) **float64, vals ...fieldValidat } } +// parsePercentage parses and validates a percentage. +func parsePercentage(selector func(dst *Measurement) **float64) fieldParser { + return parseNumber( + selector, + minimum(0.0), + maximum(100.0), + ) +} + +// parseConcentration parses and validates a concentration. Concentration must be in ppm between 0 and 1000000. +func parseConcentration(selector func(dst *Measurement) **float64) fieldParser { + return parseNumber( + selector, + minimum(0.0), + maximum(1000000.0), + ) +} + // minimum returns a field validator that checks the inclusive minimum. func minimum[T constraints.Ordered](min T) fieldValidator[T] { return func(v T, path string) error { @@ -212,12 +230,10 @@ var fieldParsers = map[string]fieldParser{ }, minimum(0.0), ), - "soil.moisture": parseNumber( + "soil.moisture": parsePercentage( func(dst *Measurement) **float64 { return &dst.Soil.Moisture }, - minimum(0.0), - maximum(100.0), ), "soil.temperature": parseNumber( func(dst *Measurement) **float64 { @@ -239,26 +255,20 @@ var fieldParsers = map[string]fieldParser{ minimum(0.0), maximum(14.0), ), - "soil.n": parseNumber( + "soil.n": parseConcentration( func(dst *Measurement) **float64 { return &dst.Soil.Nitrogen }, - minimum(0.0), - maximum(1000000.0), ), - "soil.p": parseNumber( + "soil.p": parseConcentration( func(dst *Measurement) **float64 { return &dst.Soil.Phosphorus }, - minimum(0.0), - maximum(1000000.0), ), - "soil.k": parseNumber( + "soil.k": parseConcentration( func(dst *Measurement) **float64 { return &dst.Soil.Potassium }, - minimum(0.0), - maximum(1000000.0), ), "air": object( func(dst *Measurement) *Air { @@ -271,12 +281,10 @@ var fieldParsers = map[string]fieldParser{ }, minimum(-273.15), ), - "air.relativeHumidity": parseNumber( + "air.relativeHumidity": parsePercentage( func(dst *Measurement) **float64 { return &dst.Air.RelativeHumidity }, - minimum(0.0), - maximum(100.0), ), "air.pressure": parseNumber( func(dst *Measurement) **float64 { @@ -285,12 +293,10 @@ var fieldParsers = map[string]fieldParser{ minimum(900.0), maximum(1100.0), ), - "air.co2": parseNumber( + "air.co2": parseConcentration( func(dst *Measurement) **float64 { return &dst.Air.CO2 }, - minimum(0.0), - maximum(1000000.0), ), "air.lightIntensity": parseNumber( func(dst *Measurement) **float64 { diff --git a/pkg/messageprocessors/normalizedpayload/uplink_test.go b/pkg/messageprocessors/normalizedpayload/uplink_test.go index 286f520a7fb..e023720cff1 100644 --- a/pkg/messageprocessors/normalizedpayload/uplink_test.go +++ b/pkg/messageprocessors/normalizedpayload/uplink_test.go @@ -62,6 +62,35 @@ func TestUplink(t *testing.T) { }, }, }, + { + name: "one soil nutrient concentration", + normalizedPayload: []*structpb.Struct{ + { + Fields: map[string]*structpb.Value{ + "soil": { + Kind: &structpb.Value_StructValue{ + StructValue: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "n": { + Kind: &structpb.Value_NumberValue{ + NumberValue: 999999.99, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: []normalizedpayload.Measurement{ + { + Soil: normalizedpayload.Soil{ + Nitrogen: float64Ptr(999999.99), + }, + }, + }, + }, { name: "two air temperatures", normalizedPayload: []*structpb.Struct{ @@ -122,6 +151,39 @@ func TestUplink(t *testing.T) { {}, }, }, + { + name: "above 100 percent soil moisture", + normalizedPayload: []*structpb.Struct{ + { + Fields: map[string]*structpb.Value{ + "soil": { + Kind: &structpb.Value_StructValue{ + StructValue: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "moisture": { + Kind: &structpb.Value_NumberValue{ + NumberValue: 120, + }, + }, + }, + }, + }, + }, + }, + }, + }, + expected: []normalizedpayload.Measurement{ + {}, + }, + expectedValidationErrors: [][]error{ + { + normalizedpayload.ErrFieldMaximum.WithAttributes( + "path", "soil.moisture", + "maximum", 100.0, + ), + }, + }, + }, { name: "below absolute zero", normalizedPayload: []*structpb.Struct{