From 939401f90db28756c43b87557292ebd2e61d09d5 Mon Sep 17 00:00:00 2001 From: Rob Gordon Date: Mon, 14 Mar 2022 15:12:27 -0700 Subject: [PATCH] Throw proper exception when TypeNameMatch expression is missing --- .../IotJsonPathContentTemplateFactory.cs | 1 - .../IotJsonPathLegacyMeasurementExtractor.cs | 2 +- .../JsonPathExpressionEvaluatorFactory.cs | 2 +- .../LegacyMeasurementExtractor.cs | 2 +- .../MeasurementExtractor.cs | 15 ++++++++- .../TemplateExpressionEvaluatorFactory.cs | 2 +- .../IotJsonPathContentTemplateTests.cs | 32 ++++++++++++++++++- .../JsonPathContentTemplateTests.cs | 27 +++++++++++++++- 8 files changed, 75 insertions(+), 8 deletions(-) diff --git a/src/lib/Microsoft.Health.Fhir.Ingest.Template/IotJsonPathContentTemplateFactory.cs b/src/lib/Microsoft.Health.Fhir.Ingest.Template/IotJsonPathContentTemplateFactory.cs index a93ecd86..cdae2fa7 100644 --- a/src/lib/Microsoft.Health.Fhir.Ingest.Template/IotJsonPathContentTemplateFactory.cs +++ b/src/lib/Microsoft.Health.Fhir.Ingest.Template/IotJsonPathContentTemplateFactory.cs @@ -4,7 +4,6 @@ // ------------------------------------------------------------------------------------------------- using EnsureThat; -using Microsoft.Health.Fhir.Ingest.Template; using Newtonsoft.Json.Linq; namespace Microsoft.Health.Fhir.Ingest.Template diff --git a/src/lib/Microsoft.Health.Fhir.Ingest.Template/IotJsonPathLegacyMeasurementExtractor.cs b/src/lib/Microsoft.Health.Fhir.Ingest.Template/IotJsonPathLegacyMeasurementExtractor.cs index 2a433396..1ed45300 100644 --- a/src/lib/Microsoft.Health.Fhir.Ingest.Template/IotJsonPathLegacyMeasurementExtractor.cs +++ b/src/lib/Microsoft.Health.Fhir.Ingest.Template/IotJsonPathLegacyMeasurementExtractor.cs @@ -11,7 +11,7 @@ namespace Microsoft.Health.Fhir.Ingest.Template { public class IotJsonPathLegacyMeasurementExtractor : LegacyMeasurementExtractor { - private IotJsonPathContentTemplate _template; + private readonly IotJsonPathContentTemplate _template; public IotJsonPathLegacyMeasurementExtractor( IotJsonPathContentTemplate template) diff --git a/src/lib/Microsoft.Health.Fhir.Ingest.Template/JsonPathExpressionEvaluatorFactory.cs b/src/lib/Microsoft.Health.Fhir.Ingest.Template/JsonPathExpressionEvaluatorFactory.cs index f92c2b1e..73c96f2d 100644 --- a/src/lib/Microsoft.Health.Fhir.Ingest.Template/JsonPathExpressionEvaluatorFactory.cs +++ b/src/lib/Microsoft.Health.Fhir.Ingest.Template/JsonPathExpressionEvaluatorFactory.cs @@ -14,7 +14,7 @@ public class JsonPathExpressionEvaluatorFactory : IExpressionEvaluatorFactory { public IExpressionEvaluator Create(TemplateExpression expression) { - EnsureArg.IsNotEmptyOrWhiteSpace(expression?.Value, nameof(expression.Value)); + EnsureArg.IsNotNullOrWhiteSpace(expression?.Value, nameof(expression.Value)); var expressionLanguage = expression.Language ?? TemplateExpressionLanguage.JsonPath; if (expressionLanguage != TemplateExpressionLanguage.JsonPath) diff --git a/src/lib/Microsoft.Health.Fhir.Ingest.Template/LegacyMeasurementExtractor.cs b/src/lib/Microsoft.Health.Fhir.Ingest.Template/LegacyMeasurementExtractor.cs index 71f05010..cdcbfe64 100644 --- a/src/lib/Microsoft.Health.Fhir.Ingest.Template/LegacyMeasurementExtractor.cs +++ b/src/lib/Microsoft.Health.Fhir.Ingest.Template/LegacyMeasurementExtractor.cs @@ -27,7 +27,7 @@ public LegacyMeasurementExtractor( protected override IEnumerable MatchTypeTokens(JObject token) { EnsureArg.IsNotNull(token, nameof(token)); - var evaluator = ExpressionEvaluatorFactory.Create(Template.TypeMatchExpression); + var evaluator = CreateRequiredExpressionEvaluator(Template.TypeMatchExpression, nameof(Template.TypeMatchExpression)); return evaluator.SelectTokens(token); } diff --git a/src/lib/Microsoft.Health.Fhir.Ingest.Template/MeasurementExtractor.cs b/src/lib/Microsoft.Health.Fhir.Ingest.Template/MeasurementExtractor.cs index c85970e1..c9d5f209 100644 --- a/src/lib/Microsoft.Health.Fhir.Ingest.Template/MeasurementExtractor.cs +++ b/src/lib/Microsoft.Health.Fhir.Ingest.Template/MeasurementExtractor.cs @@ -121,7 +121,7 @@ protected static bool IsExpressionDefined(params TemplateExpression[] expression protected virtual IEnumerable MatchTypeTokens(JObject token) { EnsureArg.IsNotNull(token, nameof(token)); - var evaluator = ExpressionEvaluatorFactory.Create(Template.TypeMatchExpression); + var evaluator = CreateRequiredExpressionEvaluator(Template.TypeMatchExpression, nameof(Template.TypeMatchExpression)); foreach (var extractedToken in evaluator.SelectTokens(token)) { @@ -134,6 +134,19 @@ protected virtual IEnumerable MatchTypeTokens(JObject token) } } + protected IExpressionEvaluator CreateRequiredExpressionEvaluator(TemplateExpression expression, string expressionName) + { + EnsureArg.IsNotNullOrWhiteSpace(expressionName, nameof(expressionName)); + + // If the expression object or its value aren't set, throw a detailed exception + if (string.IsNullOrWhiteSpace(expression?.Value)) + { + throw new IncompatibleDataException($"An expression must be set for [{expressionName}]"); + } + + return ExpressionEvaluatorFactory.Create(Template.TypeMatchExpression); + } + private Measurement CreateMeasurementFromToken(JToken token) { // Current assumption is that the expressions should match a single element and will error otherwise. diff --git a/src/lib/Microsoft.Health.Fhir.Ingest.Template/TemplateExpressionEvaluatorFactory.cs b/src/lib/Microsoft.Health.Fhir.Ingest.Template/TemplateExpressionEvaluatorFactory.cs index f11f95f8..c76a637b 100644 --- a/src/lib/Microsoft.Health.Fhir.Ingest.Template/TemplateExpressionEvaluatorFactory.cs +++ b/src/lib/Microsoft.Health.Fhir.Ingest.Template/TemplateExpressionEvaluatorFactory.cs @@ -24,7 +24,7 @@ public TemplateExpressionEvaluatorFactory(JmesPath jmesPath) public IExpressionEvaluator Create(TemplateExpression expression) { - EnsureArg.IsNotEmptyOrWhiteSpace(expression?.Value, nameof(expression.Value)); + EnsureArg.IsNotNullOrWhiteSpace(expression?.Value, nameof(expression.Value)); EnsureArg.IsNotNull(expression?.Language, nameof(expression.Language)); return expression.Language switch diff --git a/test/Microsoft.Health.Fhir.Ingest.Template.UnitTests/IotJsonPathContentTemplateTests.cs b/test/Microsoft.Health.Fhir.Ingest.Template.UnitTests/IotJsonPathContentTemplateTests.cs index f32a3a4e..f07c2ad1 100644 --- a/test/Microsoft.Health.Fhir.Ingest.Template.UnitTests/IotJsonPathContentTemplateTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.Template.UnitTests/IotJsonPathContentTemplateTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; -using Microsoft.Health.Fhir.Ingest.Template; using Microsoft.Health.Tests.Common; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -36,6 +35,16 @@ public class IotJsonPathContentTemplateTests }, }); + private static readonly IContentTemplate SingleValueMissingTypeNameTemplate = BuildMeasurementExtractor(new IotJsonPathContentTemplate + { + TypeName = "heartrate", + TypeMatchExpression = "$..[?(@Body.heartrate)]", + Values = new List + { + new JsonPathValueExpression { ValueName = "hr", ValueExpression = "$.Body.heartrate", Required = true }, + }, + }); + [Theory] [FileData(@"TestInput/data_IotHubPayloadExample.json")] public void GivenTemplateAndSingleValidToken_WhenGetMeasurements_ThenSingleMeasurementReturned_Test(string eventJson) @@ -57,6 +66,27 @@ public void GivenTemplateAndSingleValidToken_WhenGetMeasurements_ThenSingleMeasu }); } + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void GivenTemplateAndMissingTypeNameToken_WhenGetMeasurements_ThenExceptionIsThrown_Test(string typeMatchExpression) + { + var token = JToken.FromObject(new { heartrate = "60", device = "abc" }); + var template = BuildMeasurementExtractor(new IotJsonPathContentTemplate + { + TypeName = "heartrate", + TypeMatchExpression = typeMatchExpression, + Values = new List + { + new JsonPathValueExpression { ValueName = "hr", ValueExpression = "$.Body.heartrate", Required = true }, + }, + }); + + var ex = Assert.Throws(() => template.GetMeasurements(token).ToArray()); + Assert.Equal("An expression must be set for [TypeMatchExpression]", ex.Message); + } + [Theory] [FileData(@"TestInput/data_IotHubPayloadMultiValueExample.json")] public void GivenTemplateAndSingleMultiValueValidToken_WhenGetMeasurements_ThenSingleMeasurementReturned_Test(string eventJson) diff --git a/test/Microsoft.Health.Fhir.Ingest.Template.UnitTests/JsonPathContentTemplateTests.cs b/test/Microsoft.Health.Fhir.Ingest.Template.UnitTests/JsonPathContentTemplateTests.cs index 7e79304b..bdffa395 100644 --- a/test/Microsoft.Health.Fhir.Ingest.Template.UnitTests/JsonPathContentTemplateTests.cs +++ b/test/Microsoft.Health.Fhir.Ingest.Template.UnitTests/JsonPathContentTemplateTests.cs @@ -412,7 +412,32 @@ public void GivenPropertyWithSpace_WhenGetMeasurements_ThenSingleMeasurementRetu Assert.Equal("data", p.Value); }); }); - } + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void GivenInvalidTypeMatchExpression_WhenGetMeasurements_ThenExceptionIsThrown_Test(string typeMatchExpression) + { + var time = DateTime.UtcNow; + var token = JToken.FromObject(new JsonWidget { MyProperty = "data", Time = time }); + + var template = new JsonPathContentTemplate + { + TypeName = "space", + TypeMatchExpression = typeMatchExpression, + DeviceIdExpression = "$.['My Property']", + TimestampExpression = "$.Time", + Values = new List + { + new JsonPathValueExpression { ValueName = "prop", ValueExpression = "$.['My Property']", Required = false }, + }, + }; + + var ex = Assert.Throws(() => BuildMeasurementExtractor(template).GetMeasurements(token).ToArray()); + Assert.Equal("An expression must be set for [TypeMatchExpression]", ex.Message); + } [Fact] public void GivenSingleValueCompoundAndTemplateAndPartialToken_WhenGetMeasurements_ThenEmptyIEnumerableReturned_Test()