Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,57 @@ public void GetPropertyTest()
Assert.Null(Filters.GetProperty(context, "M", string.Empty, "code"));
}

[Fact]
public void GetProperty_WhenCodeMappingIsAppended_ReturnsMappedValue()
{
// empty context
var context = new Context(CultureInfo.InvariantCulture);
Assert.Null(Filters.GetProperty(context, null, null, null));

// context with null CodeMapping
context = new Context(
environments: new List<Hash>(),
outerScope: new Hash(),
registers: new Hash(),
errorsOutputMode: ErrorsOutputMode.Rethrow,
maxIterations: 0,
formatProvider: CultureInfo.InvariantCulture,
cancellationToken: CancellationToken.None);
context["CodeMapping"] = null;

// First CodeMapping from CodeSystem
var firstMapping = new CodeMapping(new Dictionary<string, Dictionary<string, Dictionary<string, string>>>
{
{
"CodeSystem/Gender", new Dictionary<string, Dictionary<string, string>>
{
{ "M", new Dictionary<string, string> { { "code", "male" } } },
}
},
});

// Additional CodeMapping from ValueSet
var additionalMapping = new CodeMapping(new Dictionary<string, Dictionary<string, Dictionary<string, string>>>
{
{
"ValueSet/Gender", new Dictionary<string, Dictionary<string, string>>
{
{ "F", new Dictionary<string, string> { { "code", "female" } } },
{ "O", new Dictionary<string, string> { { "code", "other" } } },
}
},
});

// Append ValueSet to CodeMapping
firstMapping.Append(additionalMapping);
context["CodeMapping"] = firstMapping;

// Assert both CodeSystem/Gender and ValueSet/Gender
Assert.Equal("male", Filters.GetProperty(context, "M", "CodeSystem/Gender", "code"));
Assert.Equal("female", Filters.GetProperty(context, "F", "ValueSet/Gender", "code"));
Assert.Equal("other", Filters.GetProperty(context, "O", "ValueSet/Gender", "code"));
}

