From 2dcf0058009b1a7b71563d19b369afab914a9dd2 Mon Sep 17 00:00:00 2001 From: QuanWanxx <68055742+QuanWanxx@users.noreply.github.com> Date: Fri, 5 Aug 2022 17:36:59 +0800 Subject: [PATCH] Change to NJsonSchema lib (#415) * Change to NJsonSchema lib * Catch Exception when Parse JsonSchema * Add meta schema as embedded resource * Update FunctionalTest to ignore meta-schema.json * Add multiple times test for validate --- data/Templates/Json/Schema/meta-schema.json | 150 ++++++++++++++++++ .../TemplateLocalFileSystemTests.cs | 10 +- .../DotLiquids/ValidateTests.cs | 39 ++++- .../Schemas/InvalidTestSchema.schema.json | 2 +- .../Schemas/TestSchema.schema.json | 1 + .../Schemas/TestSchema2.schema.json | 1 + .../Schemas/TestSubPropertySchema.schema.json | 1 + .../ValidUnmatchedTemplate3.liquid | 2 +- .../Utilities/TemplateUtilityTests.cs | 21 +++ .../DotLiquids/Validate.cs | 13 +- ...rosoft.Health.Fhir.Liquid.Converter.csproj | 6 +- .../Models/Json/JSchemaContext.cs | 6 +- .../Models/Json/JSchemaDocument.cs | 6 +- .../Models/Json/JSchemaTraceInfo.cs | 6 +- .../Processors/JsonProcessor.cs | 4 +- .../Utilities/TemplateUtility.cs | 52 +++++- .../TemplateCollectionFunctionalTests.cs | 12 +- 17 files changed, 298 insertions(+), 34 deletions(-) create mode 100644 data/Templates/Json/Schema/meta-schema.json diff --git a/data/Templates/Json/Schema/meta-schema.json b/data/Templates/Json/Schema/meta-schema.json new file mode 100644 index 000000000..85eb502a6 --- /dev/null +++ b/data/Templates/Json/Schema/meta-schema.json @@ -0,0 +1,150 @@ +{ + "id": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uri" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} +} diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/DotLiquids/TemplateLocalFileSystemTests.cs b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/DotLiquids/TemplateLocalFileSystemTests.cs index ab061c069..b9ab1b66c 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/DotLiquids/TemplateLocalFileSystemTests.cs +++ b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/DotLiquids/TemplateLocalFileSystemTests.cs @@ -12,7 +12,7 @@ using Microsoft.Health.Fhir.Liquid.Converter.Models; using Microsoft.Health.Fhir.Liquid.Converter.Models.Json; using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Schema; +using NJsonSchema; using Xunit; namespace Microsoft.Health.Fhir.Liquid.Converter.UnitTests.DotLiquids @@ -23,7 +23,6 @@ public class TemplateLocalFileSystemTests public void GivenAValidTemplateDirectory_WhenGetTemplate_CorrectResultsShouldBeReturned() { var templateLocalFileSystem = new TemplateLocalFileSystem(TestConstants.Hl7v2TemplateDirectory, DataType.Hl7v2); - var context = new Context(CultureInfo.InvariantCulture); // Template exists Assert.NotNull(templateLocalFileSystem.GetTemplate("ADT_A01")); @@ -49,11 +48,10 @@ public void GivenAValidTemplateDirectory_WhenGetTemplateWithContext_CorrectResul } [Fact] - public void GivenAValidTemplateDirectory_WhenGetJsonSchemaTemplate_CorrectResultsShouldBeReturned() + public async void GivenAValidTemplateDirectory_WhenGetJsonSchemaTemplate_CorrectResultsShouldBeReturned() { var templateLocalFileSystem = new TemplateLocalFileSystem(Path.Join(TestConstants.TestTemplateDirectory, @"ValidValidateTemplates"), DataType.Hl7v2); var testSchemaPath = "Schemas/TestSchema.schema.json"; - var context = new Context(CultureInfo.InvariantCulture); var schemaTemplate = templateLocalFileSystem.GetTemplate(testSchemaPath); @@ -64,8 +62,8 @@ public void GivenAValidTemplateDirectory_WhenGetJsonSchemaTemplate_CorrectResult Assert.NotNull(jSchemaDocument); Assert.NotNull(jSchemaDocument.Schema); - JSchema expectedJSchema = JSchema.Parse(File.ReadAllText(Path.Join(TestConstants.TestTemplateDirectory, @"ValidValidateTemplates", testSchemaPath))); - Assert.True(JToken.DeepEquals(JToken.Parse(jSchemaDocument.Schema.ToString()), JToken.Parse(expectedJSchema.ToString()))); + JsonSchema expectedJSchema = await JsonSchema.FromJsonAsync(File.ReadAllText(Path.Join(TestConstants.TestTemplateDirectory, @"ValidValidateTemplates", testSchemaPath))); + Assert.True(JToken.DeepEquals(JToken.Parse(jSchemaDocument.Schema.ToJson()), JToken.Parse(expectedJSchema.ToJson()))); } [Fact] diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/DotLiquids/ValidateTests.cs b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/DotLiquids/ValidateTests.cs index dc6ae8b30..847a1fbb3 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/DotLiquids/ValidateTests.cs +++ b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/DotLiquids/ValidateTests.cs @@ -88,6 +88,43 @@ public void GivenValidValidateMatchedTemplateContent_WhenParseAndRender_CorrectR Assert.True(JToken.DeepEquals(expectedObject, actualObject)); } + [Theory] + [MemberData(nameof(GetValidValidateMatchedTemplateContents))] + public void GivenValidValidateMatchedTemplateContent_WhenRenderMultipleTimes_CorrectResultShouldBeReturned(string templateContent, string expectedResult) + { + int repeatCount = 100000; + + // Template should be parsed correctly + var template = TemplateUtility.ParseLiquidTemplate(TemplateName, templateContent); + + var templateFolder = Path.Join(TestConstants.TestTemplateDirectory, @"ValidValidateTemplates"); + var parser = new JsonDataParser(); + var inputContent = "{\"id\": \"0\",\"valueReference\" : \"testReference\",\"resourceType\" : \"Patient\",\"name\":{\"valueString\" : \"valueString\"}}"; + + var templateProvider = new TemplateProvider(templateFolder, DataType.Json); + var jsonData = parser.Parse(inputContent); + var dictionary = new Dictionary { { "msg", jsonData } }; + var context = new Context( + environments: new List { Hash.FromDictionary(dictionary) }, + outerScope: new Hash(), + registers: Hash.FromDictionary(new Dictionary() { { "file_system", templateProvider.GetTemplateFileSystem() } }), + errorsOutputMode: ErrorsOutputMode.Rethrow, + maxIterations: 0, + timeout: 0, + formatProvider: CultureInfo.InvariantCulture); + context.AddFilters(typeof(Filters)); + + string result = string.Empty; + for (int i = 0; i < repeatCount; i++) + { + result = template.Render(RenderParameters.FromContext(context, CultureInfo.InvariantCulture)); + } + + var expectedObject = JObject.Parse(expectedResult); + var actualObject = JObject.Parse(result); + Assert.True(JToken.DeepEquals(expectedObject, actualObject)); + } + [Theory] [MemberData(nameof(GetValidValidateMatchedTemplateWithSchemaContents))] public void GivenValidValidateMatchedTemplateContent_WithJsonContext_WhenParseAndRender_InvolvedSchemaShouldBeReturned(string templateContent, List expectSchemaFiles) @@ -120,7 +157,7 @@ public void GivenValidValidateMatchedTemplateContent_WithJsonContext_WhenParseAn for (int i = 0; i < expectSchemaFiles.Count; i++) { var expectedSchemaObject = JObject.Parse(File.ReadAllText(Path.Join(TestConstants.TestTemplateDirectory, expectSchemaFiles[i]))); - var actualSchemaObject = JObject.Parse(context.ValidateSchemas[i].ToString()); + var actualSchemaObject = JObject.Parse(context.ValidateSchemas[i].ToJson()); Assert.True(JToken.DeepEquals(expectedSchemaObject, actualSchemaObject)); } } diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/InvalidValidateTemplates/Schemas/InvalidTestSchema.schema.json b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/InvalidValidateTemplates/Schemas/InvalidTestSchema.schema.json index 20636060c..cc96da5cc 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/InvalidValidateTemplates/Schemas/InvalidTestSchema.schema.json +++ b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/InvalidValidateTemplates/Schemas/InvalidTestSchema.schema.json @@ -1,3 +1,3 @@ { - "type": "invalidType" + "type": "InvalidType" } \ No newline at end of file diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSchema.schema.json b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSchema.schema.json index d59d0b605..9ea438813 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSchema.schema.json +++ b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSchema.schema.json @@ -1,4 +1,5 @@ { + "$schema": "http://json-schema.org/draft-04/schema#", "title": "Patient customized schema", "type": "object", "properties": { diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSchema2.schema.json b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSchema2.schema.json index 6df67308d..dbff6c985 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSchema2.schema.json +++ b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSchema2.schema.json @@ -1,4 +1,5 @@ { + "$schema": "http://json-schema.org/draft-04/schema#", "title": "Patient customized schema", "type": "object", "properties": { diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSubPropertySchema.schema.json b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSubPropertySchema.schema.json index 61f31e85c..8c51ec2f4 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSubPropertySchema.schema.json +++ b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/Schemas/TestSubPropertySchema.schema.json @@ -1,4 +1,5 @@ { + "$schema": "http://json-schema.org/draft-04/schema#", "title": "Patient customized schema", "type": "object", "properties": { diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/ValidUnmatchedTemplate3.liquid b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/ValidUnmatchedTemplate3.liquid index d8fdeff25..fa0dd238e 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/ValidUnmatchedTemplate3.liquid +++ b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/TestData/TestTemplates/ValidValidateTemplates/ValidUnmatchedTemplate3.liquid @@ -1,4 +1,4 @@ -{% validate "Schemas/TestSchema3.schema.json" -%} +{% validate "Schemas/TestSchema2.schema.json" -%} { "resourceType": "{{ msg.resourceType }}" } diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/Utilities/TemplateUtilityTests.cs b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/Utilities/TemplateUtilityTests.cs index a80e6f6f5..2455b311f 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/Utilities/TemplateUtilityTests.cs +++ b/src/Microsoft.Health.Fhir.Liquid.Converter.UnitTests/Utilities/TemplateUtilityTests.cs @@ -118,5 +118,26 @@ public void GivenInvalidCcdaTemplateContents_WhenParseTemplates_ExceptionsShould exception = Assert.Throws(() => TemplateUtility.ParseTemplates(templates)); Assert.Equal(FhirConverterErrorCode.InvalidCodeMapping, exception.FhirConverterErrorCode); } + + [Fact] + public void GivenInvalidJsonSchemaTemplateContents_WhenParseTemplates_ExceptionsShouldBeThrown() + { + // Invalid schema content + var templates = new Dictionary { { "InvalidSchema.schema.json", @"{""type"": ""InvalidType"" }" } }; + var exception = Assert.Throws(() => TemplateUtility.ParseTemplates(templates)); + Assert.Equal(FhirConverterErrorCode.InvalidJsonSchema, exception.FhirConverterErrorCode); + + // Invalid JSON + templates = new Dictionary { { "InvalidSchema.schema.json", @"{""a""" } }; + exception = Assert.Throws(() => TemplateUtility.ParseTemplates(templates)); + Assert.Equal(FhirConverterErrorCode.InvalidJsonSchema, exception.FhirConverterErrorCode); + + // Null or empty schema + templates = new Dictionary { { "InvalidSchema.schema.json", string.Empty } }; + exception = Assert.Throws(() => TemplateUtility.ParseTemplates(templates)); + + templates = new Dictionary { { "InvalidSchema.schema.json", null} }; + exception = Assert.Throws(() => TemplateUtility.ParseTemplates(templates)); + } } } diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter/DotLiquids/Validate.cs b/src/Microsoft.Health.Fhir.Liquid.Converter/DotLiquids/Validate.cs index bf7d8c7da..a1df3d512 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter/DotLiquids/Validate.cs +++ b/src/Microsoft.Health.Fhir.Liquid.Converter/DotLiquids/Validate.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using DotLiquid; using DotLiquid.Exceptions; @@ -13,7 +14,7 @@ using Microsoft.Health.Fhir.Liquid.Converter.Models.Json; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Schema; +using NJsonSchema; using RenderException = Microsoft.Health.Fhir.Liquid.Converter.Exceptions.RenderException; namespace Microsoft.Health.Fhir.Liquid.Converter.DotLiquids @@ -50,7 +51,7 @@ public override void Initialize(string tagName, string markup, List toke public override void Render(Context context, TextWriter result) { - JSchema validateSchema = LoadValidateSchema(context); + JsonSchema validateSchema = LoadValidateSchema(context); if (context is JSchemaContext jSchemaContext) { @@ -76,15 +77,17 @@ public override void Render(Context context, TextWriter result) throw new RenderException(FhirConverterErrorCode.InvalidValidateBlockContent, string.Format(Resources.InvalidValidateContentBlock, ex.Message)); } - if (!validateObject.IsValid(validateSchema, out IList messages)) + var errors = validateSchema.Validate(validateObject); + + if (errors.Any()) { - throw new RenderException(FhirConverterErrorCode.UnmatchedValidateBlockContent, string.Format(Resources.UnMatchedValidateBlockContent, _schemaFileName, string.Join(";", messages))); + throw new RenderException(FhirConverterErrorCode.UnmatchedValidateBlockContent, string.Format(Resources.UnMatchedValidateBlockContent, _schemaFileName, string.Join(";", errors))); } result.Write(validateContent); } - private JSchema LoadValidateSchema(Context context) + private JsonSchema LoadValidateSchema(Context context) { if (!(context.Registers["file_system"] is IFhirConverterTemplateFileSystem fileSystem)) { diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter/Microsoft.Health.Fhir.Liquid.Converter.csproj b/src/Microsoft.Health.Fhir.Liquid.Converter/Microsoft.Health.Fhir.Liquid.Converter.csproj index c758bd5e4..dbc41ddca 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter/Microsoft.Health.Fhir.Liquid.Converter.csproj +++ b/src/Microsoft.Health.Fhir.Liquid.Converter/Microsoft.Health.Fhir.Liquid.Converter.csproj @@ -13,7 +13,7 @@ - + @@ -28,6 +28,10 @@ + + + + True diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaContext.cs b/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaContext.cs index 1cf6b59ef..7b8c55384 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaContext.cs +++ b/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaContext.cs @@ -6,7 +6,7 @@ using System; using System.Collections.Generic; using DotLiquid; -using Newtonsoft.Json.Schema; +using NJsonSchema; namespace Microsoft.Health.Fhir.Liquid.Converter.Models.Json { @@ -15,9 +15,9 @@ public class JSchemaContext : Context public JSchemaContext(List environments, Hash outerScope, Hash registers, ErrorsOutputMode errorsOutputMode, int maxIterations, int timeout, IFormatProvider formatProvider) : base(environments, outerScope, registers, errorsOutputMode, maxIterations, timeout, formatProvider) { - ValidateSchemas = new List(); + ValidateSchemas = new List(); } - public List ValidateSchemas { get; set; } + public List ValidateSchemas { get; set; } } } diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaDocument.cs b/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaDocument.cs index c903bb578..33e3b7710 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaDocument.cs +++ b/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaDocument.cs @@ -5,13 +5,13 @@ using System; using DotLiquid; -using Newtonsoft.Json.Schema; +using NJsonSchema; namespace Microsoft.Health.Fhir.Liquid.Converter.Models.Json { public class JSchemaDocument : Document { - public JSchemaDocument(JSchema schema) + public JSchemaDocument(JsonSchema schema) { if (schema == null) { @@ -21,6 +21,6 @@ public JSchemaDocument(JSchema schema) Schema = schema; } - public JSchema Schema { get; set; } + public JsonSchema Schema { get; set; } } } diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaTraceInfo.cs b/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaTraceInfo.cs index 06ae1d0a3..0cc2f0d3a 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaTraceInfo.cs +++ b/src/Microsoft.Health.Fhir.Liquid.Converter/Models/Json/JSchemaTraceInfo.cs @@ -4,7 +4,7 @@ // ------------------------------------------------------------------------------------------------- using System.Collections.Generic; -using Newtonsoft.Json.Schema; +using NJsonSchema; namespace Microsoft.Health.Fhir.Liquid.Converter.Models.Json { @@ -14,11 +14,11 @@ public JSchemaTraceInfo() { } - public JSchemaTraceInfo(List validateSchemas) + public JSchemaTraceInfo(List validateSchemas) { ValidateSchemas = validateSchemas; } - public List ValidateSchemas { get; set; } + public List ValidateSchemas { get; set; } } } diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter/Processors/JsonProcessor.cs b/src/Microsoft.Health.Fhir.Liquid.Converter/Processors/JsonProcessor.cs index cac3f263a..ac3e45401 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter/Processors/JsonProcessor.cs +++ b/src/Microsoft.Health.Fhir.Liquid.Converter/Processors/JsonProcessor.cs @@ -9,7 +9,7 @@ using Microsoft.Health.Fhir.Liquid.Converter.Models; using Microsoft.Health.Fhir.Liquid.Converter.Models.Json; using Microsoft.Health.Fhir.Liquid.Converter.Parsers; -using Newtonsoft.Json.Schema; +using NJsonSchema; namespace Microsoft.Health.Fhir.Liquid.Converter.Processors { @@ -41,7 +41,7 @@ protected override Context CreateContext(ITemplateProvider templateProvider, IDi timeout: timeout, formatProvider: CultureInfo.InvariantCulture) { - ValidateSchemas = new List(), + ValidateSchemas = new List(), }; // Load filters diff --git a/src/Microsoft.Health.Fhir.Liquid.Converter/Utilities/TemplateUtility.cs b/src/Microsoft.Health.Fhir.Liquid.Converter/Utilities/TemplateUtility.cs index 616c0bd30..62b2671ad 100644 --- a/src/Microsoft.Health.Fhir.Liquid.Converter/Utilities/TemplateUtility.cs +++ b/src/Microsoft.Health.Fhir.Liquid.Converter/Utilities/TemplateUtility.cs @@ -6,6 +6,8 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Reflection; using System.Text.RegularExpressions; using DotLiquid; using DotLiquid.Exceptions; @@ -14,7 +16,9 @@ using Microsoft.Health.Fhir.Liquid.Converter.Models; using Microsoft.Health.Fhir.Liquid.Converter.Models.Json; using Newtonsoft.Json; -using Newtonsoft.Json.Schema; +using Newtonsoft.Json.Linq; +using NJsonSchema; +using NJsonSchema.Validation; namespace Microsoft.Health.Fhir.Liquid.Converter.Utilities { @@ -23,6 +27,8 @@ public static class TemplateUtility private static readonly Regex FormatRegex = new Regex(@"(\\|/)_?"); private const string LiquidTemplateFileExtension = ".liquid"; private const string JsonSchemaTemplateFileExtension = ".schema.json"; + private const string MetaJsonSchemaFileName = "meta-schema.json"; + private static readonly JsonSchema MetaJsonSchema; // Register "evaluate" tag in before Template.Parse static TemplateUtility() @@ -30,6 +36,7 @@ static TemplateUtility() Template.RegisterTag("evaluate"); Template.RegisterTag("mergeDiff"); Template.RegisterTag("validate"); + MetaJsonSchema = LoadEmbeddedMetaJsonSchema(); } /// @@ -51,6 +58,7 @@ public static Dictionary ParseTemplates(Dictionary errors; + try + { + var schemaObject = JObject.Parse(content); + errors = MetaJsonSchema.Validate(content); + } + catch (Exception ex) + { + throw new TemplateLoadException(FhirConverterErrorCode.InvalidJsonSchema, string.Format(Resources.InvalidJsonSchemaContent, ex.Message), ex); } - JSchema schema; + if (errors.Any()) + { + throw new TemplateLoadException(FhirConverterErrorCode.InvalidJsonSchema, string.Format(Resources.InvalidJsonSchemaContent, string.Join(";", errors))); + } + + JsonSchema schema; try { - schema = JSchema.Parse(content); + schema = JsonSchema.FromJsonAsync(content).GetAwaiter().GetResult(); } - catch (JSchemaReaderException ex) + catch (Exception ex) { throw new TemplateLoadException(FhirConverterErrorCode.InvalidJsonSchema, string.Format(Resources.InvalidJsonSchemaContent, ex.Message), ex); } @@ -170,5 +195,20 @@ public static bool IsJsonSchemaTemplate(string templateKey) { return templateKey.EndsWith(JsonSchemaTemplateFileExtension, StringComparison.InvariantCultureIgnoreCase); } + + private static JsonSchema LoadEmbeddedMetaJsonSchema() + { + var executingAssembly = Assembly.GetExecutingAssembly(); + var metaSchemaAssemblyName = string.Format("{0}.{1}", executingAssembly.GetName().Name, MetaJsonSchemaFileName); + + string metaSchemaContent; + using (Stream stream = executingAssembly.GetManifestResourceStream(metaSchemaAssemblyName)) + using (StreamReader reader = new StreamReader(stream)) + { + metaSchemaContent = reader.ReadToEnd(); + } + + return JsonSchema.FromJsonAsync(metaSchemaContent).GetAwaiter().GetResult(); + } } } diff --git a/src/Microsoft.Health.Fhir.TemplateManagement.FunctionalTests/TemplateCollectionFunctionalTests.cs b/src/Microsoft.Health.Fhir.TemplateManagement.FunctionalTests/TemplateCollectionFunctionalTests.cs index e61e25969..c910a0156 100644 --- a/src/Microsoft.Health.Fhir.TemplateManagement.FunctionalTests/TemplateCollectionFunctionalTests.cs +++ b/src/Microsoft.Health.Fhir.TemplateManagement.FunctionalTests/TemplateCollectionFunctionalTests.cs @@ -348,8 +348,16 @@ public async Task GiveDefaultImageReference_WhenGetTemplateCollectionWithEmptyTo var templateCollection = await templateCollectionProvider.GetTemplateCollectionAsync(); Assert.Single(templateCollection); - // metadata.json will not be returned as templates. - Assert.Equal(Directory.GetFiles(Path.Join(_templateDirectory, expectedTemplatesFolder), "*", SearchOption.AllDirectories).Length - 1, templateCollection.First().Count()); + // metadata.json will not be returned as template. + // Json/Schema/meta-schema.json will not be returned as template. + var excludeFiles = new HashSet() + { + Path.Join(_templateDirectory, expectedTemplatesFolder, "metadata.json"), + Path.Join(_templateDirectory, expectedTemplatesFolder, "Schema", "meta-schema.json"), + }; + var expectedTemplateFiles = Directory.GetFiles(Path.Join(_templateDirectory, expectedTemplatesFolder), "*", SearchOption.AllDirectories) + .Where(file => !excludeFiles.Contains(file)).ToList(); + Assert.Equal(expectedTemplateFiles.Count, templateCollection.First().Count()); } // Conversion results of DefaultTemplates.tar.gz and default template folder should be the same.