Skip to content

Commit

Permalink
Merge pull request #237 from FirelyTeam/feature/IElementSchemaResolve…
Browse files Browse the repository at this point in the history
…r-public

Make `IElementSchemaResolver` and `ElementSchema` public
  • Loading branch information
ewoutkramer authored Dec 19, 2023
2 parents b583165 + 9dc555f commit 1ecb390
Show file tree
Hide file tree
Showing 9 changed files with 48 additions and 44 deletions.
11 changes: 5 additions & 6 deletions src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* via any medium is strictly prohibited.
*/

using Hl7.Fhir.ElementModel;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -35,16 +34,16 @@ public DatatypeSchema(StructureDefinitionInformation structureDefinition, IEnume
}

/// <inheritdoc />
public override ResultReport Validate(IEnumerable<IScopedNode> input, ValidationContext vc, ValidationState state)
internal override ResultReport ValidateInternal(IEnumerable<IScopedNode> input, ValidationContext vc, ValidationState state)
{
// Schemas representing the root of a FHIR datatype cannot meaningfully be used as a GroupValidatable,
// so we'll turn this into a normal IValidatable.
var results = input.Select((i, index) => Validate(i, vc, state.UpdateInstanceLocation(d => d.ToIndex(index))));
var results = input.Select((i, index) => ValidateInternal(i, vc, state.UpdateInstanceLocation(d => d.ToIndex(index))));
return ResultReport.Combine(results.ToList());
}

/// <inheritdoc />
public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state)
internal override ResultReport ValidateInternal(IScopedNode input, ValidationContext vc, ValidationState state)
{
// FHIR specific rule about dealing with abstract datatypes (not profiles!): if this schema is an abstract datatype,
// we need to run validation against the schema for the actual type, not the abstract type.
Expand All @@ -55,10 +54,10 @@ public override ResultReport Validate(IScopedNode input, ValidationContext vc, V

var typeProfile = vc.TypeNameMapper.MapTypeName(input.InstanceType);
var fetchResult = FhirSchemaGroupAnalyzer.FetchSchema(vc.ElementSchemaResolver, state, typeProfile);
return fetchResult.Success ? fetchResult.Schema!.Validate(input, vc, state) : fetchResult.Error!;
return fetchResult.Success ? fetchResult.Schema!.ValidateInternal(input, vc, state) : fetchResult.Error!;
}
else
return base.Validate(input, vc, state);
return base.ValidateInternal(input, vc, state);

}

Expand Down
38 changes: 22 additions & 16 deletions src/Firely.Fhir.Validation/Impl/ElementSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* via any medium is strictly prohibited.
*/

