diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index 74c26f28..a4813d7e 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -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! 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(this System.Collections.Generic.IReadOnlyCollection! items, System.Func! select, System.Func! replace, System.Func! rewrite) -> T[]? +static Firely.Fhir.Validation.AssertionContainerExtensions.TryRewriteMembers(this System.Collections.Generic.IReadOnlyCollection! members, Firely.Fhir.Validation.AssertionStep step, System.Func! 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 diff --git a/src/Firely.Fhir.Validation/Schema/IAssertionContainer.cs b/src/Firely.Fhir.Validation/Schema/IAssertionContainer.cs index 71ca2de3..c9aca7c8 100644 --- a/src/Firely.Fhir.Validation/Schema/IAssertionContainer.cs +++ b/src/Firely.Fhir.Validation/Schema/IAssertionContainer.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; namespace Firely.Fhir.Validation { @@ -18,7 +19,13 @@ namespace Firely.Fhir.Validation /// 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. - 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 { /// /// A member of the container that does not change the position within the instance: a member of an @@ -62,8 +69,35 @@ internal enum AssertionStepKind /// /// 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. - 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; + } + + /// + /// The kind of step. + /// + public AssertionStepKind Kind { get; } + + /// + /// The name that goes with the kind of step: the element name, slice name, anchor or target type. + /// Always null for a step, and never null for a + /// , or + /// step. + /// + public string? Name { get; } + /// /// A step to a member that does not change the position within the instance. /// @@ -72,17 +106,20 @@ internal readonly record struct AssertionStep(AssertionStepKind Kind, string? Na /// /// A step to the assertions for the child element with the given name. /// - 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))); /// /// A step to the assertions for the slice with the given name. /// - 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))); /// /// A step to the subschema with the given anchor. /// - 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))); /// /// A step to the schema for the targets of a reference of the given type, or for the targets of @@ -130,10 +167,18 @@ internal readonly record struct AssertionStep(AssertionStepKind Kind, string? Na /// walked and rewritten without knowing the concrete container types. /// /// Every that keeps nested assertions as part of its state must - /// implement this interface - AssertionContainerTests enforces that for this assembly. Implement - /// it explicitly: it is an implementation detail of schema rewriting, not part of the assertion's - /// public surface. - internal interface IAssertionContainer + /// implement this interface - AssertionContainerTests 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. + [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 { /// /// Returns a copy of this container in which every nested assertion is replaced by the result of @@ -155,7 +200,17 @@ internal interface IAssertionContainer IAssertion WithChildren(Func rewrite); } - internal static class AssertionContainerExtensions + /// + /// Helpers for walking s, and for implementing + /// . + /// + [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 { /// /// Enumerates the nested assertions of a container, with the step leading to each of them. diff --git a/test/Firely.Fhir.Validation.Tests/Schema/AssertionContainerTests.cs b/test/Firely.Fhir.Validation.Tests/Schema/AssertionContainerTests.cs index 0107e876..7caa3a0a 100644 --- a/test/Firely.Fhir.Validation.Tests/Schema/AssertionContainerTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Schema/AssertionContainerTests.cs @@ -215,6 +215,17 @@ public void ReferenceTargetsCarryTheirType() Assert.AreEqual("->*", untypedStep.ToString()); } + [TestMethod] + public void NamedStepsRequireTheirName() + { + Assert.ThrowsException(() => AssertionStep.Child(null!)); + Assert.ThrowsException(() => AssertionStep.Slice(null!)); + Assert.ThrowsException(() => 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