Skip to content
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
31 changes: 31 additions & 0 deletions src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -584,3 +584,34 @@ virtual Firely.Fhir.Validation.InvariantValidator.PrintMembers(System.Text.Strin
Firely.Fhir.Validation.NamedExtensionMapper
Firely.Fhir.Validation.ValidationSettings.MapNamedElement -> Firely.Fhir.Validation.NamedExtensionMapper!
virtual Firely.Fhir.Validation.NamedExtensionMapper.Invoke(string! jsonName) -> string?
~override Firely.Fhir.Validation.AssertionStep.Equals(object obj) -> bool
Firely.Fhir.Validation.AssertionContainerExtensions
Firely.Fhir.Validation.AssertionStep
Firely.Fhir.Validation.AssertionStep.Anchor.get -> string?
Firely.Fhir.Validation.AssertionStep.AssertionStep() -> void
Firely.Fhir.Validation.AssertionStep.ElementName.get -> string?
Firely.Fhir.Validation.AssertionStep.Equals(Firely.Fhir.Validation.AssertionStep other) -> bool
Firely.Fhir.Validation.AssertionStep.Kind.get -> Firely.Fhir.Validation.AssertionStepKind
Firely.Fhir.Validation.AssertionStep.Name.get -> string?
Firely.Fhir.Validation.AssertionStep.SliceName.get -> string?
Firely.Fhir.Validation.AssertionStep.TargetType.get -> string?
Firely.Fhir.Validation.AssertionStepKind
Firely.Fhir.Validation.AssertionStepKind.Child = 1 -> Firely.Fhir.Validation.AssertionStepKind
Firely.Fhir.Validation.AssertionStepKind.Member = 0 -> Firely.Fhir.Validation.AssertionStepKind
Firely.Fhir.Validation.AssertionStepKind.ReferenceTarget = 4 -> Firely.Fhir.Validation.AssertionStepKind
Firely.Fhir.Validation.AssertionStepKind.Slice = 2 -> Firely.Fhir.Validation.AssertionStepKind
Firely.Fhir.Validation.AssertionStepKind.Subschema = 3 -> Firely.Fhir.Validation.AssertionStepKind
Firely.Fhir.Validation.IAssertionContainer
Firely.Fhir.Validation.IAssertionContainer.WithChildren(System.Func<Firely.Fhir.Validation.AssertionStep, Firely.Fhir.Validation.IAssertion!, Firely.Fhir.Validation.IAssertion!>! rewrite) -> Firely.Fhir.Validation.IAssertion!
override Firely.Fhir.Validation.AssertionStep.GetHashCode() -> int
override Firely.Fhir.Validation.AssertionStep.ToString() -> string!
static Firely.Fhir.Validation.AssertionContainerExtensions.Children(this Firely.Fhir.Validation.IAssertionContainer! container) -> System.Collections.Generic.IReadOnlyList<(Firely.Fhir.Validation.AssertionStep Step, Firely.Fhir.Validation.IAssertion! Child)>!
static Firely.Fhir.Validation.AssertionContainerExtensions.TryRewriteItems<T>(this System.Collections.Generic.IReadOnlyCollection<T>! items, System.Func<T, (Firely.Fhir.Validation.AssertionStep Step, Firely.Fhir.Validation.IAssertion! Child)>! select, System.Func<T, Firely.Fhir.Validation.IAssertion!, T>! replace, System.Func<Firely.Fhir.Validation.AssertionStep, Firely.Fhir.Validation.IAssertion!, Firely.Fhir.Validation.IAssertion!>! rewrite) -> T[]?
static Firely.Fhir.Validation.AssertionContainerExtensions.TryRewriteMembers(this System.Collections.Generic.IReadOnlyCollection<Firely.Fhir.Validation.IAssertion!>! members, Firely.Fhir.Validation.AssertionStep step, System.Func<Firely.Fhir.Validation.AssertionStep, Firely.Fhir.Validation.IAssertion!, Firely.Fhir.Validation.IAssertion!>! rewrite) -> Firely.Fhir.Validation.IAssertion![]?
static Firely.Fhir.Validation.AssertionStep.Child(string! elementName) -> Firely.Fhir.Validation.AssertionStep
static Firely.Fhir.Validation.AssertionStep.Member() -> Firely.Fhir.Validation.AssertionStep
static Firely.Fhir.Validation.AssertionStep.operator !=(Firely.Fhir.Validation.AssertionStep left, Firely.Fhir.Validation.AssertionStep right) -> bool
static Firely.Fhir.Validation.AssertionStep.operator ==(Firely.Fhir.Validation.AssertionStep left, Firely.Fhir.Validation.AssertionStep right) -> bool
static Firely.Fhir.Validation.AssertionStep.ReferenceTarget(string? targetType) -> Firely.Fhir.Validation.AssertionStep
static Firely.Fhir.Validation.AssertionStep.Slice(string! sliceName) -> Firely.Fhir.Validation.AssertionStep
static Firely.Fhir.Validation.AssertionStep.Subschema(string! anchor) -> Firely.Fhir.Validation.AssertionStep
75 changes: 65 additions & 10 deletions src/Firely.Fhir.Validation/Schema/IAssertionContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using System;
using System.Collections.Generic;
using System.ComponentModel;

namespace Firely.Fhir.Validation
{
Expand All @@ -18,7 +19,13 @@ namespace Firely.Fhir.Validation
/// <see cref="PathStack"/> during validation. Only steps that are visible in the compiled schema
/// exist here: the instance-side events (indexes, internal references, resource starts) have no
/// meaning while rewriting a schema.</remarks>
internal enum AssertionStepKind
[EditorBrowsable(EditorBrowsableState.Never)]
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.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.")]
#endif
public enum AssertionStepKind
{
/// <summary>
/// A member of the container that does not change the position within the instance: a member of an
Expand Down Expand Up @@ -62,8 +69,35 @@ internal enum AssertionStepKind
/// </summary>
/// <remarks>The step describes how the nested assertion is reached, not what it is: identity (canonical,
/// version, schema id) is carried by the schemas encountered along the way, not by the steps.</remarks>
internal readonly record struct AssertionStep(AssertionStepKind Kind, string? Name = null)
[EditorBrowsable(EditorBrowsableState.Never)]
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.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.")]
#endif
public readonly record struct AssertionStep
{
// Steps are only created by the factory methods below, so every step has a name exactly when its
// kind requires one. Note that default(AssertionStep) is a Member step, which is valid as well.
private AssertionStep(AssertionStepKind kind, string? name = null)
{
Kind = kind;
Name = name;
}

/// <summary>
/// The kind of step.
/// </summary>
public AssertionStepKind Kind { get; }

/// <summary>
/// The name that goes with the kind of step: the element name, slice name, anchor or target type.
/// Always <c>null</c> for a <see cref="AssertionStepKind.Member"/> step, and never <c>null</c> for a
/// <see cref="AssertionStepKind.Child"/>, <see cref="AssertionStepKind.Slice"/> or
/// <see cref="AssertionStepKind.Subschema"/> step.
/// </summary>
public string? Name { get; }

/// <summary>
/// A step to a member that does not change the position within the instance.
/// </summary>
Expand All @@ -72,17 +106,20 @@ internal readonly record struct AssertionStep(AssertionStepKind Kind, string? Na
/// <summary>
/// A step to the assertions for the child element with the given name.
/// </summary>
public static AssertionStep Child(string elementName) => new(AssertionStepKind.Child, elementName);
public static AssertionStep Child(string elementName) =>
new(AssertionStepKind.Child, elementName ?? throw new ArgumentNullException(nameof(elementName)));

/// <summary>
/// A step to the assertions for the slice with the given name.
/// </summary>
public static AssertionStep Slice(string sliceName) => new(AssertionStepKind.Slice, sliceName);
public static AssertionStep Slice(string sliceName) =>
new(AssertionStepKind.Slice, sliceName ?? throw new ArgumentNullException(nameof(sliceName)));

/// <summary>
/// A step to the subschema with the given anchor.
/// </summary>
public static AssertionStep Subschema(string anchor) => new(AssertionStepKind.Subschema, anchor);
public static AssertionStep Subschema(string anchor) =>
new(AssertionStepKind.Subschema, anchor ?? throw new ArgumentNullException(nameof(anchor)));

/// <summary>
/// A step to the schema for the targets of a reference of the given type, or for the targets of
Expand Down Expand Up @@ -130,10 +167,18 @@ internal readonly record struct AssertionStep(AssertionStepKind Kind, string? Na
/// walked and rewritten without knowing the concrete container types.
/// </summary>
/// <remarks>Every <see cref="IAssertion"/> that keeps nested assertions as part of its state must
/// implement this interface - <c>AssertionContainerTests</c> enforces that for this assembly. Implement
/// it explicitly: it is an implementation detail of schema rewriting, not part of the assertion's
/// public surface.</remarks>
internal interface IAssertionContainer
/// implement this interface - <c>AssertionContainerTests</c> enforces that for this assembly. That
/// includes custom building blocks defined outside this library: an assertion that holds nested
/// assertions but does not implement this interface is opaque to schema rewriters, which then silently
/// leave everything nested in it unchanged. Implement it explicitly: it is an implementation detail of
/// schema rewriting, not part of the assertion's public surface.</remarks>
[EditorBrowsable(EditorBrowsableState.Never)]
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.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.")]
#endif
public interface IAssertionContainer
{
/// <summary>
/// Returns a copy of this container in which every nested assertion is replaced by the result of
Expand All @@ -155,7 +200,17 @@ internal interface IAssertionContainer
IAssertion WithChildren(Func<AssertionStep, IAssertion, IAssertion> rewrite);
}

internal static class AssertionContainerExtensions
/// <summary>
/// Helpers for walking <see cref="IAssertionContainer"/>s, and for implementing
/// <see cref="IAssertionContainer.WithChildren(Func{AssertionStep, IAssertion, IAssertion})"/>.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
#if NET8_0_OR_GREATER
[System.Diagnostics.CodeAnalysis.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.")]
#endif
public static class AssertionContainerExtensions
{
/// <summary>
/// Enumerates the nested assertions of a container, with the step leading to each of them.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ public void ReferenceTargetsCarryTheirType()
Assert.AreEqual("->*", untypedStep.ToString());
}

[TestMethod]
public void NamedStepsRequireTheirName()
{
Assert.ThrowsException<ArgumentNullException>(() => AssertionStep.Child(null!));
Assert.ThrowsException<ArgumentNullException>(() => AssertionStep.Slice(null!));
Assert.ThrowsException<ArgumentNullException>(() => AssertionStep.Subschema(null!));

// the default step is a (nameless) member, which is a valid step
Assert.AreEqual(AssertionStep.Member(), default(AssertionStep));
}

#endregion

#region an unchanged container keeps its identity
Expand Down
Loading