using Hl7.Fhir.ElementModel;
using Hl7.Fhir.Utility;
using Newtonsoft.Json.Linq;
using System;
Expand All @@ -19,41 +18,41 @@ namespace Firely.Fhir.Validation
/// schema to be succesful.
/// </summary>
[DataContract]
internal class ElementSchema : IGroupValidatable
public class ElementSchema : IGroupValidatable
{
/// <summary>
/// The unique id for this schema.
/// </summary>
[DataMember]
public Canonical Id { get; private set; }
internal Canonical Id { get; private set; }

/// <summary>
/// The member assertions that constitute this schema.
/// </summary>
[DataMember]
public IReadOnlyCollection<IAssertion> Members { get; private set; }
internal IReadOnlyCollection<IAssertion> Members { get; private set; }

/// <summary>
/// List of assertions to be performed first before the other statements. Failed assertions in this list will cause the
/// other members to fail to execute, which is a performance gain
/// </summary>
public IReadOnlyCollection<IAssertion> ShortcutMembers { get; private set; }
internal IReadOnlyCollection<IAssertion> ShortcutMembers { get; private set; }

/// <summary>
/// Lists the <see cref="CardinalityValidator"/> present in the members of this schema.
/// </summary>
public IReadOnlyCollection<CardinalityValidator> CardinalityValidators { get; private set; } = Array.Empty<CardinalityValidator>();
internal IReadOnlyCollection<CardinalityValidator> CardinalityValidators { get; private set; } = Array.Empty<CardinalityValidator>();

/// <inheritdoc cref="ElementSchema(Canonical, IEnumerable{IAssertion})"/>
public ElementSchema(Canonical id, params IAssertion[] members) : this(id, members.AsEnumerable())
internal ElementSchema(Canonical id, params IAssertion[] members) : this(id, members.AsEnumerable())
{
// nothing
}

/// <summary>
/// Constructs a new <see cref="ElementSchema"/> with the given members and id.
/// </summary>
public ElementSchema(Canonical id, IEnumerable<IAssertion> members)
internal ElementSchema(Canonical id, IEnumerable<IAssertion> members)
{
Members = members.ToList();
ShortcutMembers = extractShortcutMembers(Members);
Expand All @@ -69,9 +68,7 @@ public ElementSchema(Canonical id, IEnumerable<IAssertion> members)
private static IReadOnlyCollection<IAssertion> extractShortcutMembers(IEnumerable<IAssertion> members)
=> members.OfType<FhirTypeLabelValidator>().ToList();


/// <inheritdoc cref="IGroupValidatable.Validate(IEnumerable{IScopedNode}, ValidationContext, ValidationState)"/>
public virtual ResultReport Validate(
internal virtual ResultReport ValidateInternal(
IEnumerable<IScopedNode> input,
ValidationContext vc,
ValidationState state)
Expand All @@ -95,11 +92,17 @@ public virtual ResultReport Validate(
return ResultReport.Combine(subresult.ToList());
}

/// <inheritdoc />
public virtual ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state)

/// <inheritdoc cref="IGroupValidatable.Validate(IEnumerable{IScopedNode}, ValidationContext, ValidationState)"/>
ResultReport IGroupValidatable.Validate(
IEnumerable<IScopedNode> input,
ValidationContext vc,
ValidationState state) => ValidateInternal(input, vc, state);

internal virtual ResultReport ValidateInternal(IScopedNode input, ValidationContext vc, ValidationState state)
{
// If we have shortcut members, run them first
if (ShortcutMembers.Any())
if (ShortcutMembers.Count != 0)
{
var subResult = ShortcutMembers.Where(vc.Filter).Select(ma => ma.ValidateOne(input, vc, state));
var report = ResultReport.Combine(subResult.ToList());
Expand All @@ -111,6 +114,9 @@ public virtual ResultReport Validate(IScopedNode input, ValidationContext vc, Va
return ResultReport.Combine(subresult.ToList());
}

/// <inheritdoc />
ResultReport IValidatable.Validate(IScopedNode input, ValidationContext vc, ValidationState state) => ValidateInternal(input, vc, state);

/// <summary>
/// Lists additional properties shown as metadata on the schema, separate from the members.
/// </summary>
Expand Down Expand Up @@ -155,12 +161,12 @@ static JToken nest(JToken mem) =>
/// Find the first subschema with the given anchor.
/// </summary>
/// <returns>An <see cref="ElementSchema"/> if found, otherwise <c>null</c>.</returns>
public ElementSchema? FindFirstByAnchor(string anchor) =>
internal ElementSchema? FindFirstByAnchor(string anchor) =>
Members.OfType<DefinitionsAssertion>().Select(da => da.FindFirstByAnchor(anchor)).FirstOrDefault(s => s is not null);

/// <summary>
/// Whether the schema has members.
/// </summary>
public bool IsEmpty() => !Members.Any();
internal bool IsEmpty() => !Members.Any();
}
}
9 changes: 4 additions & 5 deletions src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* via any medium is strictly prohibited.
*/

using Hl7.Fhir.ElementModel;
using Hl7.Fhir.Support;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -48,7 +47,7 @@ public ExtensionSchema(StructureDefinitionInformation structureDefinition, IEnum
.FirstOrDefault(); // this will actually always be max one, but that's validated by a cardinality validator.

/// <inheritdoc/>
public override ResultReport Validate(IEnumerable<IScopedNode> input, ValidationContext vc, ValidationState state)
internal override ResultReport ValidateInternal(IEnumerable<IScopedNode> input, ValidationContext vc, ValidationState state)
{
// Group the instances by their url - this allows a IGroupValidatable schema for the
// extension to validate the "extension cardinality".
Expand Down Expand Up @@ -128,11 +127,11 @@ static ExtensionUrlFollower callback(ExtensionUrlFollower? follower) =>
/// </summary>
protected ResultReport ValidateExtensionSchema(IEnumerable<IScopedNode> input,
ValidationContext vc,
ValidationState state) => base.Validate(input, vc, state);
ValidationState state) => base.ValidateInternal(input, vc, state);

/// <inheritdoc/>
public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) =>
Validate(new[] { input }, vc, state);
internal override ResultReport ValidateInternal(IScopedNode input, ValidationContext vc, ValidationState state) =>
ValidateInternal(new[] { input }, vc, state);


/// <inheritdoc />
Expand Down
9 changes: 4 additions & 5 deletions src/Firely.Fhir.Validation/Impl/FhirSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* via any medium is strictly prohibited.
*/

using Hl7.Fhir.ElementModel;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -42,19 +41,19 @@ public FhirSchema(StructureDefinitionInformation structureDefinition, IEnumerabl
}

/// <inheritdoc/>
public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state)
internal override ResultReport ValidateInternal(IScopedNode input, ValidationContext vc, ValidationState state)
{
state = state
.UpdateLocation(sp => sp.InvokeSchema(this))
.UpdateInstanceLocation(ip => ip.StartResource(input.InstanceType));
return base.Validate(input, vc, state);
return base.ValidateInternal(input, vc, state);
}

/// <inheritdoc/>
public override ResultReport Validate(IEnumerable<IScopedNode> input, ValidationContext vc, ValidationState state)
internal override ResultReport ValidateInternal(IEnumerable<IScopedNode> input, ValidationContext vc, ValidationState state)
{
state = state.UpdateLocation(sp => sp.InvokeSchema(this));
return base.Validate(input, vc, state);
return base.ValidateInternal(input, vc, state);
}

/// <summary>
Expand Down
14 changes: 6 additions & 8 deletions src/Firely.Fhir.Validation/Impl/ResourceSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
* via any medium is strictly prohibited.
*/

using Hl7.Fhir.ElementModel;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
Expand Down Expand Up @@ -56,16 +54,16 @@ static MetaProfileSelector callback(MetaProfileSelector? selector)
}

