Skip to content

Commit

Permalink
Merge pull request #276 from FirelyTeam/275-support-codeablereference
Browse files Browse the repository at this point in the history
Add support for CodeableReference in binding+reference validator.
  • Loading branch information
mmsmits authored Feb 2, 2024
2 parents 87d30a3 + c657e39 commit 9526cfd
Show file tree
Hide file tree
Showing 27 changed files with 1,238 additions and 438 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -348,3 +348,4 @@ MigrationBackup/

# Ionide (cross platform F# VS Code tools) working folder
.ionide/
/.idea/.idea.Firely.Validator.API/.idea/workspace.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

253 changes: 0 additions & 253 deletions .idea/.idea.Firely.Validator.API/.idea/workspace.xml

This file was deleted.

5 changes: 1 addition & 4 deletions firely-validator-api-tests.props
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,11 @@
<ContainsTests>true</ContainsTests>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
<!-- Missing XML comments -->
<NoWarn>1591</NoWarn>
<Nullable>enable</Nullable>
</PropertyGroup>

<PropertyGroup>
<FirelySdkVersion>5.5.1</FirelySdkVersion>
<LegacyValidatorVersion>5.1.0</LegacyValidatorVersion>
<FirelySdkVersion>5.5.2-20240131.10</FirelySdkVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion firely-validator-api.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</PropertyGroup>

<PropertyGroup>
<FirelySdkVersion>5.5.1</FirelySdkVersion>
<FirelySdkVersion>5.5.2-20240131.10</FirelySdkVersion>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
Expand Down
11 changes: 6 additions & 5 deletions src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
*/

using Hl7.Fhir.Model;
using Hl7.Fhir.Serialization;
using Hl7.Fhir.Specification.Navigation;
using Hl7.Fhir.Specification.Source;
using Hl7.Fhir.Support;
using Hl7.Fhir.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using static Hl7.Fhir.Model.ElementDefinition;