[Fact]
public void EvaluateTest()
{
Expand Down
48 changes: 48 additions & 0 deletions src/Microsoft.Health.Fhir.Liquid.Converter/Models/CodeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
// -------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using DotLiquid;

Expand All @@ -16,5 +17,52 @@ public CodeMapping(Dictionary<string, Dictionary<string, Dictionary<string, stri
}

public Dictionary<string, Dictionary<string, Dictionary<string, string>>> Mapping { get; set; }

/// <summary>
/// Appends mappings from another CodeMapping instance.
/// Throws InvalidOperationException if any key path already exists with a different value.
/// </summary>
/// <param name="additionalMapping">The CodeMapping to append.</param>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="mappingToAppend"/> is null.</exception>
/// <exception cref="InvalidOperationException">Thrown if any key path already exists with a different value.</exception>
public void Append(CodeMapping additionalMapping)
{
ArgumentNullException.ThrowIfNull(additionalMapping, nameof(additionalMapping));

foreach (var level1 in additionalMapping.Mapping)
{
if (!Mapping.TryGetValue(level1.Key, out var level2Dict))
{
Mapping[level1.Key] = new Dictionary<string, Dictionary<string, string>>(level1.Value);
continue;
}

foreach (var level2 in level1.Value)
{
if (!level2Dict.TryGetValue(level2.Key, out var level3Dict))
{
level2Dict[level2.Key] = new Dictionary<string, string>(level2.Value);
continue;
}

foreach (var level3 in level2.Value)
{
if (level3Dict.TryGetValue(level3.Key, out var existingValue))
{
if (!string.Equals(existingValue, level3.Value, StringComparison.Ordinal))
{
throw new InvalidOperationException(
$"Conflict at path [{level1.Key}][{level2.Key}][{level3.Key}]: " +
$"existing='{existingValue}', new='{level3.Value}'");
}
}
else
{
level3Dict[level3.Key] = level3.Value;
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading;
using DotLiquid;
using EnsureThat;
Expand Down Expand Up @@ -63,7 +64,23 @@ public string Convert(string data, string rootTemplate, ITemplateProvider templa

protected abstract string InternalConvert(string data, string rootTemplate, ITemplateProvider templateProvider, TraceInfo traceInfo = null);

protected virtual Context CreateContext(ITemplateProvider templateProvider, IDictionary<string, object> data, string rootTemplate)
protected Context CreateContext(ITemplateProvider templateProvider, IDictionary<string, object> data, string rootTemplate)
{
Context context = CreateBaseContext(templateProvider, data);

// Load filters
context.AddFilters(typeof(Filters));

// Add root template's parent path to context.
AddRootTemplatePathScope(context, templateProvider, rootTemplate);

// Inject of codemapping into context.
InjectCodeMappingIntoContext(context, templateProvider);

return context;
}

protected virtual Context CreateBaseContext(ITemplateProvider templateProvider, IDictionary<string, object> data)
{
// Load data and templates
var cancellationToken = Settings.TimeOut > 0 ? new CancellationTokenSource(Settings.TimeOut).Token : CancellationToken.None;
Expand All @@ -76,12 +93,6 @@ protected virtual Context CreateContext(ITemplateProvider templateProvider, IDic
formatProvider: CultureInfo.InvariantCulture,
cancellationToken: cancellationToken);

// Load filters
context.AddFilters(typeof(Filters));

// Add root template's parent path to context.
AddRootTemplatePathScope(context, templateProvider, rootTemplate);

return context;
}

Expand Down Expand Up @@ -182,5 +193,38 @@ protected void LogTelemetry(string telemetryName, double duration)
Logger.LogInformation("{Metric}: {Duration} milliseconds.", telemetryName, duration);
}
}

protected void InjectCodeMappingIntoContext(Context context, ITemplateProvider templateProvider)
{
var rootTemplateParentPath = context[TemplateUtility.RootTemplateParentPathScope]?.ToString();
List<string> codeMappings = new List<string> { "ValueSet/ValueSet", "CodeSystem/CodeSystem", };
var allPaths = codeMappings
.Select(name => TemplateUtility.GetFormattedTemplatePath(name, rootTemplateParentPath))
.ToList();

CodeMapping combinedMapping = null;

foreach (var path in allPaths)
{
var template = templateProvider.GetTemplate(path);
var node = template?.Root?.NodeList?.FirstOrDefault() as CodeMapping;
if (node != null)
{
if (combinedMapping == null)
{
combinedMapping = node;
}
else
{
combinedMapping.Append(node);
}
}
}

if (combinedMapping != null)
{
context["CodeMapping"] = combinedMapping;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,5 @@ protected override string InternalConvert(string data, string rootTemplate, ITem

return InternalConvertFromObject(ccdaData, rootTemplate, templateProvider, traceInfo);
}

protected override Context CreateContext(ITemplateProvider templateProvider, IDictionary<string, object> data, string rootTemplate)
{
// Load value set mapping
var context = base.CreateContext(templateProvider, data, rootTemplate);
var codeMapping = templateProvider.GetTemplate(GetCodeMappingTemplatePath(context));
if (codeMapping?.Root?.NodeList?.First() != null)
{
context["CodeMapping"] = codeMapping.Root.NodeList.First();
}

return context;
}

private string GetCodeMappingTemplatePath(Context context)
{
var rootTemplateParentPath = context[TemplateUtility.RootTemplateParentPathScope]?.ToString();
var codeSystemTemplateName = "ValueSet/ValueSet";
return TemplateUtility.GetFormattedTemplatePath(codeSystemTemplateName, rootTemplateParentPath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -225,11 +225,11 @@ public string ConvertHl7MessageToString(Hl7v2Data message)
return sb.ToString();
}

protected override Context CreateContext(ITemplateProvider templateProvider, IDictionary<string, object> data, string rootTemplate)
protected override Context CreateBaseContext(ITemplateProvider templateProvider, IDictionary<string, object> data)
{
// Load data and templates
var cancellationToken = Settings.TimeOut > 0 ? new CancellationTokenSource(Settings.TimeOut).Token : CancellationToken.None;
var context = new JSchemaContext(
return new JSchemaContext(
environments: new List<Hash> { Hash.FromDictionary(data) },
outerScope: new Hash(),
registers: Hash.FromDictionary(new Dictionary<string, object> { { "file_system", templateProvider.GetTemplateFileSystem() } }),
Expand All @@ -240,11 +240,6 @@ protected override Context CreateContext(ITemplateProvider templateProvider, IDi
{
ValidateSchemas = new List<JsonSchema>(),
};

// Load filters
context.AddFilters(typeof(Filters));

return context;
}

protected override void CreateTraceInfo(object data, Context context, TraceInfo traceInfo)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,12 @@ protected override string InternalConvert(string data, string rootTemplate, ITem
return InternalConvertFromObject(hl7v2Data, rootTemplate, templateProvider, traceInfo);
}

protected override Context CreateContext(ITemplateProvider templateProvider, IDictionary<string, object> data, string rootTemplate)
{
// Load code system mapping
var context = base.CreateContext(templateProvider, data, rootTemplate);
var codeMapping = templateProvider.GetTemplate(GetCodeMappingTemplatePath(context));
if (codeMapping?.Root?.NodeList?.First() != null)
{
context["CodeMapping"] = codeMapping.Root.NodeList.First();
}

return context;
}

protected override void CreateTraceInfo(object data, Context context, TraceInfo traceInfo)
{
if (traceInfo is Hl7v2TraceInfo hl7v2TraceInfo)
{
hl7v2TraceInfo.UnusedSegments = Hl7v2TraceInfo.CreateTraceInfo(data as Hl7v2Data).UnusedSegments;
}
}

private string GetCodeMappingTemplatePath(Context context)
{
var rootTemplateParentPath = context[TemplateUtility.RootTemplateParentPathScope]?.ToString();
var codeSystemTemplateName = "CodeSystem/CodeSystem";
return TemplateUtility.GetFormattedTemplatePath(codeSystemTemplateName, rootTemplateParentPath);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ public string Convert(JObject data, string rootTemplate, ITemplateProvider templ
return InternalConvertFromObject(jsonData, rootTemplate, templateProvider, traceInfo);
}

protected override Context CreateContext(ITemplateProvider templateProvider, IDictionary<string, object> data, string rootTemplate)
protected override Context CreateBaseContext(ITemplateProvider templateProvider, IDictionary<string, object> data)
{
// Load data and templates
var cancellationToken = Settings.TimeOut > 0 ? new CancellationTokenSource(Settings.TimeOut).Token : CancellationToken.None;
var context = new JSchemaContext(
return new JSchemaContext(
environments: new List<Hash> { Hash.FromDictionary(data) },
outerScope: new Hash(),
registers: Hash.FromDictionary(new Dictionary<string, object> { { "file_system", templateProvider.GetTemplateFileSystem() } }),
Expand All @@ -69,14 +69,6 @@ protected override Context CreateContext(ITemplateProvider templateProvider, IDi
{
ValidateSchemas = new List<JsonSchema>(),
};

// Load filters
context.AddFilters(typeof(Filters));

// Add root template's parent path to context.
AddRootTemplatePathScope(context, templateProvider, rootTemplate);

return context;
}

protected override void CreateTraceInfo(object data, Context context, TraceInfo traceInfo)
Expand Down