/// <inheritdoc />
public override ResultReport Validate(IEnumerable<IScopedNode> input, ValidationContext vc, ValidationState state)
internal override ResultReport ValidateInternal(IEnumerable<IScopedNode> input, ValidationContext vc, ValidationState state)
{
// Schemas representing the root of a FHIR resource cannot meaningfully be used as a GroupValidatable,
// so we'll turn this into a normal IValidatable.
var results = input.Select((i, index) => Validate(i, vc, state.UpdateInstanceLocation(d => d.ToIndex(index))));
var results = input.Select((i, index) => ValidateInternal(i, vc, state.UpdateInstanceLocation(d => d.ToIndex(index))));
return ResultReport.Combine(results.ToList());
}

/// <inheritdoc />
public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state)
internal override ResultReport ValidateInternal(IScopedNode input, ValidationContext vc, ValidationState state)
{
// FHIR specific rule about dealing with abstract datatypes (not profiles!): if this schema is an abstract datatype,
// we need to run validation against the schema for the actual type, not the abstract type.
Expand All @@ -76,7 +74,7 @@ public override ResultReport Validate(IScopedNode input, ValidationContext vc, V

var typeProfile = vc.TypeNameMapper.MapTypeName(input.InstanceType);
var fetchResult = FhirSchemaGroupAnalyzer.FetchSchema(vc.ElementSchemaResolver, state.UpdateLocation(d => d.InvokeSchema(this)), typeProfile);
return fetchResult.Success ? fetchResult.Schema!.Validate(input, vc, state) : fetchResult.Error!;
return fetchResult.Success ? fetchResult.Schema!.ValidateInternal(input, vc, state) : fetchResult.Error!;
}

// Update instance location state to start of a new Resource
Expand All @@ -103,7 +101,7 @@ public override ResultReport Validate(IScopedNode input, ValidationContext vc, V
// Now that we have fetched the set of most appropriate profiles, call their constraint validation -
// this should exclude the special fetch magic for Meta.profile (this function) to avoid a loop, so we call the actual validation here.
var validationResult = minimalSet.Select(s => s.ValidateResourceSchema(input, vc, state)).ToList();
var validationResultOther = fetchedNonFhirSchemas.Select(s => s.Validate(input, vc, state)).ToList();
var validationResultOther = fetchedNonFhirSchemas.Select(s => s.ValidateInternal(input, vc, state)).ToList();
return ResultReport.Combine(fetchErrors.Append(consistencyReport).Concat(validationResult).Concat(validationResultOther).ToArray());
}

Expand All @@ -119,7 +117,7 @@ protected ResultReport ValidateResourceSchema(IScopedNode input, ValidationConte
() =>
{
state.Global.ResourcesValidated += 1;
return base.Validate(input, vc, state);
return base.ValidateInternal(input, vc, state);
});
}

