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

Make IElementSchemaResolver and ElementSchema public #237

Merged
merged 2 commits into from
Dec 19, 2023
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
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))));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, and in other places, you are now calling ValidateInternal, while conceptually, you should be calling the normal Validate I think. (this will immediately call ValidateInternal in its turn, so there is no technical difference). Validate() is still the main entrance point, dealing with ValidateInternal should I think only be necessary for derived classes - we should make it protected.

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!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably the same issue as Validate. Though to have a ToJsonInternal seems overkill here. So just make this public indeed....

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