Expand Down Expand Up @@ -53,10 +55,6 @@ public SchemaBuilder(IAsyncResourceResolver source, IEnumerable<ISchemaBuilder>?
/// <inheritdoc/>
public IEnumerable<IAssertion> Build(ElementDefinitionNavigator nav, ElementConversionMode? conversionMode = ElementConversionMode.Full)
{
//Enable this when you need a snapshot of a test SD written out in your %TEMP%/testprofiles dir.
//string p = Path.Combine(Path.GetTempPath(), "testprofiles", (nav.StructureDefinition.Id ?? nav.StructureDefinition.Name) + ".xml");
//File.WriteAllText(p, nav.StructureDefinition.ToXml());

if (!nav.MoveToFirstChild()) return new[] { new ElementSchema(nav.StructureDefinition.Url) };

var subschemaCollector = new SubschemaCollector(nav);
Expand Down Expand Up @@ -405,7 +403,10 @@ private static IAssertion createDefaultSlice(SlicingComponent slicing) =>

private IAssertion buildDiscriminatorCondition(SlicingComponent slicing, ElementDefinitionNavigator slice)
{
IEnumerable<IAssertion?> sliceAssertions = slicing.Discriminator.Select(d => DiscriminatorFactory.Build(slice, d, Source));
var sliceAssertions = slicing.Discriminator
.Select(d => DiscriminatorFactory.Build(slice, d, Source))
.ToArray();

if (sliceAssertions.All(sa => sa is null))
{
var paths = string.Join(',', slicing.Discriminator.Select(d => d.Path));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ public IEnumerable<IAssertion> Build(ElementDefinitionNavigator nav, ElementConv
#endif
if ((!nav.HasChildren || hasProfileDetails) && def.Type.Count > 0)
{
var typeAssertions = ConvertTypeReferences(def.Type);
if (typeAssertions is not null)
yield return typeAssertions;
var typeAssertion = ConvertTypeReferences(def.Type);
if (typeAssertion is not null)
yield return typeAssertion;
}
}

Expand Down Expand Up @@ -135,7 +135,7 @@ public IEnumerable<IAssertion> Build(ElementDefinitionNavigator nav, ElementConv
? new AllValidator(profileAssertions, validateReferenceAssertion)
: validateReferenceAssertion;
}
else if (!(code is "Reference" or "canonical" or "CodeableReference") && typeRef.TargetProfile.Any())
else if (!ReferencedInstanceValidator.IsReferenceType(code) && typeRef.TargetProfile.Any())
{
throw new IncorrectElementDefinitionException($"Encountered targetProfiles {string.Join(",", typeRef.TargetProfile)} on an element that is not " +
$"a reference type (canonical or Reference) but a {code}.");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems" Label="Shared" />
<Import Project="../../firely-validator-api.props" />

Expand Down
32 changes: 16 additions & 16 deletions src/Firely.Fhir.Validation/Impl/BindingValidator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@

/*
/*
* Copyright (c) 2024, Firely (info@fire.ly) and contributors
* See the file CONTRIBUTORS for details.
*
Expand Down Expand Up @@ -120,6 +119,7 @@ ResultReport IValidatable.Validate(IScopedNode input, ValidationSettings vc, Val
// This would give informational messages even if the validation was run on a choice type with a binding, which is then
// only applicable to an instance which is bindable. So instead of a warning, we should just return as validation is
// not applicable to this instance.
// There is hack here, since CodeableReference is bindable, but not part of Base.
if (!ModelInspector.Base.IsBindable(input.InstanceType))
{
return vc.TraceResult(() =>
Expand All @@ -132,7 +132,7 @@ ResultReport IValidatable.Validate(IScopedNode input, ValidationSettings vc, Val
var result = verifyContentRequirements(input, bindable, s);

return result.IsSuccessful ?
validateCode(input, bindable, vc, s)
validateCode(bindable, vc, s)
: result;
}
else
Expand Down Expand Up @@ -173,7 +173,7 @@ private static bool codeableConceptHasCode(CodeableConcept cc) =>
cc.Coding.Any(cd => !string.IsNullOrEmpty(cd.Code));


private ResultReport validateCode(IScopedNode source, Element bindable, ValidationSettings vc, ValidationState s)
private ResultReport validateCode(Element bindable, ValidationSettings vc, ValidationState s)
{
//EK 20170605 - disabled inclusion of warnings/errors for all but required bindings since this will
// 1) create superfluous messages (both saying the code is not valid) coming from the validateResult + the outcome.AddIssue()
Expand All @@ -188,15 +188,15 @@ private ResultReport validateCode(IScopedNode source, Element bindable, Validati

ValidateCodeParameters buildParams()
{
var parameters = new ValidateCodeParameters();
var vcp = new ValidateCodeParameters();

return bindable switch
{
FhirString str => parameters.WithCode(str.Value, system: null, display: null, context: Context),
FhirUri uri => parameters.WithCode(uri.Value, system: null, display: null, context: Context),
Code co => parameters.WithCode(co.Value, system: null, display: null, context: Context),
Coding cd => parameters.WithCoding(cd),
CodeableConcept cc => parameters.WithCodeableConcept(cc),
FhirString str => vcp.WithCode(str.Value, system: null, display: null, context: Context),
FhirUri uri => vcp.WithCode(uri.Value, system: null, display: null, context: Context),
Code co => vcp.WithCode(co.Value, system: null, display: null, context: Context),
Coding cd => vcp.WithCoding(cd),
CodeableConcept cc => vcp.WithCodeableConcept(cc),
_ => throw Error.InvalidOperation($"Parsed bindable was of unexpected instance type '{bindable.TypeName}'.")
};
}
Expand All @@ -207,15 +207,15 @@ ValidateCodeParameters buildParams()
return result switch
{
(null, _) => ResultReport.SUCCESS,
(Issue issue, var message) => new IssueAssertion(issue, message!).AsResult(s)
({ } issue, var message) => new IssueAssertion(issue, message!).AsResult(s)
};
}

private static string buildCodingDisplay(ValidateCodeParameters p)
{
return p switch
{
{ Code: not null } code => "code " + codeToString(p.Code.Value, p.System?.Value),
{ Code: not null } => "code " + codeToString(p.Code.Value, p.System?.Value),
{ Coding: { } coding } => "coding " + codeToString(coding.Code, coding.System),
{ CodeableConcept: { } cc } when !string.IsNullOrEmpty(cc.Text) => $"concept {cc.Text} with coding(s) {ccToString(cc)}",
{ CodeableConcept: { } cc } when string.IsNullOrEmpty(cc.Text) => $"concept with coding(s) {ccToString(cc)}",
Expand All @@ -239,8 +239,8 @@ public JToken ToJson()
var props = new JObject(new JProperty("abstractAllowed", AbstractAllowed));
if (Strength is not null)
props.Add(new JProperty("strength", Strength!.GetLiteral()));
if (ValueSetUri is not null)
props.Add(new JProperty("valueSet", (string)ValueSetUri));

props.Add(new JProperty("valueSet", (string)ValueSetUri));

return new JProperty("binding", props);
}
Expand All @@ -253,9 +253,9 @@ private static (Issue?, string?) interpretResults(Parameters parameters, string
return (result, message) switch
{
(true, null) => (null, null),
(true, string) => (Issue.TERMINOLOGY_OUTPUT_WARNING, message),
(true, not null) => (Issue.TERMINOLOGY_OUTPUT_WARNING, message),
(false, null) => (Issue.TERMINOLOGY_OUTPUT_ERROR, display.Capitalize() + " is invalid, but the terminology service provided no further details."),
(false, string) => (Issue.TERMINOLOGY_OUTPUT_ERROR, message)
(false, not null) => (Issue.TERMINOLOGY_OUTPUT_ERROR, message)
};
}

Expand Down
40 changes: 25 additions & 15 deletions src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
/*
* Copyright (c) 2024, Firely (info@fire.ly) and contributors
* See the file CONTRIBUTORS for details.
*
*
* This file is licensed under the BSD 3-Clause license
* available at https://github.com/FirelyTeam/firely-validator-api/blob/main/LICENSE
*/
Expand All @@ -13,6 +13,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.Serialization;

Expand All @@ -25,9 +26,9 @@ namespace Firely.Fhir.Validation
[DataContract]
[EditorBrowsable(EditorBrowsableState.Never)]
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.Experimental(diagnosticId: "ExperimentalApi")]
[Experimental(diagnosticId: "ExperimentalApi")]
#else
[System.Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")]
[Obsolete("This function is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.")]
#endif
public class ReferencedInstanceValidator : IValidatable
{
Expand Down Expand Up @@ -84,8 +85,9 @@ ResultReport IValidatable.Validate(IScopedNode input, ValidationSettings vc, Val
var reference = input.InstanceType switch
{
"Reference" => input.Children("reference").FirstOrDefault()?.Value as string,
"CodeableReference" => input.Children("reference").Children("reference").FirstOrDefault()?.Value as string,
"canonical" => input.Value as string,
_ => throw new InvalidOperationException("Checking reference type should have been handled already.")
var unknown => throw new NotSupportedException($"Encountered unsupported reference type {unknown}.")
};

// It's ok for a reference to have no value (but, say, a description instead),
Expand Down Expand Up @@ -120,12 +122,12 @@ private record ResolutionResult(ITypedElement? ReferencedResource, AggregationMo
/// </summary>
private (IReadOnlyCollection<ResultReport>, ResolutionResult) fetchReference(IScopedNode input, string reference, ValidationSettings vc, ValidationState s)
{
ResolutionResult resolution = new(null, null, null);
List<ResultReport> evidence = new();

// First, try to resolve within this instance (in contained, Bundle.entry)
evidence.Add(resolveLocally(input.ToScopedNode(), reference, s, out resolution));

List<ResultReport> evidence =
[
// First, try to resolve within this instance (in contained, Bundle.entry)
resolveLocally(input.ToScopedNode(), reference, s, out var resolution)
];
// Now that we have tried to fetch the reference locally, we have also determined the kind of
// reference we are dealing with, so check it for aggregation and versioning rules.
if (HasAggregation && AggregationRules?.Any(a => a == resolution.ReferenceKind) == false)
Expand Down Expand Up @@ -179,7 +181,7 @@ private static ResultReport resolveLocally(ScopedNode instance, string reference
resolution = new ResolutionResult(null, null, null);
var identity = new ResourceIdentity(reference);

var (url, version, _) = new Canonical(reference);
var (_, version, _) = new Canonical(reference);
resolution = resolution with { VersioningKind = version is not null ? ReferenceVersionRules.Specific : ReferenceVersionRules.Independent };

if (identity.Form == ResourceIdentityForm.Undetermined)
Expand Down Expand Up @@ -240,7 +242,7 @@ private ResultReport validateReferencedResource(string reference, ValidationSett
/// <inheritdoc cref="IJsonSerializable.ToJson"/>
public JToken ToJson()
{
var result = new JObject()
var result = new JObject
{
new JProperty("schema", Schema.ToJson().MakeNestedProp())
};
Expand All @@ -254,8 +256,16 @@ public JToken ToJson()
}

/// <summary>
/// Whether this validator recognizes the given type as a reference type.
/// Whether this validator supports validating a given reference type.
/// </summary>
internal static bool IsSupportedReferenceType(string typeCode) =>
IsReferenceType(typeCode) && typeCode is not "canonical";

/// <summary>
/// Whether a type is a reference type.
/// </summary>
public static bool IsSupportedReferenceType(string typeCode) => typeCode is "Reference" or "CodeableReference";
/// <param name="typeCode"></param>
/// <returns></returns>
internal static bool IsReferenceType(string typeCode) => typeCode is "Reference" or "CodeableReference" or "canonical";
}
}
1 change: 0 additions & 1 deletion src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,6 @@ static Firely.Fhir.Validation.ExtensionSchema.GetExtensionUri(Firely.Fhir.Valida
static Firely.Fhir.Validation.FhirPathValidator.DefaultCompiler.get -> Hl7.FhirPath.FhirPathCompiler!
static Firely.Fhir.Validation.IssueAssertion.operator !=(Firely.Fhir.Validation.IssueAssertion? left, Firely.Fhir.Validation.IssueAssertion? right) -> bool
static Firely.Fhir.Validation.IssueAssertion.operator ==(Firely.Fhir.Validation.IssueAssertion? left, Firely.Fhir.Validation.IssueAssertion? right) -> bool
static Firely.Fhir.Validation.ReferencedInstanceValidator.IsSupportedReferenceType(string! typeCode) -> bool
static Firely.Fhir.Validation.ResultReport.Combine(System.Collections.Generic.IReadOnlyCollection<Firely.Fhir.Validation.ResultReport!>! reports) -> Firely.Fhir.Validation.ResultReport!
static Firely.Fhir.Validation.StructureDefinitionInformation.operator !=(Firely.Fhir.Validation.StructureDefinitionInformation? left, Firely.Fhir.Validation.StructureDefinitionInformation? right) -> bool
static Firely.Fhir.Validation.StructureDefinitionInformation.operator ==(Firely.Fhir.Validation.StructureDefinitionInformation? left, Firely.Fhir.Validation.StructureDefinitionInformation? right) -> bool
Expand Down
Loading

0 comments on commit 9526cfd

Please sign in to comment.