Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for CodeableReference in binding+reference validator. #276

Merged
merged 2 commits into from
Feb 2, 2024
Merged
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
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