Expand Down
3 changes: 1 addition & 2 deletions src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
* via any medium is strictly prohibited.
*/

using Hl7.Fhir.ElementModel;
using Hl7.Fhir.Model;
using Newtonsoft.Json.Linq;
using System;
Expand Down Expand Up @@ -48,7 +47,7 @@ public ResultReport Validate(IEnumerable<IScopedNode> input, ValidationContext v

return FhirSchemaGroupAnalyzer.FetchSchema(vc.ElementSchemaResolver, state, SchemaUri) switch
{
(var schema, null, _) => schema!.Validate(input, vc, state),
(var schema, null, _) => schema!.ValidateInternal(input, vc, state),
(_, var error, _) => error
};
}
Expand Down
4 changes: 4 additions & 0 deletions src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@ Firely.Fhir.Validation.Canonical.Original.get -> string!
Firely.Fhir.Validation.Canonical.ToUri() -> System.Uri!
Firely.Fhir.Validation.Canonical.Uri.get -> string?
Firely.Fhir.Validation.Canonical.Version.get -> string?
Firely.Fhir.Validation.ElementSchema
Firely.Fhir.Validation.ExtensionUrlFollower
Firely.Fhir.Validation.ExtensionUrlHandling
Firely.Fhir.Validation.ExtensionUrlHandling.DontResolve = 0 -> Firely.Fhir.Validation.ExtensionUrlHandling
Firely.Fhir.Validation.ExtensionUrlHandling.ErrorIfMissing = 2 -> Firely.Fhir.Validation.ExtensionUrlHandling
Firely.Fhir.Validation.ExtensionUrlHandling.WarnIfMissing = 1 -> Firely.Fhir.Validation.ExtensionUrlHandling
Firely.Fhir.Validation.IAssertion
Firely.Fhir.Validation.IElementSchemaResolver
Firely.Fhir.Validation.IElementSchemaResolver.GetSchema(Firely.Fhir.Validation.Canonical! schemaUri) -> Firely.Fhir.Validation.ElementSchema?
Firely.Fhir.Validation.IExternalReferenceResolver
Firely.Fhir.Validation.IExternalReferenceResolver.ResolveAsync(string! reference) -> System.Threading.Tasks.Task<object?>!
Firely.Fhir.Validation.IJsonSerializable
Expand Down Expand Up @@ -116,6 +119,7 @@ virtual Firely.Fhir.Validation.Canonical.<Clone>$() -> Firely.Fhir.Validation.Ca
virtual Firely.Fhir.Validation.Canonical.EqualityContract.get -> System.Type!
virtual Firely.Fhir.Validation.Canonical.Equals(Firely.Fhir.Validation.Canonical? other) -> bool
virtual Firely.Fhir.Validation.Canonical.PrintMembers(System.Text.StringBuilder! builder) -> bool
virtual Firely.Fhir.Validation.ElementSchema.ToJson() -> Newtonsoft.Json.Linq.JToken!
virtual Firely.Fhir.Validation.ExtensionUrlFollower.Invoke(string! location, Firely.Fhir.Validation.Canonical? url) -> Firely.Fhir.Validation.ExtensionUrlHandling
virtual Firely.Fhir.Validation.MetaProfileSelector.Invoke(string! location, Firely.Fhir.Validation.Canonical![]! originalProfiles) -> Firely.Fhir.Validation.Canonical![]!
virtual Firely.Fhir.Validation.TypeNameMapper.Invoke(string! local) -> Firely.Fhir.Validation.Canonical?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Firely.Fhir.Validation
/// <summary>
/// An interface for objects that let you obtain an <see cref="ElementSchema"/> by its schema uri.
/// </summary>
internal interface IElementSchemaResolver
public interface IElementSchemaResolver
{
/// <summary>
/// Retrieve a schema by its schema uri.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public void AvoidsRedoingProfileValidation()
vc.ResolveExternalReference = resolveTestData;

var validationState = new ValidationState();
var result = schemaElement!.Validate(new ScopedNode(all.ToTypedElement()).AsScopedNode(), vc, validationState);
var result = schemaElement!.ValidateInternal(new ScopedNode(all.ToTypedElement()).AsScopedNode(), vc, validationState);
result.Result.Should().Be(ValidationResult.Failure);
var issues = result.Evidence.OfType<IssueAssertion>().ToList();
issues.Count.Should().Be(1); // Bundle.entry[2].resource[0] is validated twice against different profiles.
Expand Down

0 comments on commit 1ecb390

Please sign in to comment.