From 9d38952c420e21357652c155c74bf7ffe4bc8b60 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 14 Nov 2023 13:50:25 +0100 Subject: [PATCH 01/27] Use IScopedNode instead of ITypedElement for the interfaces IValidatable and IGroupValidatable. --- .../ElementDefinitionValidator.cs | 8 +++---- .../StructureDefinitionValidator.cs | 6 ++--- .../Impl/AllValidator.cs | 6 ++--- .../Impl/AnyValidator.cs | 6 ++--- .../Impl/BasicValidator.cs | 2 +- .../Impl/BindingValidator.cs | 6 ++--- .../Impl/CanonicalValidator.cs | 2 +- .../Impl/CardinalityValidator.cs | 4 ++-- .../Impl/ChildrenValidator.cs | 12 +++++----- .../Impl/DatatypeSchema.cs | 4 ++-- .../Impl/ElementSchema.cs | 8 +++---- .../Impl/ExtensionSchema.cs | 8 +++---- .../Impl/FhirEle1Validator.cs | 2 +- .../Impl/FhirExt1Validator.cs | 2 +- .../Impl/FhirPathValidator.cs | 6 ++--- src/Firely.Fhir.Validation/Impl/FhirSchema.cs | 4 ++-- .../Impl/FhirTxt1Validator.cs | 6 ++--- .../Impl/FhirTxt2Validator.cs | 2 +- .../Impl/FhirTypeLabelValidator.cs | 4 ++-- .../Impl/FixedValidator.cs | 6 ++--- .../Impl/InvariantValidator.cs | 4 ++-- .../Impl/IssueAssertion.cs | 4 ++-- .../Impl/MaxLengthValidator.cs | 2 +- .../Impl/MinMaxValueValidator.cs | 2 +- .../Impl/PathSelectorValidator.cs | 15 ++++++++---- .../Impl/PatternValidator.cs | 4 ++-- .../Impl/ReferencedInstanceValidator.cs | 10 ++++---- .../Impl/RegExValidator.cs | 4 ++-- .../Impl/ResourceSchema.cs | 12 +++++----- .../Impl/ResultAssertion.cs | 2 +- .../Impl/SchemaReferenceValidator.cs | 6 ++--- .../Impl/SliceValidator.cs | 16 ++++++------- .../Impl/TraceAssertion.cs | 2 +- .../Schema/AssertionValidators.cs | 24 +++++++++++++------ .../Schema/IGroupValidatable.cs | 2 +- .../Schema/IValidatable.cs | 4 ++-- .../Schema/ValueElementNode.cs | 18 ++++++++------ .../ValidationSettings.cs | 2 +- src/Hl7.Fhir.Validation.Shared/Validator.cs | 2 +- .../BasicSchemaBuilderTests.cs | 2 +- .../FhirTestCases | 2 +- .../Impl/AllValidatorTests.cs | 2 +- .../Impl/ResourceSchemaTests.cs | 9 +++---- .../Support/IValidatableExtensions.cs | 21 ++++++++++++++++ .../Support/TypedElementOnDictionary.cs | 6 ++++- 45 files changed, 163 insertions(+), 118 deletions(-) create mode 100644 test/Firely.Fhir.Validation.Tests/Support/IValidatableExtensions.cs diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs index bb65cefe..1c7f3d09 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs @@ -18,7 +18,7 @@ public class ElementDefinitionValidator : IValidatable public JToken ToJson() => new JProperty("elementDefinition", new JObject()); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { var evidence = new List(); @@ -34,7 +34,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati /// /// /// - private static IEnumerable validateTypeCompatibilityOfValues(ITypedElement input, ValidationState state) + private static IEnumerable validateTypeCompatibilityOfValues(IScopedNode input, ValidationState state) { var typeNames = getTypeNames(input); @@ -65,7 +65,7 @@ private static IEnumerable validateTypeCompatibilityOfValues(IType return Enumerable.Empty(); } - private static IEnumerable getTypeNames(ITypedElement input) + private static IEnumerable getTypeNames(IScopedNode input) { var typeComponents = input.Children("type"); return typeComponents.SelectMany(t => @@ -74,7 +74,7 @@ private static IEnumerable getTypeNames(ITypedElement input) .Select(c => c.Value.ToString()); } - private static IEnumerable validateType(IEnumerable valueTypes, string propertyName, IEnumerable typeNames, ITypedElement input, ValidationState state) + private static IEnumerable validateType(IEnumerable valueTypes, string propertyName, IEnumerable typeNames, IScopedNode input, ValidationState state) { return valueTypes.Where(t => !typeNames.Contains(t)) .Select(t => new IssueAssertion(Issue.PROFILE_ELEMENTDEF_INCORRECT, $"Type of the {propertyName} property '{t}' doesn't match with the type(s) of the element '{string.Join(',', typeNames)}'").AsResult(state)); diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs index fb81a9f3..10e70594 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs @@ -18,7 +18,7 @@ public class StructureDefinitionValidator : IValidatable public JToken ToJson() => new JProperty("elementDefinition", new JObject()); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { //this can be expanded with other validate functionality var evidence = validateInvariantUniqueness(input, state); @@ -33,7 +33,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati /// /// /// - private static List validateInvariantUniqueness(ITypedElement input, ValidationState state) + private static List validateInvariantUniqueness(IScopedNode input, ValidationState state) { var snapshotElements = input.Children("snapshot").SelectMany(c => c.Children("element")); var diffElements = input.Children("differential").SelectMany(c => c.Children("element")); @@ -44,7 +44,7 @@ private static List validateInvariantUniqueness(ITypedElement inpu return snapshotEvidence.Concat(diffEvidence).Select(i => i.AsResult(state)).ToList(); } - private static List validateInvariantUniqueness(IEnumerable elements) + private static List validateInvariantUniqueness(IEnumerable elements) { //Selects the combination of key and elementDefintion path for the duplicate keys where the paths are not also the same. diff --git a/src/Firely.Fhir.Validation/Impl/AllValidator.cs b/src/Firely.Fhir.Validation/Impl/AllValidator.cs index e0fc2187..3f36e527 100644 --- a/src/Firely.Fhir.Validation/Impl/AllValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/AllValidator.cs @@ -65,9 +65,9 @@ public AllValidator(bool shortcircuitEvaluation, params IAssertion[] members) : { } - /// + /// public ResultReport Validate( - IEnumerable input, + IEnumerable input, ValidationContext vc, ValidationState state) { @@ -89,7 +89,7 @@ public ResultReport Validate( } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); /// diff --git a/src/Firely.Fhir.Validation/Impl/AnyValidator.cs b/src/Firely.Fhir.Validation/Impl/AnyValidator.cs index 863cbe1b..9a7aa9a4 100644 --- a/src/Firely.Fhir.Validation/Impl/AnyValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/AnyValidator.cs @@ -48,9 +48,9 @@ public AnyValidator(params IAssertion[] members) : this(members.AsEnumerable(), { } - /// + /// public ResultReport Validate( - IEnumerable input, + IEnumerable input, ValidationContext vc, ValidationState state) { @@ -82,7 +82,7 @@ public ResultReport Validate( } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); /// diff --git a/src/Firely.Fhir.Validation/Impl/BasicValidator.cs b/src/Firely.Fhir.Validation/Impl/BasicValidator.cs index 467bae59..1f66fc1e 100644 --- a/src/Firely.Fhir.Validation/Impl/BasicValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/BasicValidator.cs @@ -18,7 +18,7 @@ public abstract class BasicValidator : IValidatable public virtual JToken ToJson() => new JProperty(Key, Value); /// - public abstract ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state); + public abstract ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state); /// /// The name of the property used in the json serialization for this validator." diff --git a/src/Firely.Fhir.Validation/Impl/BindingValidator.cs b/src/Firely.Fhir.Validation/Impl/BindingValidator.cs index 357e806c..20210598 100644 --- a/src/Firely.Fhir.Validation/Impl/BindingValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/BindingValidator.cs @@ -102,7 +102,7 @@ public BindingValidator(Canonical valueSetUri, BindingStrength? strength, bool a } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState s) { if (input is null) throw Error.ArgumentNull(nameof(input)); if (input.InstanceType is null) throw Error.Argument(nameof(input), "Binding validation requires input to have an instance type."); @@ -142,7 +142,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati /// Validates whether the instance has the minimum required coded content, depending on the binding. /// /// Will throw an InvalidOperationException when the input is not of a bindeable type. - private ResultReport verifyContentRequirements(ITypedElement source, Element bindable, ValidationState s) + private ResultReport verifyContentRequirements(IScopedNode source, Element bindable, ValidationState s) { switch (bindable) { @@ -166,7 +166,7 @@ private static bool codeableConceptHasCode(CodeableConcept cc) => cc.Coding.Any(cd => !string.IsNullOrEmpty(cd.Code)); - private ResultReport validateCode(ITypedElement source, Element bindable, ValidationContext vc, ValidationState s) + private ResultReport validateCode(IScopedNode source, Element bindable, ValidationContext 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() diff --git a/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs b/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs index 647d0698..39e120dd 100644 --- a/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs @@ -17,7 +17,7 @@ public class CanonicalValidator : IValidatable public JToken ToJson() => new JProperty("canonical", new JObject()); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { switch (input.Value) { diff --git a/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs b/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs index 3ad38508..976c00a8 100644 --- a/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs @@ -75,7 +75,7 @@ string other when int.TryParse(other, out var maximum) => maximum, } /// - public ResultReport Validate(IEnumerable input, ValidationContext _, ValidationState s) + public ResultReport Validate(IEnumerable input, ValidationContext _, ValidationState s) { var count = input.Count(); return buildResult(count, s); @@ -87,7 +87,7 @@ private ResultReport buildResult(int count, ValidationState s) => !inRange(count : ResultReport.SUCCESS; /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => buildResult(1, state); private bool inRange(int x) => (!Min.HasValue || x >= Min.Value) && (!Max.HasValue || x <= Max.Value); diff --git a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs index af1be4f2..ad823960 100644 --- a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs @@ -76,7 +76,7 @@ public JToken ToJson() => new JProperty(child.Key, child.Value.ToJson().MakeNestedProp())) }); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { var evidence = new List(); @@ -111,7 +111,7 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati static string? choiceElement(Match m) => m.ChildName.EndsWith("[x]") ? m.InstanceElements?.FirstOrDefault().InstanceType : null; } - private static readonly List NOELEMENTS = new(); + private static readonly List NOELEMENTS = new(); #region IDictionary implementation /// @@ -146,7 +146,7 @@ public IAssertion this[string key] internal class ChildNameMatcher { - public static MatchResult Match(IReadOnlyDictionary assertions, IEnumerable children) + public static MatchResult Match(IReadOnlyDictionary assertions, IEnumerable children) { var elementsToMatch = children.ToList(); @@ -172,7 +172,7 @@ public static MatchResult Match(IReadOnlyDictionary assertio return new(matches, elementsToMatch.ToList()); } - private static bool nameMatches(string name, ITypedElement instanceElement) + private static bool nameMatches(string name, IScopedNode instanceElement) { var definedName = name; @@ -197,7 +197,7 @@ private static bool nameMatches(string name, ITypedElement instanceElement) /// The list of children that matched an element in the definition of the type. /// The list of children that could not be matched against the defined list of children in the definition /// of the type. - internal record MatchResult(List? Matches, List? UnmatchedInstanceElements); + internal record MatchResult(List? Matches, List? UnmatchedInstanceElements); /// /// This is a pair that corresponds to a set of elements that needs to be validated against an assertion. @@ -207,5 +207,5 @@ internal record MatchResult(List? Matches, List? Unmatched /// Set of elements belong to this child /// Usually, this is the set of elements with the same name and the group of assertions that represents /// the validation rule for that element generated from the StructureDefinition. - internal record Match(string ChildName, IAssertion Assertion, List? InstanceElements = null); + internal record Match(string ChildName, IAssertion Assertion, List? InstanceElements = null); } \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs b/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs index 4524a85c..5af1a4be 100644 --- a/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs @@ -35,7 +35,7 @@ public DatatypeSchema(StructureDefinitionInformation structureDefinition, IEnume } /// - public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IEnumerable 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. @@ -44,7 +44,7 @@ public override ResultReport Validate(IEnumerable input, Validati } /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(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. diff --git a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs index c74d4fad..0535bc3b 100644 --- a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs @@ -70,16 +70,16 @@ private static IReadOnlyCollection extractShortcutMembers(IEnumerabl => members.OfType().ToList(); - /// + /// public virtual ResultReport Validate( - IEnumerable input, + IEnumerable input, ValidationContext vc, ValidationState state) { // If there is no input, just run the cardinality checks, nothing else - essential to keep validation performance high. if (!input.Any()) { - var nothing = Enumerable.Empty(); + var nothing = Enumerable.Empty(); if (!CardinalityValidators.Any()) return ResultReport.SUCCESS; @@ -96,7 +96,7 @@ public virtual ResultReport Validate( } /// - public virtual ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public virtual ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { // If we have shortcut members, run them first if (ShortcutMembers.Any()) diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs index 78ada9f3..461b019f 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs @@ -39,7 +39,7 @@ public ExtensionSchema(StructureDefinitionInformation structureDefinition, IEnum /// /// Gets the canonical of the profile referred to in the url property of the extension. /// - public static Canonical? GetExtensionUri(ITypedElement instance) => + public static Canonical? GetExtensionUri(IScopedNode instance) => instance .Children("url") .Select(ite => ite.Value) @@ -49,7 +49,7 @@ public ExtensionSchema(StructureDefinitionInformation structureDefinition, IEnum .FirstOrDefault(); // this will actually always be max one, but that's validated by a cardinality validator. /// - public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { // Group the instances by their url - this allows a IGroupValidatable schema for the // extension to validate the "extension cardinality". @@ -126,12 +126,12 @@ static ExtensionUrlFollower callback(ValidationContext context) => /// This invokes the actual validation for an Extension schema, without the special magic of /// fetching the url, so this is the "normal" schema validation. /// - protected ResultReport ValidateExtensionSchema(IEnumerable input, + protected ResultReport ValidateExtensionSchema(IEnumerable input, ValidationContext vc, ValidationState state) => base.Validate(input, vc, state); /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); diff --git a/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs index bfff04d2..de4b7e8b 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs @@ -31,7 +31,7 @@ public class FhirEle1Validator : InvariantValidator public override string? HumanDescription => "All FHIR elements must have a @value or children"; /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState _) { // Original R4B expression: "expression": "hasValue() or (children().count() > id.count()) or $this is Parameters", diff --git a/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs index afcaef6c..87155013 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs @@ -30,7 +30,7 @@ public class FhirExt1Validator : InvariantValidator public override string? HumanDescription => "Must have either extensions or value[x], not both"; /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState _) { // Original expression: "expression": "extension.exists() != value.exists()", diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index 47ddb012..abd33d7e 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -97,11 +97,11 @@ public override JToken ToJson() } /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState s) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState s) { try { - var node = input as ScopedNode ?? new ScopedNode(input); + var node = input as ScopedNode ?? new ScopedNode(input.AsTypedElement()); var context = new FhirEvaluationContext(node.ResourceContext) { TerminologyService = new ValidateCodeServiceToTerminologyServiceAdapter(vc.ValidateCodeService) @@ -152,7 +152,7 @@ private CompiledExpression getDefaultCompiledExpression(FhirPathCompiler compile } } - private bool predicate(ITypedElement input, EvaluationContext context, ValidationContext vc) + private bool predicate(IScopedNode input, EvaluationContext context, ValidationContext vc) { var compiler = vc?.FhirPathCompiler ?? DefaultCompiler; var compiledExpression = getDefaultCompiledExpression(compiler); diff --git a/src/Firely.Fhir.Validation/Impl/FhirSchema.cs b/src/Firely.Fhir.Validation/Impl/FhirSchema.cs index 3deff3d1..38ec5692 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirSchema.cs @@ -42,7 +42,7 @@ public FhirSchema(StructureDefinitionInformation structureDefinition, IEnumerabl } /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { state = state .UpdateLocation(sp => sp.InvokeSchema(this)) @@ -51,7 +51,7 @@ public override ResultReport Validate(ITypedElement input, ValidationContext vc, } /// - public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { state = state.UpdateLocation(sp => sp.InvokeSchema(this)); return base.Validate(input, vc, state); diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs index 5cc89663..e934006d 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs @@ -32,7 +32,7 @@ public class FhirTxt1Validator : InvariantValidator public override string? HumanDescription => "The narrative SHALL contain only the basic html formatting elements and attributes described in chapters 7-11 (except section 4 of chapter 9) and 15 of the HTML 4.0 standard, elements (either name or href), images and internally contained style attributes"; /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState _) { // Original expression: "expression": "htmlChecks()" @@ -55,11 +55,11 @@ protected override (bool, ResultReport?) RunInvariant(ITypedElement input, Valid } } default: - return (false, new ResultReport(ValidationResult.Failure, + return (false, new ResultReport(ValidationResult.Failure, new IssueAssertion(Issue.CONTENT_ELEMENT_INVALID_PRIMITIVE_VALUE, $"Narrative should be of type string, but is of type ({input.Value.GetType()})"))); - } + } } /// diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs index 47ccd9aa..4f638a8b 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs @@ -30,7 +30,7 @@ public class FhirTxt2Validator : InvariantValidator public override string? HumanDescription => "The narrative SHALL have some non-whitespace content"; /// - protected override (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState _) + protected override (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState _) { //Check whether the narrative contains non-whitespace content. return (!string.IsNullOrWhiteSpace(input.Value.ToString()), null); diff --git a/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs index 96556195..86ddcda7 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs @@ -13,7 +13,7 @@ namespace Firely.Fhir.Validation /// /// Assertion about the stated instance type of an element. /// - /// The instance type is taken from + /// The instance type is taken from [DataContract] public class FhirTypeLabelValidator : BasicValidator { @@ -39,7 +39,7 @@ public FhirTypeLabelValidator(string label) protected override object Value => Label; /// - public override ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public override ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { var result = input.InstanceType == Label ? ResultReport.SUCCESS : diff --git a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs index d93cf91e..5b69d7bd 100644 --- a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs @@ -43,12 +43,12 @@ public FixedValidator(ITypedElement fixedValue) public FixedValidator(object fixedValue) : this(ElementNode.ForPrimitive(fixedValue)) { } /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { - if (!input.IsExactlyEqualTo(FixedValue, ignoreOrder: true)) + if (!input.IsExactlyEqualTo(FixedValue.AsScopedNode(), ignoreOrder: true)) { return new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, - $"Value '{displayValue(input)}' is not exactly equal to fixed value '{displayValue(FixedValue)}'") + $"Value '{displayValue(input.AsTypedElement())}' is not exactly equal to fixed value '{displayValue(FixedValue)}'") .AsResult(s); } diff --git a/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs b/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs index 39a2fad2..8c268cd7 100644 --- a/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs @@ -52,10 +52,10 @@ public abstract class InvariantValidator : IValidatable /// /// Implements the logic for running the invariant. /// - protected abstract (bool, ResultReport?) RunInvariant(ITypedElement input, ValidationContext vc, ValidationState s); + protected abstract (bool, ResultReport?) RunInvariant(IScopedNode input, ValidationContext vc, ValidationState s); /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState s) { var (success, directAssertion) = RunInvariant(input, vc, s); diff --git a/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs b/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs index 002cb1f3..1235f215 100644 --- a/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs @@ -143,7 +143,7 @@ public JToken ToJson() public static class Pattern { /// - /// Will be replaced by at runtime. + /// Will be replaced by at runtime. /// public const string INSTANCETYPE = "%INSTANCETYPE%"; @@ -154,7 +154,7 @@ public static class Pattern } /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState state) { // Validation does not mean anything more than using this instance as a prototype and // turning the issue assertion into a result by cloning the prototype and setting the diff --git a/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs b/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs index fe2e70ee..5a17a660 100644 --- a/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs @@ -44,7 +44,7 @@ public MaxLengthValidator(int maximumLength) protected override object Value => MaximumLength; /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState s) + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState s) { if (input == null) throw Error.ArgumentNull(nameof(input)); diff --git a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs index 4367ceba..1f949a8d 100644 --- a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs @@ -87,7 +87,7 @@ public MinMaxValueValidator(ITypedElement limit, ValidationMode minMaxType) public MinMaxValueValidator(long limit, ValidationMode minMaxType) : this(ElementNode.ForPrimitive(limit), minMaxType) { } /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { if (!Any.TryConvert(input.Value, out var instanceValue)) { diff --git a/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs b/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs index bf4d79b7..a44da508 100644 --- a/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs @@ -9,6 +9,7 @@ using Hl7.FhirPath; using Hl7.FhirPath.Expressions; using Newtonsoft.Json.Linq; +using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; @@ -47,11 +48,11 @@ public PathSelectorValidator(string path, IAssertion other) /// Note that this validator is only used internally to represent the checks for /// the path-based discriminated cases in a , so this validator /// does not produce standard Issue-based errors. - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { initializeFhirPathCache(vc, state); - var selected = state.Global.FPCompilerCache!.Select(input, Path).ToList(); + var selected = state.Global.FPCompilerCache!.Select(input.AsTypedElement(), Path).ToList(); if (selected.Any()) { @@ -60,17 +61,19 @@ public ResultReport Validate(ITypedElement input, ValidationContext vc, Validati state = state.UpdateInstanceLocation(ip => ip.AddInternalReference(selected.First().Location)); } - return selected switch + var selectedScopedNodes = cast(selected); + + return selectedScopedNodes switch { // 0, 1 or more results are ok for group validatables. Even an empty result is valid for, say, cardinality constraints. - _ when Other is IGroupValidatable igv => igv.Validate(selected, vc, state), + _ when Other is IGroupValidatable igv => igv.Validate(selectedScopedNodes, vc, state), // A non-group validatable cannot be used with 0 results. { Count: 0 } => new ResultReport(ValidationResult.Failure, new TraceAssertion(state.Location.InstanceLocation.ToString(), $"The FhirPath selector {Path} did not return any results.")), // 1 is ok for non group validatables - { Count: 1 } => Other.ValidateMany(selected, vc, state), + { Count: 1 } => Other.ValidateMany(selectedScopedNodes, vc, state), // Otherwise we have too many results for a non-group validatable. _ => new ResultReport(ValidationResult.Failure, @@ -86,6 +89,8 @@ static void initializeFhirPathCache(ValidationContext vc, ValidationState state) state.Global.FPCompilerCache = new FhirPathCompilerCache(compiler); } } + + List cast(List elements) => elements.Select(e => e.AsScopedNode()).ToList(); } /// diff --git a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs index 059f45df..a7772912 100644 --- a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs @@ -46,9 +46,9 @@ public PatternValidator(ITypedElement patternValue) public PatternValidator(object patternPrimitive) : this(ElementNode.ForPrimitive(patternPrimitive)) { } /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { - var result = !input.Matches(PatternValue) + var result = !input.Matches(PatternValue.AsScopedNode()) ? new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, $"Value does not match pattern '{PatternValue.ToJson()}") .AsResult(s) : ResultReport.SUCCESS; diff --git a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs index 79a828ee..e8701c28 100644 --- a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs @@ -58,8 +58,8 @@ public ReferencedInstanceValidator(IAssertion schema, /// public bool HasAggregation => AggregationRules?.Any() ?? false; - /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + /// + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { if (vc.ElementSchemaResolver is null) throw new ArgumentException($"Cannot validate because {nameof(ValidationContext)} does not contain an ElementSchemaResolver."); @@ -109,7 +109,7 @@ private record ResolutionResult(ITypedElement? ReferencedResource, AggregationMo /// or externally. In the last case, the is used /// to fetch the resource. /// - private (IReadOnlyCollection, ResolutionResult) fetchReference(ITypedElement input, string reference, ValidationContext vc, ValidationState s) + private (IReadOnlyCollection, ResolutionResult) fetchReference(IScopedNode input, string reference, ValidationContext vc, ValidationState s) { ResolutionResult resolution = new(null, null, null); List evidence = new(); @@ -220,14 +220,14 @@ private ResultReport validateReferencedResource(string reference, ValidationCont // references to external entities will operate within a new instance of a validator (and hence a new tracking context). // In both cases, the outcome is included in the result. if (resolution.ReferenceKind != AggregationMode.Referenced) - return Schema.ValidateOne(resolution.ReferencedResource, vc, state.UpdateInstanceLocation(dp => dp.AddInternalReference(resolution.ReferencedResource.Location))); + return Schema.ValidateOne(resolution.ReferencedResource.AsScopedNode(), vc, state.UpdateInstanceLocation(dp => dp.AddInternalReference(resolution.ReferencedResource.Location))); else { //TODO: We're using state to track the external URL, but this actually would be better //implemented on the ScopedNode instead - add this (and combine with FullUrl?) there. var newState = state.NewInstanceScope(); newState.Instance.ResourceUrl = reference; - return Schema.ValidateOne(new ScopedNode(resolution.ReferencedResource), vc, newState); + return Schema.ValidateOne(resolution.ReferencedResource.AsScopedNode(), vc, newState); } } diff --git a/src/Firely.Fhir.Validation/Impl/RegExValidator.cs b/src/Firely.Fhir.Validation/Impl/RegExValidator.cs index 5e302659..70eb4c48 100644 --- a/src/Firely.Fhir.Validation/Impl/RegExValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/RegExValidator.cs @@ -44,7 +44,7 @@ public RegExValidator(string pattern) protected override object Value => Pattern; /// - public override ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState s) + public override ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { var value = toStringRepresentation(input); var success = _regex.Match(value).Success; @@ -55,7 +55,7 @@ public override ResultReport Validate(ITypedElement input, ValidationContext _, : ResultReport.SUCCESS; } - private static string? toStringRepresentation(ITypedElement vp) + private static string? toStringRepresentation(IScopedNode vp) { return vp == null || vp.Value == null ? null : diff --git a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs index 333815ba..3398810a 100644 --- a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs @@ -40,7 +40,7 @@ public ResourceSchema(StructureDefinitionInformation structureDefinition, IEnume /// /// Gets the canonical of the profile(s) referred to in the Meta.profile property of the resource. /// - internal static Canonical[] GetMetaProfileSchemas(ITypedElement instance, ValidationContext vc) + internal static Canonical[] GetMetaProfileSchemas(IScopedNode instance, ValidationContext vc, ValidationState state) { var profiles = instance .Children("meta") @@ -49,14 +49,14 @@ internal static Canonical[] GetMetaProfileSchemas(ITypedElement instance, Valida .OfType() .Select(s => new Canonical(s)); - return callback(vc).Invoke(instance.Location, profiles.ToArray()); + return callback(vc).Invoke(state.Location.InstanceLocation.ToString(), profiles.ToArray()); static ValidationContext.MetaProfileSelector callback(ValidationContext context) => context.SelectMetaProfiles ?? ((_, m) => m); } /// - public override ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(IEnumerable 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. @@ -65,7 +65,7 @@ public override ResultReport Validate(IEnumerable input, Validati } /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public override ResultReport Validate(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. @@ -85,7 +85,7 @@ public override ResultReport Validate(ITypedElement input, ValidationContext vc, // FHIR has a few occasions where the schema needs to read into the instance to obtain additional schemas to // validate against (Resource.meta.profile, Extension.url). Fetch these from the instance and combine them into // a coherent set to validate against. - var additionalCanonicals = GetMetaProfileSchemas(input, vc); + var additionalCanonicals = GetMetaProfileSchemas(input, vc, state); if (additionalCanonicals.Any() && vc.ElementSchemaResolver is null) throw new ArgumentException($"Cannot validate profiles in meta.profile because {nameof(ValidationContext)} does not contain an ElementSchemaResolver."); @@ -111,7 +111,7 @@ public override ResultReport Validate(ITypedElement input, ValidationContext vc, /// This invokes the actual validation for an resource schema, without the special magic of /// fetching Meta.profile, so this is the "normal" schema validation. /// - protected ResultReport ValidateResourceSchema(ITypedElement input, ValidationContext vc, ValidationState state) + protected ResultReport ValidateResourceSchema(IScopedNode input, ValidationContext vc, ValidationState state) { return state.Global.RunValidations.Start( state, diff --git a/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs b/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs index 52742ccf..d0b85337 100644 --- a/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs @@ -64,7 +64,7 @@ private static ResultReport getResultAssertion(ValidationResult result) => ValidationResult IFixedResult.FixedResult => Result; /// - public override ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => _fixedReport; + public override ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => _fixedReport; /// public ResultReport AsResult() => _fixedReport; diff --git a/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs b/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs index c55c8e95..29136416 100644 --- a/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs @@ -40,8 +40,8 @@ public SchemaReferenceValidator(Canonical schemaUri) SchemaUri = schemaUri; } - /// - public ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + /// + public ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { if (vc.ElementSchemaResolver is null) throw new ArgumentException($"Cannot validate because {nameof(ValidationContext)} does not contain an ElementSchemaResolver."); @@ -54,7 +54,7 @@ public ResultReport Validate(IEnumerable input, ValidationContext } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); /// diff --git a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs index b840e16d..07e9395b 100644 --- a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs @@ -122,10 +122,10 @@ public SliceValidator(bool ordered, bool defaultAtEnd, IAssertion @default, IEnu } /// - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => Validate(new[] { input }, vc, state); - /// - public ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) + /// + public ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state) { var lastMatchingSlice = -1; var defaultInUse = false; @@ -207,7 +207,7 @@ public JToken ToJson() new JProperty("default", def))); } - private record OrderedTypedElement(ITypedElement Node, int Index); + private record OrderedTypedElement(IScopedNode Node, int Index); private class Buckets : Dictionary?> { @@ -225,7 +225,7 @@ public Buckets(IEnumerable slices, IAssertion defaultAssertion) _defaultAssertion = defaultAssertion; } - public void AddToSlice(SliceCase slice, ITypedElement item, int originalIndex) + public void AddToSlice(SliceCase slice, IScopedNode item, int originalIndex) { if (!TryGetValue(slice, out var list)) throw new InvalidOperationException($"Slice should have been initialized with item {slice.Name}."); @@ -234,7 +234,7 @@ public void AddToSlice(SliceCase slice, ITypedElement item, int originalIndex) list.Add(new(item, originalIndex)); } - public void AddToDefault(ITypedElement item, int originalIndex) => _defaultBucket.Add(new(item, originalIndex)); + public void AddToDefault(IScopedNode item, int originalIndex) => _defaultBucket.Add(new(item, originalIndex)); public ResultReport[] Validate(ValidationContext vc, ValidationState state) => this.Select(slice => slice.Key.Assertion.ValidateMany(toListOfTypedElements(slice.Value), vc, forSlice(state, slice.Key.Name, slice.Value))) @@ -245,8 +245,8 @@ private static ValidationState forSlice(ValidationState current, string sliceNam .UpdateLocation(vs => vs.CheckSlice(sliceName)) .UpdateInstanceLocation(vs => vs.AddOriginalIndices(toOrderedList(list))); - private static IEnumerable toListOfTypedElements(IList? list) => - list?.Select(ote => ote.Node) ?? Enumerable.Empty(); + private static IEnumerable toListOfTypedElements(IList? list) => + list?.Select(ote => ote.Node) ?? Enumerable.Empty(); private static IEnumerable toOrderedList(IList? list) => list?.Select(ote => ote.Index) ?? Enumerable.Empty(); diff --git a/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs b/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs index e337e3b2..36aa1701 100644 --- a/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs @@ -43,7 +43,7 @@ public TraceAssertion(string location, string message) } /// - public ResultReport Validate(ITypedElement input, ValidationContext _, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState state) { // Validation does not mean anything more than using this instance as a prototype and // turning the trace assertion into a result by cloning the prototype and setting the diff --git a/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs b/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs index 69295de4..3f5bdc0c 100644 --- a/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs +++ b/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs @@ -21,19 +21,29 @@ namespace Firely.Fhir.Validation /// public static class AssertionValidators { + /// + /// Validates a set of instance elements against an assertion. + /// + public static ResultReport Validate(this IAssertion assertion, IEnumerable input, ValidationContext vc) + => assertion.ValidateMany(input, vc, new ValidationState()); + /// /// Validates a set of instance elements against an assertion. /// public static ResultReport Validate(this IAssertion assertion, IEnumerable input, ValidationContext vc) - => assertion.ValidateMany(input.Select(i => i.asScopedNode()), vc, new ValidationState()); + => assertion.ValidateMany(input.Select(i => i.AsScopedNode()), vc, new ValidationState()); /// /// Validates a single instance element against an assertion. /// - public static ResultReport Validate(this IAssertion assertion, ITypedElement input, ValidationContext vc) - => assertion.ValidateOne(input.asScopedNode(), vc, new ValidationState()); + public static ResultReport Validate(this IAssertion assertion, IScopedNode input, ValidationContext vc) + => assertion.ValidateOne(input, vc, new ValidationState()); - private static ITypedElement asScopedNode(this ITypedElement node) => node is ScopedNode ? node : new ScopedNode(node); + /// + /// Validates a single instance element against an assertion. + /// + public static ResultReport Validate(this IAssertion assertion, ITypedElement input, ValidationContext vc) + => assertion.ValidateOne(input.AsScopedNode(), vc, new ValidationState()); /// /// Validates a group of instance elements using an assertion. @@ -41,7 +51,7 @@ public static ResultReport Validate(this IAssertion assertion, ITypedElement inp /// If the assertion is an , this will simply invoke the /// corresponding method on the validator. If not, it will call the validation on the assertion for /// each of the instances in the group and combine the result. - internal static ResultReport ValidateMany(this IAssertion assertion, IEnumerable input, ValidationContext vc, ValidationState state) + internal static ResultReport ValidateMany(this IAssertion assertion, IEnumerable input, ValidationContext vc, ValidationState state) { return assertion switch { @@ -53,7 +63,7 @@ internal static ResultReport ValidateMany(this IAssertion assertion, IEnumerable // Turn the validation of a group of elements using a into // a sequence of calls of each element in the group against a , and // then combines the results of each of these calls. - static ResultReport repeat(IValidatable assertion, IEnumerable input, ValidationContext vc, ValidationState state) + static ResultReport repeat(IValidatable assertion, IEnumerable input, ValidationContext vc, ValidationState state) { return input.ToList() switch { @@ -71,7 +81,7 @@ static ResultReport repeat(IValidatable assertion, IEnumerable in /// If the assertion is an , this will simply invoke the /// corresponding method on the validator. If not, it will wrap the single instance as a group /// and call validation for the . - internal static ResultReport ValidateOne(this IAssertion assertion, ITypedElement input, ValidationContext vc, ValidationState state) => + internal static ResultReport ValidateOne(this IAssertion assertion, IScopedNode input, ValidationContext vc, ValidationState state) => assertion switch { IValidatable validatable => validatable.Validate(input, vc, state), diff --git a/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs b/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs index 2d7afacd..6632d55d 100644 --- a/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs +++ b/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs @@ -18,6 +18,6 @@ public interface IGroupValidatable : IAssertion, IValidatable /// /// Validates a set of instances, given a location representative for the group. /// - ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state); + ResultReport Validate(IEnumerable input, ValidationContext vc, ValidationState state); } } diff --git a/src/Firely.Fhir.Validation/Schema/IValidatable.cs b/src/Firely.Fhir.Validation/Schema/IValidatable.cs index 1c7f2363..c1742e81 100644 --- a/src/Firely.Fhir.Validation/Schema/IValidatable.cs +++ b/src/Firely.Fhir.Validation/Schema/IValidatable.cs @@ -9,13 +9,13 @@ namespace Firely.Fhir.Validation { /// - /// Implemented by assertions that work on a single ITypedElement. + /// Implemented by assertions that work on a single . /// public interface IValidatable : IAssertion { /// /// Validates a single instance. /// - ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state); + ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state); } } diff --git a/src/Firely.Fhir.Validation/Schema/ValueElementNode.cs b/src/Firely.Fhir.Validation/Schema/ValueElementNode.cs index 19307f63..1e056d67 100644 --- a/src/Firely.Fhir.Validation/Schema/ValueElementNode.cs +++ b/src/Firely.Fhir.Validation/Schema/ValueElementNode.cs @@ -6,24 +6,28 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Language; -using Hl7.Fhir.Serialization; using System.Collections.Generic; using System.Linq; namespace Firely.Fhir.Validation { - internal class ValueElementNode : BaseTypedElement + internal class ValueElementNode : IScopedNode { - public ValueElementNode(ITypedElement wrapped) : base(wrapped) + private readonly IScopedNode _wrapped; + + public ValueElementNode(IScopedNode wrapped) { + _wrapped = wrapped; } - public override string Name => "value"; + public IScopedNode? Parent => _wrapped.Parent; + + public string Name => "value"; - public override string InstanceType => TypeSpecifier.ForNativeType(Wrapped.Value.GetType()).FullName; + public string InstanceType => TypeSpecifier.ForNativeType(_wrapped.Value.GetType()).FullName; - public override string Location => $"{Wrapped.Location}.value"; + public object Value => _wrapped.Value; - public override IEnumerable Children(string? name = null) => Enumerable.Empty(); + public IEnumerable Children(string? name = null) => Enumerable.Empty(); } } diff --git a/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs b/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs index 51d43b1f..7371f645 100644 --- a/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs +++ b/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs @@ -24,7 +24,7 @@ public class ValidationSettings { /// /// The used to map instance types encountered in - /// to canonicals when a profile for these types needs to be retrieved. + /// to canonicals when a profile for these types needs to be retrieved. /// public StructureDefinitionSummaryProvider.TypeNameMapper? ResourceMapping { get; set; } diff --git a/src/Hl7.Fhir.Validation.Shared/Validator.cs b/src/Hl7.Fhir.Validation.Shared/Validator.cs index 98acce05..556e1ac9 100644 --- a/src/Hl7.Fhir.Validation.Shared/Validator.cs +++ b/src/Hl7.Fhir.Validation.Shared/Validator.cs @@ -120,7 +120,7 @@ public Validator() : this(ValidationSettings.CreateDefault()) } /// - /// Validate an instance, use the instance's to pick the relevant profile to validate against. + /// Validate an instance, use the instance's to pick the relevant profile to validate against. /// public OperationOutcome Validate(ITypedElement instance) { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs index 657aa633..a11cbec7 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs @@ -340,7 +340,7 @@ internal class SelfDefinedValidator : IValidatable { public JToken ToJson() => new JProperty("selfdefined-validator"); - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) => ResultReport.SUCCESS; } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases index b41082c2..a03c3449 160000 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTestCases @@ -1 +1 @@ -Subproject commit b41082c2f7b6f1df62d7eb7c2d402dfb418c5d97 +Subproject commit a03c34490755359f6eec5ddba4addc367c73e52b diff --git a/test/Firely.Fhir.Validation.Tests/Impl/AllValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/AllValidatorTests.cs index 69cd592e..c69efa55 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/AllValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/AllValidatorTests.cs @@ -28,7 +28,7 @@ public JToken ToJson() throw new System.NotImplementedException(); } - public ResultReport Validate(ITypedElement input, ValidationContext vc, ValidationState state) + public ResultReport Validate(IScopedNode input, ValidationContext vc, ValidationState state) { return new ResultReport(_result, new TraceAssertion(state.Location.InstanceLocation.ToString(), _message)); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs index b93543e8..bfe87193 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs @@ -1,4 +1,5 @@ using FluentAssertions; +using Hl7.Fhir.ElementModel; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Linq; @@ -22,18 +23,18 @@ public void FollowMetaProfileTest() { profile = new[] { "profile1", "profile2", "profile3", "profile4" } } - }.ToTypedElement(); + }.ToTypedElement().AsScopedNode(); - var result = ResourceSchema.GetMetaProfileSchemas(instance, context); + var result = ResourceSchema.GetMetaProfileSchemas(instance, context, new ValidationState()); result.Should().BeEquivalentTo(new Canonical[] { "userprofile2", "profile3", "profile4", "userprofile5" }); context.SelectMetaProfiles = declineAll; - result = ResourceSchema.GetMetaProfileSchemas(instance, context); + result = ResourceSchema.GetMetaProfileSchemas(instance, context, new ValidationState()); result.Should().BeEmpty(); // remove the callback: context.SelectMetaProfiles = null; - result = ResourceSchema.GetMetaProfileSchemas(instance, context); + result = ResourceSchema.GetMetaProfileSchemas(instance, context, new ValidationState()); result.Should().BeEquivalentTo(new Canonical[] { "profile1", "profile2", "profile3", "profile4" }); static Canonical[] callback(string location, Canonical[] orignalMetaProfiles) diff --git a/test/Firely.Fhir.Validation.Tests/Support/IValidatableExtensions.cs b/test/Firely.Fhir.Validation.Tests/Support/IValidatableExtensions.cs new file mode 100644 index 00000000..c9a6b241 --- /dev/null +++ b/test/Firely.Fhir.Validation.Tests/Support/IValidatableExtensions.cs @@ -0,0 +1,21 @@ +using Hl7.Fhir.ElementModel; +using System.Collections.Generic; +using System.Linq; + +namespace Firely.Fhir.Validation.Tests +{ + /// + /// Helper class to make it easier to test and implementations with + /// the interface instead of . + /// + internal static class IValidatableExtensions + { + /// + public static ResultReport Validate(this IValidatable validatable, ITypedElement input, ValidationContext vc, ValidationState state) + => validatable.Validate(input.AsScopedNode(), vc, state); + + /// + public static ResultReport Validate(this IGroupValidatable validatable, IEnumerable input, ValidationContext vc, ValidationState state) + => validatable.Validate(input.Select(i => i.AsScopedNode()), vc, state); + } +} \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Tests/Support/TypedElementOnDictionary.cs b/test/Firely.Fhir.Validation.Tests/Support/TypedElementOnDictionary.cs index 725939a4..12f22e37 100644 --- a/test/Firely.Fhir.Validation.Tests/Support/TypedElementOnDictionary.cs +++ b/test/Firely.Fhir.Validation.Tests/Support/TypedElementOnDictionary.cs @@ -54,9 +54,13 @@ public TypedElementOnDictionary(string rootName, IDictionary wra public string Name => _name; +#pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). public string? InstanceType => TryGetValue("_type", out var type) ? type as string : ResourceType; +#pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). +#pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). public object? Value => TryGetValue("_value", out var value) ? value : null; +#pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). public string Location => _location; @@ -92,7 +96,7 @@ private record ConstantElement(string Name, string InstanceType, object Value, s { public IElementDefinitionSummary? Definition => null; - public IEnumerable Children(string name) => + public IEnumerable Children(string? name) => Enumerable.Empty(); } From 9665480126b9f0888687ec12400d3adc468657f2 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 14 Nov 2023 14:43:39 +0100 Subject: [PATCH 02/27] Convert input to a ITypedElement. --- src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index abd33d7e..98e6b9d1 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -157,7 +157,7 @@ private bool predicate(IScopedNode input, EvaluationContext context, ValidationC var compiler = vc?.FhirPathCompiler ?? DefaultCompiler; var compiledExpression = getDefaultCompiledExpression(compiler); - return compiledExpression.IsTrue(input, context); + return compiledExpression.IsTrue(input.AsTypedElement(), context); } /// From 5a917f3303c3585c63e78ce681d530f727738d6d Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 14 Nov 2023 16:05:51 +0100 Subject: [PATCH 03/27] Use unrelease version of SDK: 5.4.1-20231114.4 --- firely-validator-api-tests.props | 2 +- firely-validator-api.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firely-validator-api-tests.props b/firely-validator-api-tests.props index 8c5099e6..b69bc8f8 100644 --- a/firely-validator-api-tests.props +++ b/firely-validator-api-tests.props @@ -10,7 +10,7 @@ - 5.3.0 + 5.4.1-20231114.4 5.1.0 diff --git a/firely-validator-api.props b/firely-validator-api.props index e7a5a2d4..85439109 100644 --- a/firely-validator-api.props +++ b/firely-validator-api.props @@ -19,7 +19,7 @@ - 5.3.0 + 5.4.1-20231114.4 From a55dacc2c9e894c298cd4c921b3c34e48ec28523 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Tue, 21 Nov 2023 11:59:55 +0100 Subject: [PATCH 04/27] Fixes to make snapgen work under R5. --- .../SchemaBuilders/TypeReferenceBuilder.cs | 2 +- .../Impl/ReferencedInstanceValidator.cs | 2 +- .../BasicSchemaBuilderTests.cs | 15 ++++++--------- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs index 8a78723d..7bece9c5 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs @@ -126,7 +126,7 @@ internal IAssertion ConvertTypeReference(CommonTypeRefComponent typeRef) var validateReferenceAssertion = buildvalidateInstance(typeRef.AggregationElement, typeRef.Versioning, targetProfileAssertions); return new AllValidator(profileAssertions, validateReferenceAssertion); } - else if (code != "Reference" && code != "canonical" && typeRef.TargetProfile.Any()) + else if (!(code is "Reference" or "canonical" or "CodeableReference") && 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}."); diff --git a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs index e8701c28..513a986c 100644 --- a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs @@ -250,6 +250,6 @@ public JToken ToJson() /// /// Whether this validator recognizes the given type as a reference type. /// - public static bool IsSupportedReferenceType(string typeCode) => typeCode == "Reference"; // || typeCode == "canonical" + public static bool IsSupportedReferenceType(string typeCode) => typeCode is "Reference" or "CodeableReference"; } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs index a11cbec7..ad622af3 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs @@ -34,23 +34,20 @@ public class BasicSchemaBuilderTests : IClassFixture public BasicSchemaBuilderTests(SchemaBuilderFixture fixture, ITestOutputHelper oh) => (_output, _fixture) = (oh, fixture); - [Fact(Skip = "Only enable this when you want to rewrite the snaps to update them to a new correct situation")] - //[Fact] +#if !R5 + // [Fact(Skip = "Only enable this when you want to rewrite the snaps to update them to a new correct situation")] + [Fact] public void OverwriteSchemaSnaps() { compareToSchemaSnaps(true); } - - [Fact -#if R5 - (Skip = "TODO: Make this work for R5 as well.") -#endif - ] + [Fact] public void CompareToCorrectSchemaSnaps() { - compareToSchemaSnaps(true); + compareToSchemaSnaps(false); } +#endif private void compareToSchemaSnaps(bool overwrite) { From 8d694af6f8a7c2c58252e2a4abb6f648b9f76a68 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Tue, 21 Nov 2023 12:49:44 +0100 Subject: [PATCH 05/27] Fixed error in test profile, which had an illegal elementref in R5. --- .../SchemaSnaps/BackboneElement.json | 2 +- .../SchemaSnaps/Composition.json | 254 +++++++--------- .../SchemaSnaps/Extension.json | 69 ++++- .../SchemaSnaps/Patient.json | 65 +++- .../SchemaSnaps/PatternSlice.json | 22 +- .../SchemaSnaps/ProfiledFlag.json | 21 +- .../SchemaSnaps/ProfiledObservation.json | 287 +++++++++++++++++- .../SchemaSnaps/ProfiledQuestionnaire.json | 180 +++++++---- .../SchemaSnaps/Questionnaire.json | 186 ++++++++++-- .../SchemaSnaps/SecondaryTypeRefSlice.json | 173 +++++------ .../SchemaSnaps/SimpleQuantity.json | 4 +- .../SchemaSnaps/Xhtml.json | 2 +- .../SchemaSnaps/boolean.json | 2 +- .../BasicSchemaBuilderTests.cs | 6 +- .../TestProfileArtifactSource.cs | 10 +- 15 files changed, 890 insertions(+), 393 deletions(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/BackboneElement.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/BackboneElement.json index 7c66a605..e6e49f87 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/BackboneElement.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/BackboneElement.json @@ -4,7 +4,7 @@ "schema-subtype": "datatype", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/BackboneElement", - "base": "http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "BackboneElement", "abstract": true, "derivation": "specialization" diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json index f75e7770..4d5a1ab0 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Composition.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/Composition", - "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Composition", "abstract": false, "derivation": "specialization" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Composition.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -100,18 +100,37 @@ "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Extension" }, + "url": { + "id": "#Composition.url", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/uri" + }, "identifier": { "id": "#Composition.identifier", "FastInvariant-ele1": {}, - "cardinality": "0..1", + "fhirPath-ident-1": { + "key": "ident-1", + "expression": "value.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Identifier with no value has limited utility. If communicating that an identifier value has been suppressed or missing, the value element SHOULD be present with an extension indicating the missing semantic - e.g. data-absent-reason" + }, + "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" }, + "version": { + "id": "#Composition.version", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + }, "status": { "id": "#Composition.status", "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/composition-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/composition-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -133,7 +152,7 @@ "binding": { "abstractAllowed": true, "strength": "example", - "valueSet": "http://hl7.org/fhir/ValueSet/document-classcodes" + "valueSet": "http://hl7.org/fhir/ValueSet/referenced-item-category" }, "FastInvariant-ele1": {}, "cardinality": "0..*", @@ -144,12 +163,19 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, - "cardinality": "0..1", + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, + "cardinality": "0..*", "allOf": [ { "ref": "http://hl7.org/fhir/StructureDefinition/Reference" @@ -168,11 +194,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -193,16 +226,29 @@ "cardinality": "1..1", "ref": "http://hl7.org/fhir/StructureDefinition/dateTime" }, + "useContext": { + "id": "#Composition.useContext", + "FastInvariant-ele1": {}, + "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/UsageContext" + }, "author": { "id": "#Composition.author", "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "1..*", "allOf": [ { @@ -284,22 +330,23 @@ } ] }, + "name": { + "id": "#Composition.name", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + }, "title": { "id": "#Composition.title", "FastInvariant-ele1": {}, "cardinality": "1..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, - "confidentiality": { - "id": "#Composition.confidentiality", - "binding": { - "abstractAllowed": true, - "strength": "required", - "valueSet": "http://terminology.hl7.org/ValueSet/v3-ConfidentialityClassification|2014-03-26" - }, + "note": { + "id": "#Composition.note", "FastInvariant-ele1": {}, - "cardinality": "0..1", - "ref": "http://hl7.org/fhir/StructureDefinition/code" + "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/Annotation" }, "attester": { "id": "#Composition.attester", @@ -329,12 +376,12 @@ "id": "#Composition.attester.mode", "binding": { "abstractAllowed": true, - "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/composition-attestation-mode|4.0.1" + "strength": "preferred", + "valueSet": "http://hl7.org/fhir/ValueSet/composition-attestation-mode" }, "FastInvariant-ele1": {}, "cardinality": "1..1", - "ref": "http://hl7.org/fhir/StructureDefinition/code" + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" }, "time": { "id": "#Composition.attester.time", @@ -347,11 +394,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -431,11 +485,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -454,86 +515,7 @@ "id": "#Composition.relatesTo", "FastInvariant-ele1": {}, "cardinality": "0..*", - "children": { - "id": { - "id": "#Composition.relatesTo.id", - "cardinality": "0..1", - "ref": "http://hl7.org/fhirpath/System.String" - }, - "extension": { - "id": "#Composition.relatesTo.extension", - "FastInvariant-ele1": {}, - "FastInvariant-ext1": {}, - "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/Extension" - }, - "modifierExtension": { - "id": "#Composition.relatesTo.modifierExtension", - "FastInvariant-ele1": {}, - "FastInvariant-ext1": {}, - "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/Extension" - }, - "code": { - "id": "#Composition.relatesTo.code", - "binding": { - "abstractAllowed": true, - "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/document-relationship-type|4.0.1" - }, - "FastInvariant-ele1": {}, - "cardinality": "1..1", - "ref": "http://hl7.org/fhir/StructureDefinition/code" - }, - "target[x]": { - "id": "#Composition.relatesTo.target[x]", - "FastInvariant-ele1": {}, - "cardinality": "1..1", - "slice": { - "ordered": false, - "defaultAtEnd": false, - "case": [ - { - "name": "Identifier", - "condition": { - "fhir-type-label": "Identifier" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" - } - }, - { - "name": "Reference", - "condition": { - "fhir-type-label": "Reference" - }, - "assertion": { - "allOf": [ - { - "ref": "http://hl7.org/fhir/StructureDefinition/Reference" - }, - { - "validate": { - "schema": { - "ref": "http://hl7.org/fhir/StructureDefinition/Composition" - } - } - } - ] - } - } - ], - "default": { - "issue": { - "issueNumber": 1011, - "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Identifier','Reference')", - "type": "Invalid" - } - } - } - } - } + "ref": "http://hl7.org/fhir/StructureDefinition/RelatedArtifact" }, "event": { "id": "#Composition.event", @@ -559,26 +541,15 @@ "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Extension" }, - "code": { - "id": "#Composition.event.code", - "binding": { - "abstractAllowed": true, - "strength": "example", - "valueSet": "http://terminology.hl7.org/ValueSet/v3-ActCode" - }, - "FastInvariant-ele1": {}, - "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" - }, "period": { "id": "#Composition.event.period", "FastInvariant-ele1": {}, "fhirPath-per-1": { "key": "per-1", - "expression": "start.hasValue().not() or end.hasValue().not() or (start <= end)", + "expression": "start.hasValue().not() or end.hasValue().not() or (start.lowBoundary() <= end.highBoundary())", "severity": "error", "bestPractice": false, - "humanDescription": "If present, start SHALL have a lower value than end" + "humanDescription": "If present, start SHALL have a lower or equal value than end" }, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/Period" @@ -586,17 +557,10 @@ "detail": { "id": "#Composition.event.detail", "FastInvariant-ele1": {}, - "fhirPath-ref-1": { - "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", - "severity": "error", - "bestPractice": false, - "humanDescription": "SHALL have a contained resource if a local reference is provided" - }, "cardinality": "0..*", "allOf": [ { - "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableReference" }, { "validate": { @@ -675,11 +639,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..*", "allOf": [ { @@ -766,11 +737,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -791,17 +769,6 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/Narrative" }, - "mode": { - "id": "#Composition.section.mode", - "binding": { - "abstractAllowed": true, - "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/list-mode|4.0.1" - }, - "FastInvariant-ele1": {}, - "cardinality": "0..1", - "ref": "http://hl7.org/fhir/StructureDefinition/code" - }, "orderedBy": { "id": "#Composition.section.orderedBy", "binding": { @@ -818,11 +785,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..*", "allOf": [ { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Extension.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Extension.json index 419ed03a..88afb049 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Extension.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Extension.json @@ -4,7 +4,7 @@ "schema-subtype": "extension", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/Extension", - "base": "http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/DataType,http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Extension", "abstract": false, "derivation": "specialization" @@ -130,6 +130,15 @@ "ref": "http://hl7.org/fhir/StructureDefinition/integer" } }, + { + "name": "integer64", + "condition": { + "fhir-type-label": "integer64" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/integer64" + } + }, { "name": "markdown", "condition": { @@ -256,6 +265,26 @@ "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" } }, + { + "name": "CodeableReference", + "condition": { + "fhir-type-label": "CodeableReference" + }, + "assertion": { + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableReference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/Resource" + } + } + } + ] + } + }, { "name": "Coding", "condition": { @@ -364,6 +393,15 @@ "ref": "http://hl7.org/fhir/StructureDefinition/Ratio" } }, + { + "name": "RatioRange", + "condition": { + "fhir-type-label": "RatioRange" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/RatioRange" + } + }, { "name": "Reference", "condition": { @@ -420,15 +458,6 @@ "ref": "http://hl7.org/fhir/StructureDefinition/ContactDetail" } }, - { - "name": "Contributor", - "condition": { - "fhir-type-label": "Contributor" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Contributor" - } - }, { "name": "DataRequirement", "condition": { @@ -483,6 +512,24 @@ "ref": "http://hl7.org/fhir/StructureDefinition/UsageContext" } }, + { + "name": "Availability", + "condition": { + "fhir-type-label": "Availability" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Availability" + } + }, + { + "name": "ExtendedContactDetail", + "condition": { + "fhir-type-label": "ExtendedContactDetail" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/ExtendedContactDetail" + } + }, { "name": "Dosage", "condition": { @@ -506,7 +553,7 @@ "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('base64Binary','boolean','canonical','code','date','dateTime','decimal','id','instant','integer','markdown','oid','positiveInt','string','time','unsignedInt','uri','url','uuid','Address','Age','Annotation','Attachment','CodeableConcept','Coding','ContactPoint','Count','Distance','Duration','HumanName','Identifier','Money','Period','Quantity','Range','Ratio','Reference','SampledData','Signature','Timing','ContactDetail','Contributor','DataRequirement','Expression','ParameterDefinition','RelatedArtifact','TriggerDefinition','UsageContext','Dosage','Meta')", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('base64Binary','boolean','canonical','code','date','dateTime','decimal','id','instant','integer','integer64','markdown','oid','positiveInt','string','time','unsignedInt','uri','url','uuid','Address','Age','Annotation','Attachment','CodeableConcept','CodeableReference','Coding','ContactPoint','Count','Distance','Duration','HumanName','Identifier','Money','Period','Quantity','Range','Ratio','RatioRange','Reference','SampledData','Signature','Timing','ContactDetail','DataRequirement','Expression','ParameterDefinition','RelatedArtifact','TriggerDefinition','UsageContext','Availability','ExtendedContactDetail','Dosage','Meta')", "type": "Invalid" } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json index d5277427..6d5cb35e 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Patient.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/Patient", - "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Patient", "abstract": false, "derivation": "specialization" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Patient.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -103,6 +103,13 @@ "identifier": { "id": "#Patient.identifier", "FastInvariant-ele1": {}, + "fhirPath-ident-1": { + "key": "ident-1", + "expression": "value.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Identifier with no value has limited utility. If communicating that an identifier value has been suppressed or missing, the value element SHOULD be present with an extension indicating the missing semantic - e.g. data-absent-reason" + }, "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" }, @@ -136,7 +143,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -324,7 +331,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -335,11 +342,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -359,10 +373,10 @@ "FastInvariant-ele1": {}, "fhirPath-per-1": { "key": "per-1", - "expression": "start.hasValue().not() or end.hasValue().not() or (start <= end)", + "expression": "start.hasValue().not() or end.hasValue().not() or (start.lowBoundary() <= end.highBoundary())", "severity": "error", "bestPractice": false, - "humanDescription": "If present, start SHALL have a lower value than end" + "humanDescription": "If present, start SHALL have a lower or equal value than end" }, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/Period" @@ -397,8 +411,8 @@ "id": "#Patient.communication.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -417,11 +431,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..*", "allOf": [ { @@ -481,11 +502,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "0..1", "allOf": [ { @@ -529,11 +557,18 @@ "FastInvariant-ele1": {}, "fhirPath-ref-1": { "key": "ref-1", - "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", + "expression": "reference.exists() implies (reference.startsWith('#').not() or (reference.substring(1).trace('url') in %rootResource.contained.id.trace('ids')) or (reference='#' and %rootResource!=%resource))", "severity": "error", "bestPractice": false, "humanDescription": "SHALL have a contained resource if a local reference is provided" }, + "fhirPath-ref-2": { + "key": "ref-2", + "expression": "reference.exists() or identifier.exists() or display.exists() or extension.exists()", + "severity": "error", + "bestPractice": false, + "humanDescription": "At least one of reference, identifier and display SHALL be present (unless an extension is provided)." + }, "cardinality": "1..1", "allOf": [ { @@ -584,7 +619,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/link-type|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/link-type|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json index ba64e65b..cdada6a9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/PatternSlice.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/PatternSliceTestcase", - "base": "http://hl7.org/fhir/StructureDefinition/Patient,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Patient,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Patient", "abstract": false, "derivation": "constraint" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Patient.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -141,7 +141,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/identifier-use|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/identifier-use|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -243,7 +243,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/identifier-use|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/identifier-use|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -338,7 +338,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -512,7 +512,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/administrative-gender|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -571,8 +571,8 @@ "id": "#Patient.communication.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -737,7 +737,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/link-type|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/link-type|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledFlag.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledFlag.json index 787dc377..439b7313 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledFlag.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledFlag.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/ProfiledFlag", - "base": "http://hl7.org/fhir/StructureDefinition/Flag,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Flag,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Flag", "abstract": false, "derivation": "constraint" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Flag.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -111,7 +111,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/flag-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/flag-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -267,6 +267,15 @@ "ref": "http://hl7.org/fhir/StructureDefinition/Patient" } }, + { + "name": "forRelatedPerson", + "condition": { + "fhir-type-label": "RelatedPerson" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/RelatedPerson" + } + }, { "name": "forPractitioner", "condition": { @@ -290,7 +299,7 @@ "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json index d6704114..ff40477f 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledObservation.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/ProfiledObservation", - "base": "http://hl7.org/fhir/StructureDefinition/Observation,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Observation,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Observation", "abstract": false, "derivation": "constraint" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -58,7 +58,14 @@ "expression": "value.empty() or component.code.where(coding.intersect(%resource.code.coding).exists()).empty()", "severity": "error", "bestPractice": false, - "humanDescription": "If Observation.code is the same as an Observation.component.code then the value element associated with the code SHALL NOT be present" + "humanDescription": "If Observation.component.code is the same as Observation.code, then Observation.value SHALL NOT be present (the Observation.component.value[x] holds the value)." + }, + "fhirPath-obs-8": { + "key": "obs-8", + "expression": "bodySite.exists() implies bodyStructure.empty()", + "severity": "error", + "bestPractice": false, + "humanDescription": "bodyStructure SHALL only be present if Observation.bodySite is not present" }, "children": { "id": { @@ -82,8 +89,8 @@ "id": "#Observation.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -120,6 +127,54 @@ "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" }, + "instantiates[x]": { + "id": "#Observation.instantiates[x]", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "slice": { + "ordered": false, + "defaultAtEnd": false, + "case": [ + { + "name": "canonical", + "condition": { + "fhir-type-label": "canonical" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/canonical" + } + }, + { + "name": "Reference", + "condition": { + "fhir-type-label": "Reference" + }, + "assertion": { + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/ObservationDefinition" + } + } + } + ] + } + } + ], + "default": { + "issue": { + "issueNumber": 1011, + "severity": "Error", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('canonical','Reference')", + "type": "Invalid" + } + } + } + }, "basedOn": { "id": "#Observation.basedOn", "FastInvariant-ele1": {}, @@ -204,6 +259,66 @@ } ] }, + "triggeredBy": { + "id": "#Observation.triggeredBy", + "FastInvariant-ele1": {}, + "cardinality": "0..*", + "children": { + "id": { + "id": "#Observation.triggeredBy.id", + "cardinality": "0..1", + "ref": "http://hl7.org/fhirpath/System.String" + }, + "extension": { + "id": "#Observation.triggeredBy.extension", + "FastInvariant-ele1": {}, + "FastInvariant-ext1": {}, + "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/Extension" + }, + "modifierExtension": { + "id": "#Observation.triggeredBy.modifierExtension", + "FastInvariant-ele1": {}, + "FastInvariant-ext1": {}, + "cardinality": "0..*", + "ref": "http://hl7.org/fhir/StructureDefinition/Extension" + }, + "observation": { + "id": "#Observation.triggeredBy.observation", + "FastInvariant-ele1": {}, + "cardinality": "1..1", + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/Observation" + } + } + } + ] + }, + "type": { + "id": "#Observation.triggeredBy.type", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/observation-triggeredbytype|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "1..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, + "reason": { + "id": "#Observation.triggeredBy.reason", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + } + } + }, "partOf": { "id": "#Observation.partOf", "FastInvariant-ele1": {}, @@ -272,13 +387,22 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/ImagingStudy" } + }, + { + "name": "forGenomicStudy", + "condition": { + "fhir-type-label": "GenomicStudy" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/GenomicStudy" + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/MedicationAdministration, http://hl7.org/fhir/StructureDefinition/MedicationDispense, http://hl7.org/fhir/StructureDefinition/MedicationStatement, http://hl7.org/fhir/StructureDefinition/Procedure, http://hl7.org/fhir/StructureDefinition/Immunization, http://hl7.org/fhir/StructureDefinition/ImagingStudy). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/MedicationAdministration, http://hl7.org/fhir/StructureDefinition/MedicationDispense, http://hl7.org/fhir/StructureDefinition/MedicationStatement, http://hl7.org/fhir/StructureDefinition/Procedure, http://hl7.org/fhir/StructureDefinition/Immunization, http://hl7.org/fhir/StructureDefinition/ImagingStudy, http://hl7.org/fhir/StructureDefinition/GenomicStudy). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } @@ -293,7 +417,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/observation-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/observation-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -682,13 +806,42 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/Period" } + }, + { + "name": "Attachment", + "condition": { + "fhir-type-label": "Attachment" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Attachment" + } + }, + { + "name": "Reference", + "condition": { + "fhir-type-label": "Reference" + }, + "assertion": { + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/MolecularSequence" + } + } + } + ] + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Quantity','CodeableConcept','string','boolean','integer','Range','Ratio','SampledData','time','dateTime','Period')", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Quantity','CodeableConcept','string','boolean','integer','Range','Ratio','SampledData','time','dateTime','Period','Attachment','Reference')", "type": "Invalid" } } @@ -733,6 +886,23 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" }, + "bodyStructure": { + "id": "#Observation.bodyStructure", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/BodyStructure" + } + } + } + ] + }, "method": { "id": "#Observation.method", "binding": { @@ -747,6 +917,13 @@ "specimen": { "id": "#Observation.specimen", "FastInvariant-ele1": {}, + "fhirPath-obs-9": { + "key": "obs-9", + "expression": "(reference.resolve().exists() and reference.resolve() is Group) implies reference.resolve().member.entity.resolve().all($this is Specimen)", + "severity": "error", + "bestPractice": false, + "humanDescription": "If Observation.specimen is a reference to Group, the group can only have specimens" + }, "cardinality": "0..1", "allOf": [ { @@ -755,7 +932,38 @@ { "validate": { "schema": { - "ref": "http://hl7.org/fhir/StructureDefinition/Specimen" + "slice": { + "ordered": false, + "defaultAtEnd": false, + "case": [ + { + "name": "forSpecimen", + "condition": { + "fhir-type-label": "Specimen" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Specimen" + } + }, + { + "name": "forGroup", + "condition": { + "fhir-type-label": "Group" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Group" + } + } + ], + "default": { + "issue": { + "issueNumber": 1011, + "severity": "Error", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Specimen, http://hl7.org/fhir/StructureDefinition/Group). None of these are profiles on type %INSTANCETYPE% of the resource.", + "type": "Invalid" + } + } + } } } } @@ -852,6 +1060,17 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/SimpleQuantity" }, + "normalValue": { + "id": "#Observation.referenceRange.normalValue", + "binding": { + "abstractAllowed": true, + "strength": "extensible", + "valueSet": "http://hl7.org/fhir/ValueSet/observation-referencerange-normalvalue" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" + }, "type": { "id": "#Observation.referenceRange.type", "binding": { @@ -884,7 +1103,7 @@ "id": "#Observation.referenceRange.text", "FastInvariant-ele1": {}, "cardinality": "0..1", - "ref": "http://hl7.org/fhir/StructureDefinition/string" + "ref": "http://hl7.org/fhir/StructureDefinition/markdown" } } }, @@ -979,12 +1198,12 @@ } }, { - "name": "forMedia", + "name": "forImagingSelection", "condition": { - "fhir-type-label": "Media" + "fhir-type-label": "ImagingSelection" }, "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Media" + "ref": "http://hl7.org/fhir/StructureDefinition/ImagingSelection" } }, { @@ -1013,13 +1232,22 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/MolecularSequence" } + }, + { + "name": "forGenomicStudy", + "condition": { + "fhir-type-label": "GenomicStudy" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/GenomicStudy" + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/DocumentReference, http://hl7.org/fhir/StructureDefinition/ImagingStudy, http://hl7.org/fhir/StructureDefinition/Media, http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse, http://hl7.org/fhir/StructureDefinition/Observation, http://hl7.org/fhir/StructureDefinition/MolecularSequence). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/DocumentReference, http://hl7.org/fhir/StructureDefinition/ImagingStudy, http://hl7.org/fhir/StructureDefinition/ImagingSelection, http://hl7.org/fhir/StructureDefinition/QuestionnaireResponse, http://hl7.org/fhir/StructureDefinition/Observation, http://hl7.org/fhir/StructureDefinition/MolecularSequence, http://hl7.org/fhir/StructureDefinition/GenomicStudy). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } @@ -1170,13 +1398,42 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/Period" } + }, + { + "name": "Attachment", + "condition": { + "fhir-type-label": "Attachment" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Attachment" + } + }, + { + "name": "Reference", + "condition": { + "fhir-type-label": "Reference" + }, + "assertion": { + "allOf": [ + { + "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + }, + { + "validate": { + "schema": { + "ref": "http://hl7.org/fhir/StructureDefinition/MolecularSequence" + } + } + } + ] + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Quantity','CodeableConcept','string','boolean','integer','Range','Ratio','SampledData','time','dateTime','Period')", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Quantity','CodeableConcept','string','boolean','integer','Range','Ratio','SampledData','time','dateTime','Period','Attachment','Reference')", "type": "Invalid" } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json index 94e66236..05023009 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/ProfiledQuestionnaire.json @@ -4,51 +4,16 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/ProfiledBackboneAndContentref", - "base": "http://hl7.org/fhir/StructureDefinition/Questionnaire,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Questionnaire,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Questionnaire", "abstract": false, "derivation": "constraint" } }, "fhir-type-label": "Questionnaire", - "fhirPath-dom-2": { - "key": "dom-2", - "expression": "contained.contained.empty()", - "severity": "error", - "bestPractice": false, - "humanDescription": "If the resource is contained in another resource, it SHALL NOT contain nested Resources" - }, - "fhirPath-dom-3": { - "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", - "severity": "error", - "bestPractice": false, - "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" - }, - "fhirPath-dom-4": { - "key": "dom-4", - "expression": "contained.meta.versionId.empty() and contained.meta.lastUpdated.empty()", - "severity": "error", - "bestPractice": false, - "humanDescription": "If a resource is contained in another resource, it SHALL NOT have a meta.versionId or a meta.lastUpdated" - }, - "fhirPath-dom-5": { - "key": "dom-5", - "expression": "contained.meta.security.empty()", - "severity": "error", - "bestPractice": false, - "humanDescription": "If a resource is contained in another resource, it SHALL NOT have a security label" - }, - "fhirPath-dom-6": { - "key": "dom-6", - "expression": "text.`div`.exists()", - "severity": "warning", - "bestPractice": true, - "humanDescription": "A resource should have narrative for robust management" - }, - "fhirPath-que-0": { - "key": "que-0", - "expression": "name.matches('[A-Z]([A-Za-z0-9_]){0,254}')", + "fhirPath-cnl-0": { + "key": "cnl-0", + "expression": "name.exists() implies name.matches('^[A-Z]([A-Za-z0-9_]){1,254}$')", "severity": "warning", "bestPractice": false, "humanDescription": "Name should be usable as an identifier for the module by machine processing applications such as code generation" @@ -82,8 +47,8 @@ "id": "#Questionnaire.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -116,6 +81,13 @@ }, "url": { "id": "#Questionnaire.url", + "fhirPath-cnl-1": { + "key": "cnl-1", + "expression": "exists() implies matches('^[^|# ]+$')", + "severity": "warning", + "bestPractice": false, + "humanDescription": "URL should not contain | or # - these characters make processing canonical references problematic" + }, "FastInvariant-ele1": {}, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/uri" @@ -132,6 +104,48 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, + "versionAlgorithm[x]": { + "id": "#Questionnaire.versionAlgorithm[x]", + "binding": { + "abstractAllowed": true, + "strength": "extensible", + "valueSet": "http://hl7.org/fhir/ValueSet/version-algorithm" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "slice": { + "ordered": false, + "defaultAtEnd": false, + "case": [ + { + "name": "string", + "condition": { + "fhir-type-label": "string" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/string" + } + }, + { + "name": "Coding", + "condition": { + "fhir-type-label": "Coding" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Coding" + } + } + ], + "default": { + "issue": { + "issueNumber": 1011, + "severity": "Error", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('string','Coding')", + "type": "Invalid" + } + } + } + }, "name": { "id": "#Questionnaire.name", "FastInvariant-ele1": {}, @@ -155,7 +169,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/publication-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/publication-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -172,7 +186,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/resource-types|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/resource-types|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..*", @@ -231,6 +245,12 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/markdown" }, + "copyrightLabel": { + "id": "#Questionnaire.copyrightLabel", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + }, "approvalDate": { "id": "#Questionnaire.approvalDate", "FastInvariant-ele1": {}, @@ -263,12 +283,26 @@ "item": { "id": "#Questionnaire.item", "FastInvariant-ele1": {}, - "fhirPath-que-1": { - "key": "que-1", - "expression": "(type='group' implies item.empty().not()) and (type.trace('type')='display' implies item.trace('item').empty())", + "fhirPath-que-1a": { + "key": "que-1a", + "expression": "(type='group' and %resource.status='complete') implies item.empty().not()", + "severity": "error", + "bestPractice": false, + "humanDescription": "Group items must have nested items when Questionanire is complete" + }, + "fhirPath-que-1b": { + "key": "que-1b", + "expression": "type='group' implies item.empty().not()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Groups should have items" + }, + "fhirPath-que-1c": { + "key": "que-1c", + "expression": "type='display' implies item.empty()", "severity": "error", "bestPractice": false, - "humanDescription": "Group items must have nested items, display items cannot have nested items" + "humanDescription": "Display items cannot have child items" }, "fhirPath-que-3": { "key": "que-3", @@ -286,10 +320,10 @@ }, "fhirPath-que-5": { "key": "que-5", - "expression": "(type ='choice' or type = 'open-choice' or type = 'decimal' or type = 'integer' or type = 'date' or type = 'dateTime' or type = 'time' or type = 'string' or type = 'quantity') or (answerValueSet.empty() and answerOption.empty())", + "expression": "(type='coding' or type = 'decimal' or type = 'integer' or type = 'date' or type = 'dateTime' or type = 'time' or type = 'string' or type = 'quantity') or (answerValueSet.empty() and answerOption.empty())", "severity": "error", "bestPractice": false, - "humanDescription": "Only 'choice' and 'open-choice' items can have answerValueSet" + "humanDescription": "Only coding, decimal, integer, date, dateTime, time, string or quantity items can have answerOption or answerValueSet" }, "fhirPath-que-6": { "key": "que-6", @@ -314,7 +348,7 @@ }, "fhirPath-que-10": { "key": "que-10", - "expression": "(type in ('boolean' | 'decimal' | 'integer' | 'string' | 'text' | 'url' | 'open-choice')) or maxLength.empty()", + "expression": "(type in ('boolean' | 'decimal' | 'integer' | 'string' | 'text' | 'url')) or answerConstraint='optionOrString' or maxLength.empty()", "severity": "error", "bestPractice": false, "humanDescription": "Maximum length can only be declared for simple question types" @@ -324,11 +358,11 @@ "expression": "answerOption.empty() or initial.empty()", "severity": "error", "bestPractice": false, - "humanDescription": "If one or more answerOption is present, initial[x] must be missing" + "humanDescription": "If one or more answerOption is present, initial cannot be present. Use answerOption.initialSelected instead" }, "fhirPath-que-12": { "key": "que-12", - "expression": "enableWhen.count() > 2 implies enableBehavior.exists()", + "expression": "enableWhen.count() > 1 implies enableBehavior.exists()", "severity": "error", "bestPractice": false, "humanDescription": "If there are more than one enableWhen, enableBehavior must be specified" @@ -340,6 +374,13 @@ "bestPractice": false, "humanDescription": "Can only have multiple initial values for repeating items" }, + "fhirPath-que-14": { + "key": "que-14", + "expression": "answerConstraint.exists() implies answerOption.exists() or answerValueSet.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Can only have answerConstraint if answerOption or answerValueSet are present. (This is a warning because extensions may serve the same purpose)" + }, "cardinality": "1..100", "children": { "id": { @@ -364,6 +405,13 @@ "linkId": { "id": "#Questionnaire.item.linkId", "FastInvariant-ele1": {}, + "fhirPath-que-15": { + "key": "que-15", + "expression": "$this.length() <= 255", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Link ids should be 255 characters or less" + }, "cardinality": "1..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, @@ -401,7 +449,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/item-type|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/item-type|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -449,7 +497,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-operator|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-operator|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -587,7 +635,18 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-behavior|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-behavior|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, + "disabledDisplay": { + "id": "#Questionnaire.item.disabledDisplay", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-disabled-display|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -617,6 +676,17 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/integer" }, + "answerConstraint": { + "id": "#Questionnaire.item.answerConstraint", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-answer-constraint|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, "answerValueSet": { "id": "#Questionnaire.item.answerValueSet", "FastInvariant-ele1": {}, diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json index 3da46bdd..be36e6e5 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Questionnaire.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/Questionnaire", - "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Questionnaire", "abstract": false, "derivation": "specialization" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -46,6 +46,13 @@ "bestPractice": true, "humanDescription": "A resource should have narrative for robust management" }, + "fhirPath-cnl-0": { + "key": "cnl-0", + "expression": "name.exists() implies name.matches('^[A-Z]([A-Za-z0-9_]){1,254}$')", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Name should be usable as an identifier for the module by machine processing applications such as code generation" + }, "fhirPath-que-2": { "key": "que-2", "expression": "descendants().linkId.isDistinct()", @@ -53,13 +60,6 @@ "bestPractice": false, "humanDescription": "The link ids for groups and questions must be unique within the questionnaire" }, - "fhirPath-que-0": { - "key": "que-0", - "expression": "name.matches('[A-Z]([A-Za-z0-9_]){0,254}')", - "severity": "warning", - "bestPractice": false, - "humanDescription": "Name should be usable as an identifier for the module by machine processing applications such as code generation" - }, "children": { "id": { "id": "#Questionnaire.id", @@ -82,8 +82,8 @@ "id": "#Questionnaire.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -117,12 +117,26 @@ "url": { "id": "#Questionnaire.url", "FastInvariant-ele1": {}, + "fhirPath-cnl-1": { + "key": "cnl-1", + "expression": "exists() implies matches('^[^|# ]+$')", + "severity": "warning", + "bestPractice": false, + "humanDescription": "URL should not contain | or # - these characters make processing canonical references problematic" + }, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/uri" }, "identifier": { "id": "#Questionnaire.identifier", "FastInvariant-ele1": {}, + "fhirPath-ident-1": { + "key": "ident-1", + "expression": "value.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Identifier with no value has limited utility. If communicating that an identifier value has been suppressed or missing, the value element SHOULD be present with an extension indicating the missing semantic - e.g. data-absent-reason" + }, "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Identifier" }, @@ -132,6 +146,48 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, + "versionAlgorithm[x]": { + "id": "#Questionnaire.versionAlgorithm[x]", + "binding": { + "abstractAllowed": true, + "strength": "extensible", + "valueSet": "http://hl7.org/fhir/ValueSet/version-algorithm" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "slice": { + "ordered": false, + "defaultAtEnd": false, + "case": [ + { + "name": "string", + "condition": { + "fhir-type-label": "string" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/string" + } + }, + { + "name": "Coding", + "condition": { + "fhir-type-label": "Coding" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Coding" + } + } + ], + "default": { + "issue": { + "issueNumber": 1011, + "severity": "Error", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('string','Coding')", + "type": "Invalid" + } + } + } + }, "name": { "id": "#Questionnaire.name", "FastInvariant-ele1": {}, @@ -155,7 +211,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/publication-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/publication-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -172,7 +228,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/resource-types|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/resource-types|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..*", @@ -231,6 +287,12 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/markdown" }, + "copyrightLabel": { + "id": "#Questionnaire.copyrightLabel", + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/string" + }, "approvalDate": { "id": "#Questionnaire.approvalDate", "FastInvariant-ele1": {}, @@ -248,10 +310,10 @@ "FastInvariant-ele1": {}, "fhirPath-per-1": { "key": "per-1", - "expression": "start.hasValue().not() or end.hasValue().not() or (start <= end)", + "expression": "start.hasValue().not() or end.hasValue().not() or (start.lowBoundary() <= end.highBoundary())", "severity": "error", "bestPractice": false, - "humanDescription": "If present, start SHALL have a lower value than end" + "humanDescription": "If present, start SHALL have a lower or equal value than end" }, "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/Period" @@ -263,6 +325,13 @@ "strength": "example", "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-questions" }, + "fhirPath-cod-1": { + "key": "cod-1", + "expression": "code.exists().not() implies display.exists().not()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "A Coding SHOULD NOT have a display unless a code is also present. Computation on Coding.display alone is generally unsafe. Consider using CodeableConcept.text" + }, "FastInvariant-ele1": {}, "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Coding" @@ -293,10 +362,10 @@ }, "fhirPath-que-5": { "key": "que-5", - "expression": "(type ='choice' or type = 'open-choice' or type = 'decimal' or type = 'integer' or type = 'date' or type = 'dateTime' or type = 'time' or type = 'string' or type = 'quantity') or (answerValueSet.empty() and answerOption.empty())", + "expression": "(type='coding' or type = 'decimal' or type = 'integer' or type = 'date' or type = 'dateTime' or type = 'time' or type = 'string' or type = 'quantity') or (answerValueSet.empty() and answerOption.empty())", "severity": "error", "bestPractice": false, - "humanDescription": "Only 'choice' and 'open-choice' items can have answerValueSet" + "humanDescription": "Only coding, decimal, integer, date, dateTime, time, string or quantity items can have answerOption or answerValueSet" }, "fhirPath-que-4": { "key": "que-4", @@ -312,19 +381,33 @@ "bestPractice": false, "humanDescription": "Display items cannot have a \"code\" asserted" }, - "fhirPath-que-10": { - "key": "que-10", - "expression": "(type in ('boolean' | 'decimal' | 'integer' | 'string' | 'text' | 'url' | 'open-choice')) or maxLength.empty()", + "fhirPath-que-1c": { + "key": "que-1c", + "expression": "type='display' implies item.empty()", "severity": "error", "bestPractice": false, - "humanDescription": "Maximum length can only be declared for simple question types" + "humanDescription": "Display items cannot have child items" }, - "fhirPath-que-1": { - "key": "que-1", - "expression": "(type='group' implies item.empty().not()) and (type.trace('type')='display' implies item.trace('item').empty())", + "fhirPath-que-1a": { + "key": "que-1a", + "expression": "(type='group' and %resource.status='complete') implies item.empty().not()", "severity": "error", "bestPractice": false, - "humanDescription": "Group items must have nested items, display items cannot have nested items" + "humanDescription": "Group items must have nested items when Questionanire is complete" + }, + "fhirPath-que-1b": { + "key": "que-1b", + "expression": "type='group' implies item.empty().not()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Groups should have items" + }, + "fhirPath-que-10": { + "key": "que-10", + "expression": "(type in ('boolean' | 'decimal' | 'integer' | 'string' | 'text' | 'url')) or answerConstraint='optionOrString' or maxLength.empty()", + "severity": "error", + "bestPractice": false, + "humanDescription": "Maximum length can only be declared for simple question types" }, "fhirPath-que-13": { "key": "que-13", @@ -333,16 +416,23 @@ "bestPractice": false, "humanDescription": "Can only have multiple initial values for repeating items" }, + "fhirPath-que-14": { + "key": "que-14", + "expression": "answerConstraint.exists() implies answerOption.exists() or answerValueSet.exists()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Can only have answerConstraint if answerOption or answerValueSet are present. (This is a warning because extensions may serve the same purpose)" + }, "fhirPath-que-11": { "key": "que-11", "expression": "answerOption.empty() or initial.empty()", "severity": "error", "bestPractice": false, - "humanDescription": "If one or more answerOption is present, initial[x] must be missing" + "humanDescription": "If one or more answerOption is present, initial cannot be present. Use answerOption.initialSelected instead" }, "fhirPath-que-12": { "key": "que-12", - "expression": "enableWhen.count() > 2 implies enableBehavior.exists()", + "expression": "enableWhen.count() > 1 implies enableBehavior.exists()", "severity": "error", "bestPractice": false, "humanDescription": "If there are more than one enableWhen, enableBehavior must be specified" @@ -377,6 +467,13 @@ "linkId": { "id": "#Questionnaire.item.linkId", "FastInvariant-ele1": {}, + "fhirPath-que-15": { + "key": "que-15", + "expression": "$this.length() <= 255", + "severity": "warning", + "bestPractice": false, + "humanDescription": "Link ids should be 255 characters or less" + }, "cardinality": "1..1", "ref": "http://hl7.org/fhir/StructureDefinition/string" }, @@ -393,6 +490,13 @@ "strength": "example", "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-questions" }, + "fhirPath-cod-1": { + "key": "cod-1", + "expression": "code.exists().not() implies display.exists().not()", + "severity": "warning", + "bestPractice": false, + "humanDescription": "A Coding SHOULD NOT have a display unless a code is also present. Computation on Coding.display alone is generally unsafe. Consider using CodeableConcept.text" + }, "FastInvariant-ele1": {}, "cardinality": "0..*", "ref": "http://hl7.org/fhir/StructureDefinition/Coding" @@ -414,7 +518,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/item-type|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/item-type|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -462,7 +566,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-operator|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-operator|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -600,7 +704,18 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-behavior|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-enable-behavior|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, + "disabledDisplay": { + "id": "#Questionnaire.item.disabledDisplay", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-disabled-display|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -630,6 +745,17 @@ "cardinality": "0..1", "ref": "http://hl7.org/fhir/StructureDefinition/integer" }, + "answerConstraint": { + "id": "#Questionnaire.item.answerConstraint", + "binding": { + "abstractAllowed": true, + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/questionnaire-answer-constraint|5.0.0" + }, + "FastInvariant-ele1": {}, + "cardinality": "0..1", + "ref": "http://hl7.org/fhir/StructureDefinition/code" + }, "answerValueSet": { "id": "#Questionnaire.item.answerValueSet", "FastInvariant-ele1": {}, diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json index 05d6eba4..0ec983e9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SecondaryTypeRefSlice.json @@ -4,7 +4,7 @@ "schema-subtype": "resource", "sd-info": { "url": "http://validationtest.org/fhir/StructureDefinition/SecondaryTargetRefSlice", - "base": "http://hl7.org/fhir/StructureDefinition/Communication,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource", + "base": "http://hl7.org/fhir/StructureDefinition/Communication,http://hl7.org/fhir/StructureDefinition/DomainResource,http://hl7.org/fhir/StructureDefinition/Resource,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Communication", "abstract": false, "derivation": "constraint" @@ -20,7 +20,7 @@ }, "fhirPath-dom-3": { "key": "dom-3", - "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().as(canonical) | %resource.descendants().as(uri) | %resource.descendants().as(url))) or descendants().where(reference = '#').exists() or descendants().where(as(canonical) = '#').exists() or descendants().where(as(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", + "expression": "contained.where((('#'+id in (%resource.descendants().reference | %resource.descendants().ofType(canonical) | %resource.descendants().ofType(uri) | %resource.descendants().ofType(url))) or descendants().where(reference = '#').exists() or descendants().where(ofType(canonical) = '#').exists() or descendants().where(ofType(canonical) = '#').exists()).not()).trace('unmatched', id).empty()", "severity": "error", "bestPractice": false, "humanDescription": "If the resource is contained in another resource, it SHALL be referred to from elsewhere in the resource or SHALL refer to the containing resource" @@ -68,8 +68,8 @@ "id": "#Communication.language", "binding": { "abstractAllowed": true, - "strength": "preferred", - "valueSet": "http://hl7.org/fhir/ValueSet/languages" + "strength": "required", + "valueSet": "http://hl7.org/fhir/ValueSet/all-languages|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -174,7 +174,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/event-status|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/event-status|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "1..1", @@ -207,7 +207,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/request-priority|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/request-priority|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..1", @@ -344,6 +344,15 @@ "ordered": false, "defaultAtEnd": false, "case": [ + { + "name": "forCareTeam", + "condition": { + "fhir-type-label": "CareTeam" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/CareTeam" + } + }, { "name": "forDevice", "condition": { @@ -353,6 +362,33 @@ "ref": "http://hl7.org/fhir/StructureDefinition/Device" } }, + { + "name": "forGroup", + "condition": { + "fhir-type-label": "Group" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Group" + } + }, + { + "name": "forHealthcareService", + "condition": { + "fhir-type-label": "HealthcareService" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/HealthcareService" + } + }, + { + "name": "forLocation", + "condition": { + "fhir-type-label": "Location" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Location" + } + }, { "name": "forOrganization", "condition": { @@ -399,30 +435,12 @@ } }, { - "name": "forGroup", - "condition": { - "fhir-type-label": "Group" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Group" - } - }, - { - "name": "forCareTeam", - "condition": { - "fhir-type-label": "CareTeam" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/CareTeam" - } - }, - { - "name": "forHealthcareService", + "name": "forEndpoint", "condition": { - "fhir-type-label": "HealthcareService" + "fhir-type-label": "Endpoint" }, "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/HealthcareService" + "ref": "http://hl7.org/fhir/StructureDefinition/Endpoint" } } ], @@ -430,7 +448,7 @@ "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/Group, http://hl7.org/fhir/StructureDefinition/CareTeam, http://hl7.org/fhir/StructureDefinition/HealthcareService). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/CareTeam, http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Group, http://hl7.org/fhir/StructureDefinition/HealthcareService, http://hl7.org/fhir/StructureDefinition/Location, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/Endpoint). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } @@ -517,13 +535,31 @@ "assertion": { "ref": "http://hl7.org/fhir/StructureDefinition/HealthcareService" } + }, + { + "name": "forEndpoint", + "condition": { + "fhir-type-label": "Endpoint" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/Endpoint" + } + }, + { + "name": "forCareTeam", + "condition": { + "fhir-type-label": "CareTeam" + }, + "assertion": { + "ref": "http://hl7.org/fhir/StructureDefinition/CareTeam" + } } ], "default": { "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/HealthcareService). None of these are profiles on type %INSTANCETYPE% of the resource.", + "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Device, http://hl7.org/fhir/StructureDefinition/Organization, http://hl7.org/fhir/StructureDefinition/Patient, http://hl7.org/fhir/StructureDefinition/Practitioner, http://hl7.org/fhir/StructureDefinition/PractitionerRole, http://hl7.org/fhir/StructureDefinition/RelatedPerson, http://hl7.org/fhir/StructureDefinition/HealthcareService, http://hl7.org/fhir/StructureDefinition/Endpoint, http://hl7.org/fhir/StructureDefinition/CareTeam). None of these are profiles on type %INSTANCETYPE% of the resource.", "type": "Invalid" } } @@ -533,8 +569,8 @@ } ] }, - "reasonCode": { - "id": "#Communication.reasonCode", + "reason": { + "id": "#Communication.reason", "binding": { "abstractAllowed": true, "strength": "example", @@ -542,69 +578,14 @@ }, "FastInvariant-ele1": {}, "cardinality": "0..*", - "ref": "http://hl7.org/fhir/StructureDefinition/CodeableConcept" - }, - "reasonReference": { - "id": "#Communication.reasonReference", - "FastInvariant-ele1": {}, - "cardinality": "0..*", "allOf": [ { - "ref": "http://hl7.org/fhir/StructureDefinition/Reference" + "ref": "http://hl7.org/fhir/StructureDefinition/CodeableReference" }, { "validate": { "schema": { - "slice": { - "ordered": false, - "defaultAtEnd": false, - "case": [ - { - "name": "forCondition", - "condition": { - "fhir-type-label": "Condition" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Condition" - } - }, - { - "name": "forObservation", - "condition": { - "fhir-type-label": "Observation" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/Observation" - } - }, - { - "name": "forDiagnosticReport", - "condition": { - "fhir-type-label": "DiagnosticReport" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/DiagnosticReport" - } - }, - { - "name": "forDocumentReference", - "condition": { - "fhir-type-label": "DocumentReference" - }, - "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/DocumentReference" - } - } - ], - "default": { - "issue": { - "issueNumber": 1011, - "severity": "Error", - "message": "Referenced resource '%RESOURCEURL%' does not validate against any of the expected target profiles (http://hl7.org/fhir/StructureDefinition/Condition, http://hl7.org/fhir/StructureDefinition/Observation, http://hl7.org/fhir/StructureDefinition/DiagnosticReport, http://hl7.org/fhir/StructureDefinition/DocumentReference). None of these are profiles on type %INSTANCETYPE% of the resource.", - "type": "Invalid" - } - } - } + "ref": "http://hl7.org/fhir/StructureDefinition/Resource" } } } @@ -643,12 +624,12 @@ "defaultAtEnd": false, "case": [ { - "name": "string", + "name": "Attachment", "condition": { - "fhir-type-label": "string" + "fhir-type-label": "Attachment" }, "assertion": { - "ref": "http://hl7.org/fhir/StructureDefinition/string" + "ref": "http://hl7.org/fhir/StructureDefinition/Attachment" } }, { @@ -707,7 +688,7 @@ "issue": { "issueNumber": 1011, "severity": "Error", - "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('string','Reference')", + "message": "Element is of type '%INSTANCETYPE%', which is not one of the allowed choice types ('Attachment','Reference')", "type": "Invalid" } } @@ -719,17 +700,17 @@ "defaultAtEnd": false, "case": [ { - "name": "String", + "name": "Attachment", "condition": { "pathSelector": { "path": "content", "assertion": { - "fhir-type-label": "string" + "fhir-type-label": "Attachment" } } }, "assertion": { - "id": "#Communication.payload:String", + "id": "#Communication.payload:Attachment", "FastInvariant-ele1": {}, "cardinality": "0..*", "children": { @@ -756,7 +737,7 @@ "id": "#Communication.payload.content[x]", "FastInvariant-ele1": {}, "cardinality": "1..1", - "ref": "http://hl7.org/fhir/StructureDefinition/string" + "ref": "http://hl7.org/fhir/StructureDefinition/Attachment" } } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SimpleQuantity.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SimpleQuantity.json index 6cae7b99..55ec6906 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SimpleQuantity.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/SimpleQuantity.json @@ -4,7 +4,7 @@ "schema-subtype": "datatype", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/SimpleQuantity", - "base": "http://hl7.org/fhir/StructureDefinition/Quantity,http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/Quantity,http://hl7.org/fhir/StructureDefinition/DataType,http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "Quantity", "abstract": false, "derivation": "constraint" @@ -50,7 +50,7 @@ "binding": { "abstractAllowed": true, "strength": "required", - "valueSet": "http://hl7.org/fhir/ValueSet/quantity-comparator|4.0.1" + "valueSet": "http://hl7.org/fhir/ValueSet/quantity-comparator|5.0.0" }, "FastInvariant-ele1": {}, "cardinality": "0..0", diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Xhtml.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Xhtml.json index 6504e64f..70dd50dd 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Xhtml.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/Xhtml.json @@ -4,7 +4,7 @@ "schema-subtype": "datatype", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/xhtml", - "base": "http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "xhtml", "abstract": false, "derivation": "specialization" diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/boolean.json b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/boolean.json index c4ca144d..5599583a 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/boolean.json +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R5/SchemaSnaps/boolean.json @@ -4,7 +4,7 @@ "schema-subtype": "datatype", "sd-info": { "url": "http://hl7.org/fhir/StructureDefinition/boolean", - "base": "http://hl7.org/fhir/StructureDefinition/Element", + "base": "http://hl7.org/fhir/StructureDefinition/PrimitiveType,http://hl7.org/fhir/StructureDefinition/DataType,http://hl7.org/fhir/StructureDefinition/Element,http://hl7.org/fhir/StructureDefinition/Base", "datatype": "boolean", "abstract": false, "derivation": "specialization" diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs index ad622af3..9cca1636 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs @@ -34,9 +34,8 @@ public class BasicSchemaBuilderTests : IClassFixture public BasicSchemaBuilderTests(SchemaBuilderFixture fixture, ITestOutputHelper oh) => (_output, _fixture) = (oh, fixture); -#if !R5 - // [Fact(Skip = "Only enable this when you want to rewrite the snaps to update them to a new correct situation")] - [Fact] + [Fact(Skip = "Only enable this when you want to rewrite the snaps to update them to a new correct situation")] + //[Fact] public void OverwriteSchemaSnaps() { compareToSchemaSnaps(true); @@ -47,7 +46,6 @@ public void CompareToCorrectSchemaSnaps() { compareToSchemaSnaps(false); } -#endif private void compareToSchemaSnaps(bool overwrite) { diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs index b4799e54..0d44dd5a 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs @@ -213,7 +213,7 @@ private static StructureDefinition buildSliceWithSecondaryTargetReferenceDiscrim // Intro slice child content[x] cons.Add(new ElementDefinition("Communication.payload.content[x]") - .OrType(FHIRAllTypes.String) + .OrType(FHIRAllTypes.Attachment) .OrReferenceWithProfiles( new[] { "http://hl7.org/fhir/StructureDefinition/DocumentReference", "http://hl7.org/fhir/StructureDefinition/Task" })); @@ -221,14 +221,14 @@ private static StructureDefinition buildSliceWithSecondaryTargetReferenceDiscrim // Slice 1 ========================== cons.Add(new ElementDefinition("Communication.payload") { - ElementId = "Communication.payload:String", - SliceName = "String" + ElementId = "Communication.payload:Attachment", + SliceName = "Attachment" }); cons.Add(new ElementDefinition("Communication.payload.content[x]") { - ElementId = "Communication.payload:String.content[x]", - }.OfType(FHIRAllTypes.String)); + ElementId = "Communication.payload:Attachment.content[x]", + }.OfType(FHIRAllTypes.Attachment)); // Slice 2 =========================== cons.Add(new ElementDefinition("Communication.payload") From 911e1e0f665bbeb5834b3b475a29de6654d38d90 Mon Sep 17 00:00:00 2001 From: mmsmits Date: Tue, 21 Nov 2023 13:31:50 +0100 Subject: [PATCH 06/27] Ignore AddFirelySdkValidatorResults() again --- .../FhirTests/ValidationManifestTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs index 925dd83f..6baf5fff 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/ValidationManifestTest.cs @@ -72,8 +72,9 @@ public void OldExamples(TestCase testCase, string baseDirectory) /// that method /// [TestMethod] + [Ignore] public void AddFirelySdkValidatorResults() - => _runner.AddOrEditValidatorResults(TEST_CASES_MANIFEST, new[] { WipValidator.Create() }); + => _runner.AddOrEditValidatorResults(TEST_CASES_MANIFEST, new[] { CurrentValidator.Create(), WipValidator.Create() }); [TestMethod] public void RoundTripTest() From 51448743f1ad86de6c01d963dc47394d07e583d2 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Tue, 21 Nov 2023 13:48:24 +0100 Subject: [PATCH 07/27] Why did I pick a resource that changed in R5? Sigh. --- .../TestProfileArtifactSource.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs index 0d44dd5a..bacac4d3 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/TestProfileArtifactSource.cs @@ -211,6 +211,7 @@ private static StructureDefinition buildSliceWithSecondaryTargetReferenceDiscrim (ElementDefinition.DiscriminatorType.Type, "content.ofType(Reference).resolve()")); cons.Add(slicingIntro); +#if R5 // Intro slice child content[x] cons.Add(new ElementDefinition("Communication.payload.content[x]") .OrType(FHIRAllTypes.Attachment) @@ -229,6 +230,26 @@ private static StructureDefinition buildSliceWithSecondaryTargetReferenceDiscrim { ElementId = "Communication.payload:Attachment.content[x]", }.OfType(FHIRAllTypes.Attachment)); +#else + // Intro slice child content[x] + cons.Add(new ElementDefinition("Communication.payload.content[x]") + .OrType(FHIRAllTypes.String) + .OrReferenceWithProfiles( + new[] { "http://hl7.org/fhir/StructureDefinition/DocumentReference", + "http://hl7.org/fhir/StructureDefinition/Task" })); + + // Slice 1 ========================== + cons.Add(new ElementDefinition("Communication.payload") + { + ElementId = "Communication.payload:String", + SliceName = "String" + }); + + cons.Add(new ElementDefinition("Communication.payload.content[x]") + { + ElementId = "Communication.payload:String.content[x]", + }.OfType(FHIRAllTypes.String)); +#endif // Slice 2 =========================== cons.Add(new ElementDefinition("Communication.payload") From ab35ef37dd9bd6afbec9f1e8878b4986568b9acf Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Wed, 22 Nov 2023 17:53:03 +0100 Subject: [PATCH 08/27] Step 1 - Create a public entry point, the Validator --- Firely.Validator.API.sln | 15 +- .../Impl/BindingValidator.cs | 8 +- .../Impl/ExtensionSchema.cs | 2 +- .../Impl/ResourceSchema.cs | 4 +- .../Schema/ValidationContext.cs | 152 +++--- .../Hl7.Fhir.Validation.R4.csproj | 21 +- src/Hl7.Fhir.Validation.R4/fhir-single.zip | Bin 287722 -> 0 bytes .../Hl7.Fhir.Validation.STU3.csproj | 23 + .../Properties/AssemblyInfo.cs | 12 + .../ConstraintBestPracticesSeverity.cs | 26 - .../Hl7.Fhir.Validation.Shared.projitems | 7 +- .../IExternalReferenceResolver.cs | 23 + .../IsExternalInit.cs | 24 - .../PocoValidationExtensions.cs | 52 -- .../SchemaCollection.cs | 63 --- .../ValidationSettings.cs | 158 ------ src/Hl7.Fhir.Validation.Shared/Validator.cs | 459 ++---------------- .../XmlValidationExtensions.cs | 136 ------ .../BasicSchemaBuilderTests.cs | 12 +- .../Impl/BindingValidatorTests.cs | 4 +- .../Impl/ResourceSchemaTests.cs | 6 +- 21 files changed, 230 insertions(+), 977 deletions(-) delete mode 100644 src/Hl7.Fhir.Validation.R4/fhir-single.zip create mode 100644 src/Hl7.Fhir.Validation.STU3/Hl7.Fhir.Validation.STU3.csproj create mode 100644 src/Hl7.Fhir.Validation.STU3/Properties/AssemblyInfo.cs delete mode 100644 src/Hl7.Fhir.Validation.Shared/ConstraintBestPracticesSeverity.cs create mode 100644 src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs delete mode 100644 src/Hl7.Fhir.Validation.Shared/IsExternalInit.cs delete mode 100644 src/Hl7.Fhir.Validation.Shared/PocoValidationExtensions.cs delete mode 100644 src/Hl7.Fhir.Validation.Shared/SchemaCollection.cs delete mode 100644 src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs delete mode 100644 src/Hl7.Fhir.Validation.Shared/XmlValidationExtensions.cs diff --git a/Firely.Validator.API.sln b/Firely.Validator.API.sln index 08e68bf2..0e475cf7 100644 --- a/Firely.Validator.API.sln +++ b/Firely.Validator.API.sln @@ -8,8 +8,8 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{32AC4A3D-A456-4CAC-8458-B0F20103F669}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - firely-validator-api.props = firely-validator-api.props firely-validator-api-tests.props = firely-validator-api-tests.props + firely-validator-api.props = firely-validator-api.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{7D9F5C18-2827-4975-8FBF-DD45758075AE}" @@ -27,8 +27,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Comp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{1B02120E-C942-448D-A444-6F7EB1816C95}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Hl7.Fhir.Validation.Shared", "src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.shproj", "{21BF806C-BBE6-4A33-91F9-BF3AB5F1E70B}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hl7.Fhir.Validation.R5.Tests", "test\Hl7.Fhir.Validation.Tests\Hl7.Fhir.Validation.R5.Tests.csproj", "{533FADED-DC64-408D-AF93-E7AE8D9DAF56}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hl7.Fhir.Validation.R4", "src\Hl7.Fhir.Validation.R4\Hl7.Fhir.Validation.R4.csproj", "{22520A59-7EBF-4740-A66C-3651742FAC0B}" @@ -45,6 +43,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Comp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Compilation.R4.Tests", "test\Firely.Fhir.Validation.Compilation.Tests.R4\Firely.Fhir.Validation.Compilation.R4.Tests.csproj", "{B5968107-6E13-46C1-AB04-7FCEFFF179C7}" EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Hl7.Fhir.Validation.Shared", "src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.shproj", "{21BF806C-BBE6-4A33-91F9-BF3AB5F1E70B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hl7.Fhir.Validation.STU3", "src\Hl7.Fhir.Validation.STU3\Hl7.Fhir.Validation.STU3.csproj", "{4C9C5275-0C05-4CDE-A74C-1B43245252FB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -112,6 +114,12 @@ Global {B5968107-6E13-46C1-AB04-7FCEFFF179C7}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {B5968107-6E13-46C1-AB04-7FCEFFF179C7}.Release|Any CPU.ActiveCfg = Release|Any CPU {B5968107-6E13-46C1-AB04-7FCEFFF179C7}.Release|Any CPU.Build.0 = Release|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.FullDebug|Any CPU.Build.0 = Debug|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C9C5275-0C05-4CDE-A74C-1B43245252FB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -132,6 +140,7 @@ Global src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems*{052b9175-3a5b-46ca-b677-8d082c9a6063}*SharedItemsImports = 5 src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.projitems*{21bf806c-bbe6-4a33-91f9-bf3ab5f1e70b}*SharedItemsImports = 13 src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.projitems*{22520a59-7ebf-4740-a66c-3651742fac0b}*SharedItemsImports = 5 + src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.projitems*{4c9c5275-0c05-4cde-a74c-1b43245252fb}*SharedItemsImports = 5 src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems*{7391862b-ac99-40c5-a616-dc83edd83a88}*SharedItemsImports = 5 src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems*{7794c7d2-ea50-405d-a61a-23f8ecf4228b}*SharedItemsImports = 13 test\Firely.Fhir.Validation.Compilation.Tests.Shared\Firely.Fhir.Validation.Compilation.Tests.Shared.projitems*{994a42c0-396b-48b6-81a0-4e622460ecab}*SharedItemsImports = 5 diff --git a/src/Firely.Fhir.Validation/Impl/BindingValidator.cs b/src/Firely.Fhir.Validation/Impl/BindingValidator.cs index 20210598..f0ce451b 100644 --- a/src/Firely.Fhir.Validation/Impl/BindingValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/BindingValidator.cs @@ -261,14 +261,14 @@ private static (Issue?, string?) callService(ValidateCodeParameters parameters, } catch (FhirOperationException tse) { - var desiredResult = ctx.OnValidateCodeServiceFailure?.Invoke(parameters, tse) - ?? ValidationContext.TerminologyServiceExceptionResult.Warning; + var desiredResult = ctx.ValidateCodeServiceFailureHandler?.Invoke(parameters, tse) + ?? TerminologyServiceExceptionResult.Warning; var message = $"Terminology service failed while validating {display}: {tse.Message}"; return desiredResult switch { - ValidationContext.TerminologyServiceExceptionResult.Error => (Issue.TERMINOLOGY_OUTPUT_ERROR, message), - ValidationContext.TerminologyServiceExceptionResult.Warning => (Issue.TERMINOLOGY_OUTPUT_WARNING, message), + TerminologyServiceExceptionResult.Error => (Issue.TERMINOLOGY_OUTPUT_ERROR, message), + TerminologyServiceExceptionResult.Warning => (Issue.TERMINOLOGY_OUTPUT_WARNING, message), _ => throw new NotSupportedException("Logic error: unknown terminology service exception result.") }; } diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs index 461b019f..baf7646d 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs @@ -119,7 +119,7 @@ public override ResultReport Validate(IEnumerable input, Validation return ResultReport.FromEvidence(evidence); static ExtensionUrlFollower callback(ValidationContext context) => - context.FollowExtensionUrl ?? ((l, c) => ExtensionUrlHandling.WarnIfMissing); + context.ExtensionUrlFollower ?? ((l, c) => ExtensionUrlHandling.WarnIfMissing); } /// diff --git a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs index 3398810a..267a72e5 100644 --- a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs @@ -51,8 +51,8 @@ internal static Canonical[] GetMetaProfileSchemas(IScopedNode instance, Validati return callback(vc).Invoke(state.Location.InstanceLocation.ToString(), profiles.ToArray()); - static ValidationContext.MetaProfileSelector callback(ValidationContext context) - => context.SelectMetaProfiles ?? ((_, m) => m); + static MetaProfileSelector callback(ValidationContext context) + => context.MetaProfileSelector ?? ((_, m) => m); } /// diff --git a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs index 1c5467fb..8af4344b 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs @@ -21,40 +21,6 @@ namespace Firely.Fhir.Validation /// subsystem doing validation. public class ValidationContext { - /// - /// How to handle the extension Url - /// - public enum ExtensionUrlHandling - { - /// - /// Do not resolve the extension - /// - DontResolve, - /// - /// Add a warning to the validation result when the extension cannot be resolved - /// - WarnIfMissing, - /// - /// Add an error to the validation result when the extension cannot be resolved - /// - ErrorIfMissing, - } - - /// - /// The validation result when there is an exception in the Terminology Service - /// - public enum TerminologyServiceExceptionResult - { - /// - /// Return a warning in case of an exception in Terminology Service. - /// - Warning, - /// - /// Return an error in case of an exception in Terminology Service. - /// - Error, - } - /// /// Initializes a new ValidationContext with the minimal dependencies. /// @@ -71,24 +37,10 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT public ICodeValidationTerminologyService ValidateCodeService; /// - /// The to invoke when the validator calls out to a terminology service and this call + /// The to invoke when the validator calls out to a terminology service and this call /// results in an exception. When no function is set, the validator defaults to returning a warning. /// - public ValidateCodeServiceFailureHandler? OnValidateCodeServiceFailure = null; - - /// The function has 2 input parameters: - /// - valueSetUrl (of type Canonical): the valueSetUrl of the Binding - /// - codes (of type string): a comma separated list of codings - /// - abstract: whether a concept designated as 'abstract' is appropriate/allowed to be use or not - /// - context: the context of the value set - /// - /// Result of the function is . - /// - /// A delegate that determines the result of a failed terminology service call by the validator. - /// - /// The object that was passed to the terminology service. - /// The as returned by the service. - public delegate TerminologyServiceExceptionResult ValidateCodeServiceFailureHandler(ValidateCodeParameters p, FhirOperationException e); + public ValidateCodeServiceFailureHandler? ValidateCodeServiceFailureHandler = null; /// /// An that is used when the validator encounters a reference to @@ -108,7 +60,7 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// to contained resources will always be followed. If this property is not set, references will be /// ignored. /// - public Func>? ExternalReferenceResolver = null; + public ExternalReferenceResolver? ExternalReferenceResolver = null; /// /// An instance of the FhirPath compiler to use when evaluating constraints @@ -123,34 +75,18 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// https://www.hl7.org/fhir/best-practices.html for more information. public ValidateBestPracticesSeverity ConstraintBestPractices = ValidateBestPracticesSeverity.Warning; - /// - /// The to invoke when a is encountered. If not set, the list of profiles + /// The to invoke when a is encountered. If not set, the list of profiles /// is used as encountered in the instance. /// - public MetaProfileSelector? SelectMetaProfiles = null; + public MetaProfileSelector? MetaProfileSelector = null; /// - /// A function that determines which profiles in the validator should use to validate this instance. - /// - /// The location within the resource where the Meta.profile is found. - /// The original list of profiles found in Meta.profile. - /// A new set of meta profiles that the validator will use for validation of this instance. - public delegate Canonical[] MetaProfileSelector(string location, Canonical[] originalProfiles); - - /// - /// The to invoke when an is encountered in an instance. + /// The to invoke when an is encountered in an instance. /// If not set, then a validation of an Extension will warn if the extension cannot be resolved, or will return an error when /// the extension cannot be resolved and is a modififier extension. /// - public ExtensionUrlFollower? FollowExtensionUrl = null; - - /// - /// A function to determine how to handle an extension that is encountered in the instance. - /// - /// The location within the resource where the Meta.profile is found. - /// The canonical of the extension that was encountered in the instance. - public delegate ExtensionUrlHandling ExtensionUrlFollower(string location, Canonical? url); + public ExtensionUrlFollower? ExtensionUrlFollower = null; /// /// A function to include the assertion in the validation or not. If the function is left empty (null) then all the @@ -216,4 +152,78 @@ internal class NoopTerminologyService : ICodeValidationTerminologyService public Task ValueSetValidateCode(Parameters parameters, string? id = null, bool useGet = false) => throw new NotImplementedException(); } } + + /// + /// A function to determine how to handle an extension that is encountered in the instance. + /// + /// The location within the resource where the Meta.profile is found. + /// The canonical of the extension that was encountered in the instance. + public delegate ExtensionUrlHandling ExtensionUrlFollower(string location, Canonical? url); + + /// The function has 2 input parameters: + /// - valueSetUrl (of type Canonical): the valueSetUrl of the Binding + /// - codes (of type string): a comma separated list of codings + /// - abstract: whether a concept designated as 'abstract' is appropriate/allowed to be use or not + /// - context: the context of the value set + /// + /// Result of the function is . + /// + /// A delegate that determines the result of a failed terminology service call by the validator. + /// + /// The object that was passed to the terminology service. + /// The as returned by the service. + public delegate TerminologyServiceExceptionResult ValidateCodeServiceFailureHandler(ValidateCodeParameters p, FhirOperationException e); + + /// + /// A function that resolves an url to an external resource instance, parsed as an . + /// + /// + /// May return null if the reference could not be resolved. + public delegate Task ExternalReferenceResolver(string reference); + + /// + /// A function that determines which profiles in the validator should use to validate this instance. + /// + /// The location within the resource where the Meta.profile is found. + /// The original list of profiles found in Meta.profile. + /// A new set of meta profiles that the validator will use for validation of this instance. + public delegate Canonical[] MetaProfileSelector(string location, Canonical[] originalProfiles); + + /// + /// How to handle the extension Url + /// + public enum ExtensionUrlHandling + { + /// + /// Do not resolve the extension + /// + DontResolve, + + /// + /// Add a warning to the validation result when the extension cannot be resolved + /// + WarnIfMissing, + + /// + /// Add an error to the validation result when the extension cannot be resolved + /// + ErrorIfMissing, + } + + /// + /// The validation result when there is an exception in the Terminology Service + /// + public enum TerminologyServiceExceptionResult + { + /// + /// Return a warning in case of an exception in Terminology Service. + /// + Warning, + + /// + /// Return an error in case of an exception in Terminology Service. + /// + Error, + } + } diff --git a/src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj b/src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj index 6a1aeeaa..17042ff4 100644 --- a/src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj +++ b/src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj @@ -1,24 +1,23 @@ - - + + - Firely Validation shims for R4 - R4 specific shims to simulate the "old" validator on top of the new ElementSchema-based validator. + Firely validation library for FHIR R4 and up + Next-gen Firely validator for R4 and up, which validates FHIR data against profiles. HL7;FHIR;Validation;Utility; - Firely.Fhir.Validation.Compilation + Firely.Fhir.Validation.R4 - - - - - - + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.R4/fhir-single.zip b/src/Hl7.Fhir.Validation.R4/fhir-single.zip deleted file mode 100644 index 80eb23a8c957eef59d6031a75067b754149a9f5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 287722 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

II-X_ngt^{ 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

=L+9PrG_0+X~%QO4Y_y=*qtU{V~YpbN}Lo z*6ePEwN#$R+=wDjRtFvT8cKHsX2qNW@H^hg(b0)_#^-d$Wv+h#s%V=l&?R!XCK-7K z`8CKy`UKdQO@%;!{s7C>rCOK}L+E+ zs@O(h{8$yMR+1`=ObWi7yn%=k%x_1z5liParM`lg@%#Xu2y6|sF`ks!Nk;l&RtzU- z*_m==2ks)${y4YJ=}xSFOtaOUnDG?t1(~-|A~Rh`0M+TqtQWbNgc0myL3^Y`mQ3a2 z@9lZ}v7WbmPDS|CAl_WI9*u|U$N^jdv52s(ylOj#fU0~z3V^^J1Xh1U4IU{|LM|EG z6mWWL5oVLh$}|2c(j`c3##kzD+KxAXC(kBoX;>MM!g^41#%o_q;tSKtQ!AKU@t|Rp z%htIq*wF;w!5jg*ELz%aun1y+x*wsQ98i_D58@r~!Ds}s7Gqw63#TWX3gxbf=mCAn zE{fHI&h>k>$&o5WO=$)Xxe6S>1#MLa2>md?69V>X=*M*aLEsJ_lN8Vm2H>Q^ ze7X$J75(I%cN~M5z$V^Ut~665q*b*M7$=-~A`cTb7@Nd(p@EmLmp zr<7RWAL9YKa8PHFf5Dk_K{%pwp4Yrlyd1c9)s=Afbaz1N|Enjv<5Y;43$Rc4m(OMo zKCuh?Hb$_V@eXkII7zEM~c_f4vB;0kEo-^IuU=*n0X9W0@h`Ku&#=G zDRhm!1RltgvRSbl;Uk9e=n8ye381WnNzmpHKMbe#qxaR5Ype^;ugF8bpu(vrPb~bF z7YhX5Ps8Yx=_qCY zz=f>z#O{LfmwRkPR2NlIIN&4SB$4(G%=cS3C;B`=##&9?jhZ*XmZIv4kjpCwZxjSL zO!@j7i1I^ypi3|Jn?B{>f;l0LhthtZ^CDJ>E4F$;KPo9M-HAopz^JzG1f7gVAh|6q{s9qlvhJ}T z#+Fw7I*@X*=KMU_oz+`2bXfSq^TeZ84>ng%FJHg&c8fc)vm#>NfxdFjx0LUze&r^G z8W(6eEoM(F5sy9hZoYO|g$-%K0P)n-en5{z6QXwht|Fx5=ZO5F+K^K5`BSN(TZ@_ zZp5vv*+H~7%G7mA`MF#d`(nP@`me(YeBee6OhzMH3m%P}tT^L5AWN&3Sw&7(F5+G% z(jWIQk=~-fUYKZRMPfG;r^zT#B}lpz-K|9T0ztV>*{#x|?2Bw8#BJ$B-Y+TIJdBG) zA_w9vtzH%lI9a)oQDABH2PH)(Pw?^7y{jpTRwQ^mC2_iy*jJF)Rq@!fpswaT@G{U~ z7x>fW+SH5P_~g{A14x;qp1j0)$9dujSZ8uEpMFF+e=<)`Ci}tusvg|=?|*$RIj9BD1a~AwZ7#%D{TIo9r$sA?aGDxy&sMIz8BrEc*7J2FYNaWpS(i_KPCD>Ed1`El7 zy_9G^lo5~8XXf~f(bTQ#=~_@ICt*0|FW7~_?4G0ef~t&^NZP?;MwNwdn!+*7W??Bs z8Z{g{&GL)*2>L=> zPf}k*px&xuK?jfPj%C*sz>Us>y_53o7T2%cO*>;x+|aC2R)yAV-2`BCis_zbR;}|0 zX*iqNRM!0AcOtM1`x)KPc(oLRx5TieQ0-4uQEmoMdbynnu(`qeuF!y|1O8afL%rjH zRj+Ctp4oB(Erftf21+Q4Y36KLjteZc-ae-_Es~3KDA`2?xg@8`YcCs4L{J#BB9U2Y z%^~pPXXL#kfm zLlCIHS6>k&5mjr?B;;RLAs|ces|ESjy;uAqpTNc}w{Bmadtkfl4O(SfQ7QA(k25;# zsw5d%-=~-i-li)z`PxUjijG{*dPO3h<`6VTkb*c58E*-4o{*7hqK3x-OBYi$QI_kC z@uY?5-3Iu$eT6IdRQY6zuKP9+_TKM|YuqomNJ=k{APQ)_F^SR8AIAyxPyt;XeO}H1 zYXerF{1!;4HV-1WK(Zv>tj5}>v?jE0-zqZn#>k#pb5}HICT?{)M$0g6+C$bCdu=XQ zVVo7|TnFnl!E~$^Lk^V;J4t~ehO=+WxZDm#^>+CR*X=>!7PtdY;ybEsqkMTs)DFTlQmE;k@6X0s$kX@bMHH(?j{4V!~Hg26wUd;jTJu>u)B7E$D~ za!0eT9)E7$945O3zc`GFK)^*WurZ*Ve$1xoOPM!Ff=U}%vKUo&zgyv+;F-(0pN>WD zp$FHQ|2p2BS>WtLQrlw~hqOQEh%8X|bU_k!M9R@ZqNXjy2*eU@XN21Ud!S7$ootdR z!<46k1AF+`1Pm_EOjNe9+5l{rOmWfVAjL2r_J0gGKD{% z_;G$W%<_7Jwl$r_e=n#{6((xPc2^T#oCA+Mz1&l;juq)GhHp>(Li6%h;dcWQU7C05 zm1cm|Tm5uD8ZOs39horuG_ZwaR4(sp44_}I(a|U;L-okr+jl&^#WF1a1 z_8eD31^nDAAN0z8Ao{Iqx%n4TD`zG#8U1(5@1Ft~WyFI1X!*@o?4Y;69auOe%vTbtds7L*QuN?Z4?BxjJwTR>d zpOgmDvTM`S4@o+508jUry_x`=JB7}M;cjv+oBW%eGe7GoqljbmEdsTRzkJOw zF@O26U7)&Izi%STO5B>m#3(nYVJmv@WZH+}8soO$ejx6s)vnywYcp_7rmlV5(3>De zS)HzkQi7zaq8nGAD|mP<>%MWC^9oy?e@i&r=+LF>+j#>cm*#j?m4eGhAJv!)BXePv zQ^-exNe;QSs^b_fIt$x%B=48gs>4+f!DJc^jhR`Efv(bzP`k9fTV5^JwGDG=kjgu8 z8}6FhQhK>3|K)=A9P>u5{T3Z;Q`S93i=bJ-S^Gq6Dfo+!XCT`*htL+VrKKMzdQMg^ z`t4T~_Rz0r;j>VQQDAK&jwLxKYtQmzHnX<$Gp6Qbx)aS~3l=DN5m2r;F!LmYZIu8GflriZXKhNi_ii><8rkqpJv^)t-u<lz44wm^wS_rqGWur;|;=OI1y9O0aN%3{sq?r zzjOce_;xv*m;7_+2+D+?4ksZFJkB_=pRNZvzuKD>=`3+r?>(2pxq!)?Y#_jct77;A zqs=4My{wDfo3aBOhq4})PT(@Jn=k;ot-xFgV8kLPf2JRu`xlw4o!~wstYPhe6?~3E zjdvP29zGXkiMo$~BmL+CxDb`LQtuijQ3Oj+nwJmedKwBI-)iQIIAh+8D9^{unFv41 zCqoz7KILDL(y&NC1ms^B#S*5H44JQo$lFV8~3hec_=x&D3rE{&}Zij)=vuPM$~j1Nz6E4$4{ zjXD67=@T;MrLJaJ<{chF&hxz=$H_T*_+#1I#`r*sHMBwcr->MK@RJ~c6kp= zX|zbZVAn;PyooT&3RDbMpV)q;-9D7~(~+b-C==FTSIIc3GLuT8@cbvBpi*H6sPpOw zqpanjPO!QDDA%o^^mkJ*Y{faom*5w%s1SQJuiWx$pW`arZR)3;srV18dTT}93XLj# z*~}) zW8MVsp}Ln!6=@iVf=TT1#J-0CB?RWN zxR8i6B!p;JBD=oT=YWyMI=@~yFq(OVwB1a4r)BwS0DC*clozED`%;5ug*cZ4OX0}H z^;tyS$lw!nmjDYoOFSwsvo;zfSBP5PF?5N98S9hE-Gu0K&;cxt@Uw_nF`fG9g>m}= zc$J&?PB@*(wqv)~@8r|V+^pnGQ)HsgNVf3MF%@YO-YBeRDgo^^5R_MedOy$o;RGpJ zM){QV9SoVx2dfY&_&ZE><)WW?Y}Bc+#=MHZ=iP@-hBMYj;Wl7dMeXH-CP%(H^Xi8}==G@wZY z6eXF>B1&3gCB3A4HG7lcG<5O>$LGaDV)Y7d(%G|;6g~CPy4aA(Y5QIl#^Xqsy{>4l zi#UX*!5UljhD`UHO2*vtPEs)S!x=dOm7^dGBT2_p45T*K>!Kp)*mgu(SvtGZq zTmiiSNx<08h8&^3404IQde!O-QSM7zUcqpMVS_bt#QNy=&(Mi}%jou2$xJQti*E0f zT`f7%JM}SlUEHSY@QJ{-2NW-$bVKg_EpE#qB*$-sr}2rb(7@WDvf}JsaejXtG+|{2t*Z<51LD<5m$< zq9~;>4B~5;J#DYrwP+J)%Lqy@S?RM7>ks-y$FG zIol&1Epe4gzAu4rT&heo07=41>r?h?sYWS@sXibRdelD2ZK{4dRVy#UEv{fr;U<}( z9M}Y;_sc{WQ#|g>b43CS!O z<>!7X(}8Dc5)?yOezi6Qi;IxRYtaV6USg?t3wY{Xki3DMDWI2BOP^Cj%BnLAlJhJ= z^4}@Z%VPm)yl%@0ftKKu`rHy_-r1Q(9Z6-{DN|eO979Z<&U8r$hR#Y|-!%uyqL)Q3 zti4n9ehV>a7eV>U6)Q6-8^AT2oGui2dby_n^lDd{-VyZ^&Ff!76I(;}IAl>eW8Z zMze?+wE?y-Nvv>HQg{if^Pui{XPjZEdp>(7s*G>PJBq@wocXEWM{A)6VEYt0&Cv~` z5$FQ{S7#BV4FKE|!C}fG3D^G@gQFpv$ z{3i{EhMc{1aJYYa2fbJ1yaMVndIaKSS=r1)|egk;&BL_mWN&J_aQs0bTck zG7_&QbLZG?`7N~F`y0CVtG8qu!fJ6z^>+IoYK|+ff8S{EBU9gNh2Zi_$|5VjipH5@ z7j@s&s*h`5=1`eQLpsGMC?=vz$i~2l`pm_0vOQda*Vf)PH8Qp;AhwN%EscliJH!<@ z@C#@2_$-WyP2tueXPy&eDljN*)%GpRzh(n7JkE1ge?JT{+qm zuicZ$MTR%?j&}$O!vve1duLqrwS!8?7>m2SHxiumE5)bk{qdQCJFc@StgL7F>8Y=$ z%rvaR)jn-xcP(C%>E%Y0RD8E-&vw0uh-MkLae%cxRTVwD|CQVqd5K}pU&z;JlFpS8 znc?M-BgUpPNej3=T#~Du+Q67_MJ=9>1=|V*%iczt6AX5=^D(Ma*ROpiAYremDL|!+)eUEA(zYZ=?lp_^pLGecgXt(ylcrojzWhPZykdWrO%fgcp8NTl z5`uq}iU|D+1(_=j(kTUtB-gtwdNhZ!`7+wdGlgO`!Wxm+9r6Vu39} zKgMOPex>;lRzbQA0yz!>HS^jl%`Bqc>L=-A6r*mU+y z-~HGOnb+|HZlE6p-ljpUMJkxDa{)}}D|@YaQWJ6^lDB3cE|QpY)R6;~hA$`Ym5~ZO zhYx#-O>Bg~eYzF6TY-CBfh$_f@o*MRI5g(Y7#5{oBlVk;n&?oMtJEb#u6U)IT++w+ zZ$uQQkX!}Le*I|_=~dMh>_*eA78z&siB}4Rk^6PzaG1b7m*8=v@GzWQ*(GV1CsQ1L zBg~WvJif;Qi3@dy)6*z`=kk-OQTli$;7~kZs<)wWkM`%slvZ!2P94<7>YBIV{g|@z=`r_ z;eJm6AoRb2(0(E=^qp!BgHWr-r~tL5Edf=c-Uz_&csv&f^ckwMQRA1DaHC)FBX=4& z$97J5>yJTsZ3BN_*DErpT#9#W#NvHbqj)}-=0K;q7{L88LD7Dno#lRAVFaN;nu*#$ z$|RA24R_)|*Xy;vd;5aAQ-NNo-}eAqpC(InJC10E)e+tZ@Ka|O=n>4qOhLcsZS~{L zPqqqp^o*|QX zZkLB`w5MpohPA%jlt48IWp`zAsB%^wkS>C7S!?8`y`J0SX2Wp{_0^zW{p=sRf{-Fz zmLt$(BZGZlfI*f-*!b|#>9N*CRX?(7!G&T9$&g;AIg;+SFz4hT6$EDXQnwcU{0??H z5fL`t$MOP<)Dqbz&2B{5H4vNm#ikWxdR=J-hqh(Y-^wFvn3c;4W%hNrEOv z`K}N48IGciFj@A?Va&dA zQW@@2D|{}65;HnyXtCPIxxO6^f~%d9nq?c=(@XjmUUIE>MXABk)!lNFuC@ny19Ybm z6bT5qWrP!bPu^^V|*E*h{I>FoQq?T{l^|$98(NIJ%H&TD7i^`&KBzA`v z!$_A0OXra*G=rAqD?vZK1rb6d?RmFQ)T>_L#C20~yN%*j^8{&%Wa|tk@Kc8VHz+^= zpdzbeJ%2zB$jU2U3T3Val`=% zyH-^vy$B)PG7|3+ntA~RM$Q2y>7zVh>C zaaB^LqRg=P-k;)VU^omZd53V6AI9_9Vwsai z+NjR%hPR3|%mSWfa}_@7H_*8cCg~Wq6^j#vGE6-jXNT6WnY^&nU#8mDN{hU3IPe~` zK2kLB*LqdxRd7&%*2k&TEZLSLCD3%BFB(vHGZ{9m&Op>m@!sg^OA(iiL zT*azu%GHL^`F!F>hgYySEbo`L3=)&WbwJ3Jt-RXOPgN>I-oAVgZ$jxti2cye_>DBL zWCXG=q%G6*P*fFoN4UZrJE)(+wu|$dfjo75Piet;E%DMd)|?9@CqVln%)C>KMf0MA zM6mBp8e(isgkv0#Sbh?h;tGF0c%bo}tUsMfW>N)3%K0j*g}k{l2{kCgYdh8=AKjbu zbSQdUL7FGCaJV^_>0#+P^JrY1pMv5s?%cg%KQ|`3^jeHu376Li)6s_haQ`2n*JWDn1At3Ct;) zVf-YCo|HNKl-`9OVa>JD!@<~kSE!00T;4upO{gvhwcZm0QA|-{50i;Z%2*Ga@*cYJ zfbw&YJ%~9iK6xS5geh1INt)dhOy}CQlsX+hNX4>_bE)9b%}7|w>`+(e%A02&O#aao zeUhiOuiAxKMCBesomKOaeRJTSb-|5obVl02>h+2CMQmxPn(}MjMjYeZeIs zrNSo0RP?m#1DqEQ%{z4CY)ZDf0tJ_ZfTVCYCsV4+0*l#?Twzis2Ub9FT=qRzL#I^4 zPs3e7o>*;%k){}k5k|<^>@Cw9RrpusD!KwuqP^$Fib-|ZWp4J%%WrflrtN6_Lr>c^U&WEHLt7dt=8TmMGZG5igJK1-)m^TuPr{SP&%RU3*ijzgpGh| zW~^sjcx{Flp4Z&A(s(P4z117NSQ;yxdvH-IgtT#eoT4Up)URTCek<4EQRNGEae8yY zvr=8kD2=?oIeI&E?*w>(}iyAbg?>|b;chfL?>=rWo z#W|U`PEXEh)sJ)9v_379`#@61bGFBK+83dRO>tZ!F`zwva_dE35)B#EDs!F?vX`z%VxCnL8&~6COnz<_|{q zg`X|=U_0WW>IB0_2aoP!ReG-VMtP68LT8r3a!}T{byc6te_=D6uKGzdzbXE!mLvCp z>}Kdh^IM~Sv-yEm!Ms(ADp2*K7Hy~oFihf+^p{bJCFa7barXbc3Li7HBN zZUa?67aLsu-v9lZTtEi@_&=H;as>tQ|N4-)mz;~#Bhik>ZjCtPty)p`gTkZPYz-)E z#!XX3T(>vO#@{g5qh@K#T*pN)`yQ`ooze0poZ@Z$`!$i%J9t(MkiCIr%ZYj`Wk~f) zE%3LEcFmZeQ_ss$9ElCZ{U|C~wGU)BOte?FWh>@4Ud(TXAwe9L(ERqwDJq3L3;#Y2 z&`rDi{5h25d%cR8?bXLCxjJ%v3g|4v*G&0rMz43V^@=Hk)3T$36e8GjY!}P=LQ31O z3Y-{F;8um)TeFO&*M-d@L{{vZTJ2KQ7eUSlmTPRg_FW^QZuvPpqXfd{GttY&=9NrQ zGJxiZhD*CCrQw>FvXR2jnA{QSK`E+kF*VYz+2a~g_)LEVBpBJg@O<7a^(yuJ!H=8y z51bUCKVzVMd)did^-MRZqxpi{-p_$I2G}UP63&sj^t@2O3etc3tEw5|uimjNm#X)~ zQ8{Q!E=a%7jTx!37TbqQktUTqUk{u?-{V983r%Qdmh5|w-pX#5?8r0SC=-z0M!q$^ zBgX*YSI~!-WwOI7ewROHbm-K-d^O!~?&er~cA*@&(}$+j1XBB6zWQ|a(#cnKwfUUqr))%ij%tO+^sg}9r4Dx3H}-R0 z5=R0#^>f%oliQGf4*RPN1&v<#*KVVh-GT+}LW=$YDMKc5p508O(@@U# zD|t>k3(y|GMh~i%uKHzc0Eh-bEJPr!2Q(<)qiIr%xsefnmU^t~e(Z@ZFQZ>!LGlCy zDv2BVAsX@E(ewv=Vd`oJ!TQNsF@G;Za1WHBrKd5FnTl)DO&2%=pRb*A$8_h0=>Q!w zxI4FCYvc!SgG7(zzjr)%-gFFKz1Z<4=<1QT3%1fgK@a+9K|3EMv}>4sgFoC$9{<8U zSa^sID(|l44ABA`kC1L8u3YSD+0EMhLuX1km5tKi(=W);d^RnB%>|N*6 z@J#84!}$)uG|YIyTJEPg>kHJAUvGT5E*iNRR~84FPQueW^!(bPh=B`|J?}7Km4sU* zQd>8)LVRv#>caz_8%!x;_8f9&)A+)R{K1_7M0lKJIW-GmLkBxyP4T1)Kf_)*qa=!w zbJm&+1&e>}GewvpJiF%b0QoMCqKSYu@^UHuWpg!O4_w4q!V?@^!+yyU797$Mx6rJZ zQ|xg23V)(wzuB4U51UH|W4W6=r$g+5A{J2rFzg+Xz;UeUm_&TwU+khb3oktm3H-RA zhCDm4V-tUXb1vySOU+MSsU3KwTK#Y%ytc2V30#l0zHw4i)TgI%H$OEcRiY2r24NB< z;|pg1&fwtV9QyU6cPyv1v#2fPnXOA&SGZ&a4HRz!OJ6k#4-Ekc5|0`XDjUv|!>*m< zB4%L*Z17-o;J55#&@)PJ^2J!TzBaTbJ-bHfSlA9LBl9|yQ_ae+@q>|pu1AVvQ~6K; z4RZ{R9+svCcjBV(l9Q8u(I=<7MN%A$8*!Q1LNaEzA>Z(2+ivzEloPPW7&DCt|5V7n z;-KwiSy5^uJC%hz34IR5O%rTTm%w?Ql6D~~AETJzFiBDPByr6^@aYVK)fu*l7Xc{rRsp-*~Dl9vtP!)SfQAfefW76$W|1#<&G*2AJ@PM*%MR;}R-mRvu z4QXex8Bo6vewsNGo#?s<9$cx(cwdL$A*qy~DbQtHb1-D^`HV89`Hl(UohfbswX#XM z17A5YT~cudqET%;Zw#E6Z?-_$KB(v%CX-AgGj8uwX--(W`iwi?6?AY|utES|X@!mh z9yFH3nht2<0>^>{HMVqx(QN%apvitB$%vv2hb*m4sfk29&L?fqI4n5eW*Lv)DsVKD z%uz!ms`n1IBGgQgHvvmx!vhc!I@))c1WI7mPooA8dt4&x}czjhxoZ ztCGGsbG{F9*&vLfP)_k7)Q()EHc7G>7)p_JlFYN(-TDWXTv>KeJd{J+%4DeWFjB98 z`Jk>V)X-z!4A(vVP75})i`PAQDAnRJ>8h^ycPAhS2N+YASNM9)s@$#<8NP3$2laVY zD{aq}UvP0t`RYl1DO231#`WIc{|{iFs^nAL!A`Jtc}RNhYer@zpOh zFMl=GH!roQd8=M&Mp(VoPd5HGTczDmX%sZ`u8#%S_2&AJCccVQNXISS5GvzDws8m~ zhxUQZy2MB&lVK}G)+j~RoApyBT71qcb9PXal*v4wGqM!60-{ks&?fc)&AQ}+8Qj>4 zhc$|a%}UWZO(Ncfl0EP+I5(CzAaWw7XO^)7DJM#yV1lIojRs#>bI|H>+gJk&rf%fV zXu1`4uN&-wd8pG?Rgtp(>Pu<}sw?En;b?8p@5@SrUClUOusU#~v<}&dq_Do>>*)if zb&2TG;k^~mYZTDKB<6v2ZFH5s&co`k4`?l=WGV>!Fh(C4lao#{J~g_)wmB{ovhS`b z3`ChcA?M2BJLe$?t1?GtfVofy3NY^$<|2F5-^@Hc#i%^hvZKIWK<@9`l(g@ACn_(b zk5k>W)kU@ed7N|w%i9L)H+8rP-Pj1|oG9ef8ogaRD#Ws5BqRUt&;MmFZn7pAQ$K#( z21k%6ROoyhTgMPqU}Vad7~az3hEjqV*3k$)@Qk*n>^vBsNYmWq9KD2+nD7AK>( zvcMCpxYu&MtXb^i3^YNKq~SPh)jPVcn-niF6)n6|i44&dsUi_&^l<1?$C?q=kZ9;! z;e|h#pK)Dk3()$#nOxZmt(0~_sRXB5i3yT`nc|>t~KDoe9r_-|BAQbtDR?9ZW8*vEh!kPoQuuNv51FH=# z`nLL-a3TfUj}G8G<|1oXA?O6CGhP_R&v9jxZ~f`x6VL<`KgGuHI9|!w36wDHr_@@O zF3^oZYCpd4@tGo=5j08Z zvHZOxZX=1bI+*5}yoWoqkYH^?R*SRW^# z@p{YW(n1zU2hGunp?gUWL5xZsV^LZTQG7`jZ4QMu7r@ZS>cmJL_Rt^N^N#0;qKuBQ zhXY*isUoY0sjB~y7C8y&dd0jtICBBWwL2J9akXV^nE27d<-s;bVx4;}ASfy?8ZJTx zoYh*{hyXSS+vS5UHxR*{bI|1Oq)9TpBl6+iZEWmDpW#|=T~3Llunis3lFgN-Ad~zt z`NL8Vj(+Mc-EhreJPh1PCX_5buWz6UTdQkUtlS4oecq|_4Jexhko{JaGDWR^(=38! z#lD>|wWa7Uf}W9V--r^G18c1(_lLHdO)gwFa%}p<8sw*voRhUP@8;GPf1v2uTs^(q zq0Q&=yZ@Rh?fhNeMpmt&dXt*ELG0Z_a!!`6a`Y{&{WVF>$=Y>Mfu+SC zD0)s-FHi!DU>De5q-l~`dWa(?i-|2qNY=@-6fs4MEIq;_re<^H^s)t2%8CkgP^2kp zIo!VdD$OufQU2JdQ4ZCosz2c<4yeUTxgz;@H5 z*jkT@QAypxYP8V^>sUc6aD@t-*;tSQG8YGMS(3Le+w9N@X3o+rxn;kpCees*6dBls z{0EiF^WK#L@EnP~d3Ja&tM!aTg_Pbr_b>Kj!Z3-%JBP;y5ANR?2HEZ;Ngg}P5yAhq zi3{-TDC;{gN<%;|x}eo~Wwy0G%JSafJT^Y_n0xICo~a*G#O8q^gxbK>XB>83Akr$S zN16%UEo!%q?K%`21J3G-tq|etnq2o}k(uqoC@BKE_`-%RBC3z52xv#>1A(9W&R7?( zet&6m-iDke`pSu^I+;JvVP_<1Eh)IQ2u)zq6Bgj@vY}8`4D_f>uIErQpo*-NEl7qh zEzjt@fmdUi6oW{Bcu8E&09{w*2iP;bOv{fj>{%9X$t$jjFN7I%D;#b zKUi_cInKKyKI4Va@6|?y`a}Fpnie=@6||!MgLXPCVrtNBGz@0Y9m9G*oUUs4J_jw{ z_dVb_rMgrE6NCQL-_t-Dgz2uwl|i#vBn!6gj3#0FuYuIg{Ht$eC=jG2QqrbekGxC> zG4ea`H$kQkxd*bK-pLnw18=e6ghwS#^s9kDO^}iiq_nSSWzM9y9|Yv{ZlMJhsqMM* zxO*T(n8)iUAuoI^01kGg8JsAH;GeG0nFv3)nrYULq84x^AppWF%Dg+3vPQ-Dn8P7E zMIngrgL z5A+CU=%cg5()u3)DVuAim%D^`n%qzeo^R!|%_YgS(G^!wy-6qU3wuNlX|oivFe zeBqP}bIO&~-{{I8VW#^LsH}h1zCxTThJk2c8TF&uw3baj=`+;PV2l%!u-~WIDT-RS zEUU+8+F{1CMfl}~LSpp-Z_=TA5#u5#N>8~JFm}1k!Wi33y005=Uvu$V7z5Xaexx>n zNIozJf6Y6igkQh!Rr!{B53LQ)lIcsZu$X_!;Q30;+@v2x7Uag%l|-%&r={5-4BUZ4~jY9Q9l67D+agq=a;#;CcuoXs<*v1-b_ zE66Vf7^|1K)seS4@>WM)jZdn)3abI?>mqwV?XSw#BPAHR$&D*Ck|Kw5+wxk+22Z$E z59F8ZqHJjpTw~C<0@KzHvn`dEC z)_#;=c3W12oXcMh0-`QuJS7No22#yS&P>o`gL1b_I~3HCpup~{WTb0ef)}I_=Ipph zDYz*ku{PN*rI+1U8r2!=(#WX1sD7yVDON$fU*_=Po6T)`uv5C3^>=4cqn>Iq7G|yJ zS{_c{PmqN;(M@Gl&LtGWTCo=&wc9{3qIVr9s$Z1NfaC(rIdaR}m^AJ5+z!9;w9%B= zLVhjCSEI2tnAUGFRk+)e)A*?H5PLi7@qdOH#W|_vJF)_Yl%gUNW)eKIwlTra=g~IE zIUwO5t4>F92}E{Oa88JvSSTI-2EnI4> zRTDC``FU;F-7bpA+!OwFQb=hzLiU)<=Qv-_Ce0^lQj90uGaqxcxd{|8WoaMfRqCca z?JRQ=QEvsTlf>eEp3S74zX}%oZTzjeMAi|kXJ5-VGPhGc@PR>ffb2@txntYJzQ zf+91bM2mlf#W9v0e5<7{!10y6?jVip00GyI-6yG zDwpKRHKEZ%Oo=!4sE9^9HJX}`!#S?VPBeM-z z-={_1p+AO6g2`i4U8cKk_vlJ#LM|8nbQ3twyjV>LrT!@Gq>(?wBkGrxleMda%lDMT zbZyl5L-Gb}3Z8QOx?EKiN!?dMj!ZIU-B*;Y9+0D#d-CY5@Fv+FGXAmgEj4fRE6uy3 z-s-12_ttZ8C7HAV^m(7b?#y8uqJ|Z*F&Er5jmV`kRuRleOQ}8gbS=Ev@@O?A--wP8PoH3|uaL z+ZlMpv(U?iWTlr`^tUR$UTh#Pc;HnJOKz9EQi0t**0GBCO-kQbpkXgB#9nlbx~;~3 z0{=e5$bi)G<>G2DcjuED#fD-F9FkgO)Eugo8q293ax_&D`LMA_joL=BnRcb^WgHD9 zYQcGVQ7~A&xSMp=tfWd|ucsC!1K8`9O$Bsf46-Cbq44PRSjS8nI?kdIOIsY2979~d z9TRma0bMq_B45?UO+mI`vV+LNaZDYNV_k~zDbJ6x#KV@2`0YGHE=sr+sZr3vy^+uE zK6rHR5Dn&WlnkiLV-bgdGWlD|cYQF?;qJpkEbh65W4746QvL%LTI7=?rKrY}vN)s+ zme-#ctteQ%oTGNzn-znIbR4<7N2m8x`#8!6;nG_S1Q-uhmR)$qIjnjA-aU8?`G_5H zz!OHgdBYOWgU&ta%FHzGAnh#NvSHbM`yRFDG8v{5GfRQP+?py+(JCkM{`QTH$z-vtUABE39cclfJ79_`m5i2ySEhT3@e4VIqIff8vJ` zUd7}DW)~vK%{Y;Z;awo0<##hBi)d7-1Pm^=aEO)6vk6Hfbw{~kO#JWKxN;vBkr#L^ z;A;gow#M~nc2wz(bq`7eE@)Z?v)`21ybLKei0uzV|BARZ|Awnvc26&kTh%D71XP z%B&b{IB1qdkj5>7LbeSSNSyyU_0z`!KYQD}nrneIooxeRwLlq6^9=ginc*Y}hhpg_ z*oK{M4Sd2hDP|_L4Xu7PIv~s5!v4Msbgq>EEk$U7@WdaihY=S<7w@#CR|a|KqP}K+w%rMOt~vH@AzLfo8{L9aFgh38hBQ{%G)W~P@|g@L@C$}zsk=rfO_M*6 z@mb0%UkWP~;lyw9(_q8K`cffPSL#RgpTiLVpox>At@?5=()ShZRXlj@Lt5Q$1zD>E z)?V&)+$mBnB_fs74zgkbxV{5y(oJVtZ}Rodt|i=t*B(l7NNsB@gLqJzN&)g8i*$gC zebOe;{uzZ_bB8=W)D8YA_eRb2(A?k(M#Hz-h5_-`3u>bm72yu(;d~l_V}PFK^F~rb zcZj(|OD%|eJlh%kNcvKS$!BPuW;U!w|5)Giu}CxsnD4zQ@FX|S%yOuGTE0EZ4VrNmlr{ZQ}BoqF5b*-E)O0(BOE#L zR2{lZ_$k`Lob?PwaGK0goUGHJ7)sZTvwPsJA`P>E!vdvd08gQ-b6tYANX}w z5R$FZ2Ebmll6`rVVs%^f0+=M{3ptJrE_oS5A;&z{a1{)b*+q(O*Eaa|#DHj<$yM;| zUs_)(BGx%%3zvC+a~#}3t*>;)fS!FYPbs6zK#F^?4_HtbNg0_Mc!OiMsA(W8H%5No zx|Bx}{b0)Gn)rE9FYzCq3V8)5HN#aLkA0$(SB zy+YC)W#dXV#M7U|rMSXhj7BU6Pu5?+TdfXRqDYt_@qqGkkdq*pV(fWlIPo203WjBp zW;X@XI+BJq#t%|)x4=WPYDSkEVV#NG<@8y3dF-coIJ%;j^|ba?yD&@YT3svC7mVO? zg}{#YR*{*#@)In62MyYAXDAnxD|o>~8rOSPyu2LNhy0cKEL`(fIsl6*nOv66e&s$X zSv}~J&>CrS>NMdpUQia|^7}(onMetPl{u=C#3)Q9UHBW5n=Xie6eV+XD0gZqdJ!)4 za!(aw)m=YAjmroY>c18F=C!xkIYp{8W2%u?jU*;rG4fW zyG@Jlx)XS9wcdoewD|6VYHBT>njz6mw=0hZwj5Xie`hN*J(`qI2~CROpjYdg8^O9%M0Y)b}u z-HfZ349IP9gb%In;aWk$yM~q_p(yKmFbg*Co-`uQ;g0@>XJAsBDh-QXPJr= z(y>5}h5N)A*f*yxL))y|dW<&go7d_wlx*G1I*i~75%u*Zjw?;_rY4Nb^=}i!ub3$A z_7WU!6U1%6cvBO^tD1ExNb~hD%K(+wO31r4$o`EAbfI&zkkO}NcfUJ2ytT;-a@t=v{uN?xXZ1awEhBas(Jk&$}WHkEmmS8vDGFt|8o63A$Ot-1b=5KX?S*0@TQ1*>XWmXxCIAR6w+_3sB`IX-y6|I+Eq zv?2D>s+)FaZdYD;x-60Xg~w?1+PA6a%~$Nkq@F)f4AmiZ^oHf2>qlN=GP=2jZC1L) ztn_Ano^8|9J8lVHLaldWQF^)!Vbdv3T`yppu#T*?O<3P1thY#5*T-g?x!&upTsL#Q z+#g3kZBy8{DeTKq*tM0qO=WMx#(85ZyCHSAIqeIe3$B#YUaFrrJh@#z$TrLUT3PPS zFU;XK-Q5O^H$2^4AKPu-`!?_0CV~&k%YU2q-Y)OGbii&C;IEkgZ~CyenebQ1gfHEY zYe^nka*SC@mRIDqN%QLM6@JWZ(tMAo zUM243&~4KEbury0&6~f~0p=GYX&#Ykqpa0$Vax!v3@EB@_+xmCwW=mpy8}u? znH5hNlPaZqMTRL0>)80YtV+*5jCHLAzb>FM5)SJuco{H%} zq}iP5ET4DJyI(|kIO7P6L5b~j%4tx}H_pTb%|WK*W@3ubonbHROfUD8AHF)zxxTaN zmzp1A)h<13(3>|S?YVhlUTMZty;aXP#*&;t5{n~V>owDjuNV>OB_^j6SK0(anoi!Z z%p_t9s*E=YseohtZ|IZLH^xhO7sFJ2oFKW6bEDel@&oK*NRuO0${nvDRRoRO%)3=) zOT{XzJQaa?lO_YqBWAwd@s6Hi8Z4&~-jbyF(vUBg8>dLWcK7)7#0Mz`#14Mz9{KYR z{oFt0q&i%av5-SJqauf;1))EVlMEeUG3Z9hjv@!o1sI{XSMJ=>t!q9AYV=0yvG2>t z9HVV8Jcs$a0Y7Yk^1#RmD5$v@meh`n#d^+dBqEU zJyM4SCk<<9rvAPjId~}4n3NRbzpg8~Ffr{lVAZo6eyPnd(~Iwmrvmu{fED_!*j=pPK@!rlWMXo61GZzRQAdH^F*X7dT?WrN{LelB@ev-4`lh3UCl z@UW{Ml!B~D#!h4uOr-9qsA@(o<8q!D+GgyV1M}$!>!l1<-SNt8-|-9tmzqth7I>2? zu^0G6{2casfCbLHjkLgJ-dL7V=vMf*zpC=Xjmx_yX=z?a_NC;srgnEYoUq2&=BZq= z<{-4VY9!`p-G02AjCL(!j*{VwGzI+2jc6|)#p+g6A0`N$Jq)D+0ajb4n z{ekdrtB=(2B5u;%uXzEVsLPk^2XD$mOFdm(w79D~5{m7Gy5vHgzs7pLNULVx5!c$E z4Mhw$dQ!SNt1owB8c7Y0$a#`J9z|g0t?um5v?UCIVWu@R{3a}aHQk%U+8?W~J#xP@ z$v$2BXxxydJ@qGEL(iUuC2U*v%m;z5+_GmWGtagidphn2dL&wJM!jQ?ML~0|hCTJ) z|5)_vam1MT-?edN>)tap4tT*^&un%_in+bm%!U+ZXQws{-cJyF-+PtmO3{Y0R>qN7 zHY`+ZSjfpzF|b&vc5qgtvm_I&_K2Y1t-m9XIR6uC8U=p#wkaPQDl&;K@OFU*!D%U4 zaD@W2wYCbGyZ3Hrr;z${Xxe<^;b&e_--(hj-9u#-&r7Qlv@9{kjqfN@7mbc^8*;J4 zRaE~z>nkMf8`n@|+sWbPc5;}zK(~z?Sp8_-$e}?6+4ga0)5l@XI(6B`VNExV)I-;} zId8J^+t}Vh(aP`%RpgzBw+N&x8= zIIgyrS>QYD#ehCOqyWn>P2!6wMcQydeK1?o)8m7+dGs3V5a%FgSq1~`H~c}31IQ)p z?a01_>;ewt)D<*q_4C)=Dh!5C+SqxlF0`3TALP|aWqVR$$%8mw7TepIS+9WB8rH{L zMk`Hkjba)ZFANmFY1MON1k;_iFyY^S*iw9-nxD51j1t-Fuu|44re*Cui)v$np){vg zo-y?^cA(e{5q+1s5$l;74bpmT0O_2PW`pe0VpYmXUk2HJ=?AW*lDIxj6d4I)5gf0u z{txCq=o~gB*4S@It5K?vPK7HYc1Amg+82Cp0LzP(R|xTnu8#70RS3n?>KB@qzY6FZ zuR+LDwRQzUs1zGYscbs*zVjo$=-OC-PEXgy0?l6E6%wL8Kw0kzofo7Z^Rv1E+J4o) zyzp}AgBjaPFUN}RQd#8|qxBwG<+uNsAJh4%YA#PGT^FH`DHC#vn!GU97{u5N`)l8e zl6VX%M#F8oMsQ{+W^8a;-m;ii-|!q(QK6JQ%p%VzlW-tLiJW1mB4ZhLn{fwIlI5F- z(})HENR)VffC&=V`pzKAaxq(Em7dXi&J9u07jRwbph;Z%fMmUw9|5e$H?MsqSU3IV z+t|4N8yvCjk@*_o@QZedP5l%MaJ8*0DDr4AN(b#LY~O@}zEKyk#e?z#OQ5og4ap@M z72tEv6bd!tzg)qhX5C_MmPM&n=0}{LnX*ZlG5Q#50!W;jgF$ltvEVvL;p<3HsYt2Z zLzFc9{ z8bV>mBNzG0d!N|*%|5uX37WhfxOC~vFhiF5{Cd0I!RezzR{*RvY>5C&s$n{cV9xV~ zD>&E;Tb^>;FD7qJ5^3rY26^honOh7}F%)p|tQP{gh_!*RDL#}T4K>L1)Ha-VP&zlI z=gAc)`gH6MS7=E+>)LNl1nSySO2|c_;vEQNlP6Rj=i;icBJk#K>Q zkm(Ngt0Pp7Ett845iUcK-7YjEx`T@bURg0>*{f7uR;+a0%ml|yK6Y1c zxhaYY>+z}Z1M20{lpJlqC7!}gfWV|Fwz3gI1Dpvi-vh&lFE795*>HvN273Cbic$?# zMh6edTN6Rpr8>7Mts)m#^Pic2&X8HLq3E^`Z)VxU2_V(<@<^!RPV0-T{-;R)}Q>RI*?FcL(T_^*a#x?BrMf-A6zB%V%Hydz%tt z$xgtAfvhv8kJ;LkI^hnWSSJ|fafLVaz=t+|RyCyFfBs;)ikreJf(Ma~6Ai<;qE!=rYE9goK zcl^VDc>AM2`0_kE6V2WYGlL>^#*p-&Wv z#k7#w0JgD^@h$4Sg)=5tuc3CH~+2uRmdiMF>xqRpC%lH1|!+-cA z+U_MZCer&o=;TFprZ z-L5(0oL~Og7oNZOm#+EbRC#8t^NXU~0T%ldk$+CZ40A?(n|$=ak7#99`3WBvgA8^U z>+#0EIO|xZxlC!IKYu7z=#+?1LW-i!{tX_)!O%rM7b>#z3vR zhI{$$_da^}dk*!TSN$v;TH0`;)d2t-G;o`dMt3!XR?v7g%V)-kaT5Lh9C(w~TgU3P;Q1E_>)XU6Ef7P4&K`|3x(`1a+O|IR6MB(jTh zba5!s@QDac(j+K`wnYBoALxKq2g25XOM;8DJcXaDO%LvZl}WQJJeFwTQA0X8&R#ea z(Ls{Vx-dE(y8A$5Lc1pp1rqG4WgTVmNoj?J=(a@iF~0X#AO8I7&)@(0v;Xl0H~d6x z7;G^T5$gbf6(YFRCq7)eosN2@h;&&BxeDL@{b%3#Qzx5w;LrR)7=<|;LD|9A(#|$e z?f^><6v@v!jQrGRM=z2eT2O;O`n{iDe&ySj-~Y+8Z~c*@Jxs+(WNoCTBG&;1OJq=@ zpM-v#9EJxVF*{JA0}lT@6lY-CcNOAH>7YPn=_N1;nJ zyYAGp?|kq1x8HRbTsxTH)Wu`Cg>3DUCfETAdxSDNl4CeZ9VyZ7{K?;a^s|5b<@f#( z_Q>(>SPzFck?sJ`wMbfnDe^;W{EM`@0@0C%^oxT|H}zM7xcUH`3ZgU&Klo|v-SJ-Y z$!_aFl(fUY{gcbL|BJirwH$4!(Ch$(O?zS>Py$&wL0Mr&{!vkYJN#bNZK6;QuJOEBzoOl>UaGp@;^IHczInFmJk2oXP57N zzxzASfBOFO_Z?XimAR5@IFIoB94n|NO_7Uw!ZLJKuEaD1k>v zGTR#|DRzLw9wmAGASD|j!p@?kTtG@TuAb-bf8p|LU%vdOcb@R=!@)v*m z>|5Wx{E3?=weHVpk9mU5htd)qmN-Dij=bro>y!`#Yb;TrDcxaDB zkXnbww?jp;2NvpgQ5xi?;$rNmlb2un>a#Dr_9&G`Yr#5G(n|VM)`W$i*-h z!Eu%qwm7CjvjZSDX;G42nhYX1@~2@Wo^;bBDANzgeelDn@D4xt5w5b6^q`DCxxih1 zYClN4JL)z4QitCl{pauh*X28Z{QQr8^2=|&`^!K3mLn#65?ZSgXJQ>tutJ17LXecf z7A9Rbg&g*Z(x?L=a_d@I5=o-(fARUx-Q-d6W>|N{*U5GO#4;uW`!C)4 zAN>;Q*FQdIf_S4WC@Bi2}em*v~v{%2qP(zEY= z$>H|fwSp=%I{;#nRxJi>W50~}@1q>F(~Le~jl0g02*gWX1orvMq1D9u_b3y#%aP^@ zcYww^!4m5afBuW-fAT|z)kBA&KaRm93x}s-mZaA3u958kj%9Lmw8aRl!lb0y<)D!5 z>CbRknqaqlLt=*?f92UX{`%Qh{^IhFztVveiiYEOl%!M9_GztgmZX2*kHeA0>!L=s z3pjSkk=h{1blv`E-}utA?|uK-KYVdjw)pDpS4&FNqq^OGI%~pSGqO3lUEr}ykJW|| zCSW1X?EqiRRHtB)eDEV$oFdtQBG)$X!-l?%HI@u}`A`1^t}jRYHTA>zRQ5Nu##|=R z4v^R-#?>fAnkAs!B~!2@yKdJn|LpfK|MX8C_U||vjl?iNdJ+c6#%C9CJ`q`%S=zS+ z)H{H5U8>gbim8pQNNpjc=yk=!0v(iTpN41dawf=S`jfx8{ObE3{o+f{fB1(TNO48# z5Ig>+ZtljTIG`SL*7lakb-}F3wAvlb96gEVw+yY z!*PrQDQskuNe9PqtS4M_AXsy2(9`=?Qjs{7;Mq@qc=`KZb?DtN_s?o5c%E99r%lWUf50oV z(<(+U&1y@MXaD`}%ddRzqp$q#vv=Jj%2K0H>v6yu)eg|urB)j%z+F;1&@1DA_{L#( zf+Sa{&#J>p9%Tku+k7V10fyFOY(wyAKMiq`mt`mvSxb<#B4-Vn>eL%s$W*Cz1;!dR zDmRy84#T5NOlO_w(N*ylP>JBCILf*&knV~`v{@%0y>ZWS=2nW~2^wNXT zt0D%GXU&poAl(6;9!R3@fFxy2?_u%k2S182m86r1lYK}&eE(Y?{rG3DLV~Y><{Mqy z7dVUlZkXkXwQgh$lskZ9pCWD|Q3+FlG*dzJyNTpX(gbN7-4Bs!LR3S)iw}+bU+@0i zvv0n0`Og1x!xoW0w#5fPs>8EeAVJQG)Sn5z=s*Sc_EmIvbTT_AeDd8dee~{6ob2Q4 zi62?oMSJ{kI{I1{PGDvCL}9pJG|kIY`A^4Tys^FbJQv{Lc#%T*l;AlSW7hpE}~ zw_bkr{pa8K=7+!d&gJj?1$$i`wy`~)`r|MjpXEhh%K$S;c7VnrDYBb^hCq4(q(TQ; z|mS~H0{~qFr5m` z4uD*ZmOVtCB=#&DNv$&qtEt#}{(IpdwKtJcQtSBq7N}%QMWi46byv6b<$GUuv=i}T zdkvc;wGPj2i3(-QM*dKMopJzr?^8Kz;?rR~ETRPdwLeT|Dih7?@KcoY|Mh={EBEq? zKYjjFHw~2Th1ODOkyMArw?G1|6eQ8*$e>b%G`cVi9lI}o^P}%AFSA+Ad8;4B8XqK9 zo6swAU7p_#8QM=E6WLLVKm7UIt6F=s*w$pYTK%%Mv-gFc*&8b>sdWLs3Ki<47};hSH6@$JiZ{qiS$>0xVE_4Yc?m+cm44V1gUXq_TgU&70_wY?VI)K;_1smSoXiX5)I=9opo z*pE)qBq*$nxTuru0+?NLvPX1~4?;@P8s>)(C(+3)<=VezKK_^$8+ zYb`k|G&=xdlNOcZ#Q6vRGnadBXQ!Th_e+<5{<99Gkckfbff&NRv|%A@`<_H%SVVq$ zh8|LT?T_Z@cEQFrJu0NbeyGFH*MvI|l&--CsXbX;CE5WJtHf~ofkrAc#SX-%5e6(MCSt3r)K+i(M_>Dk%P+ov`PKI? zzx6jxw*J8&6X_Fr$F=;qf*k&qG+#w@Vl2kUVgw^upcjHHShJn zFxJ)lM%rBf>ValW-M7S9RgE7z+ReleM`CoKS@M`et^dJHzF?m@<|%go$3De6V<7Ra zY*Cx8_+a+Ik9fr$Xx3Sc7P>P1eKo5+`}*4-{{QT~eUl@}c^~*yDj0E^nTYP5-C0VO zxl~w~?w;MI=cU=*L+a$Xa3rb{K<*T(u=UbC&EOvjo7RgRXX_-{)=3JVEXyY|!j^P# z@|{dt{)gY>g1cOPi+f%&v#NjsP(8>>pm*W0meT+#EAy96KJU+yfA=TbAhlOzfwioz z=}Zs10W6M((_O*K7`{oJ(bvUKe#k>Qp{|M&e)i+v|Lp(zjSYq}IRr6Y6|AY$o0QAO z!f_Xg`jDHT;%+F?jCd$FT_kugCxtyf`|%Hc_T%6B{7-)L`M+D;UTQrJ8ML-TJ%iB* z8&~#3NVU;7LFpYwb%oct$i!3@GRqx7R1LTxDsBeK?Lb+&$w1^B_*ehr^MC!IFVn(ZuY<;z zn}FePEK1qBzE*j9qRz#*L&D;Eni;@w=R(QpW`sj!5;QXjGuvcRR7U&bKm7bZ|Bs*l z#vlF6Q07;>ZCqtz1qaS_tzx|;fqJXzL z_f$0p-2e=?gU$JU*7ihNzfoWO<-h;zw|~#0%jcXj`tTEHVXFF&8^GakD6R{FPf?@W zU>wpdf9EfL`nP}7;XtjY9bR21K*LZaAUJ``V%D z@_PE?wW$ubIy$75YncdlSwq!;n_%H;AWbkZ5J37p2PCukl*p&_TR-~j&o_0mGOeSJNW>@2~&q=l{ol{Q1BA%g_GT|969- z$iD2l!d}Sd!tI?Btz&NjP!D6)uNZbnUgnZvS7fGxfP4wYAnr^icO%}P{g*%Z?9W$s zD|bbA7cv%wjGTHS8e?t%i_5WUzKP>B%W%|<<(+m?oF+6$wbA@)?g|_YZDO%;SF@k} z@!x*-&;IWY=9W8ZqAnCJFG{Kpxd9vwhpMhkI#CxYoG7_jKTzCyy8H-ltd7CV#>+G| zjmvSDjn97jH+|iQ1YA*yEOdXR+2X((K=Td*x(4W!JFNGH)57?U;JVwmu2FM&hfZTm zN9b%kR&H;;Igw|{HR=^(7kO~3SN!Zx|KMjo{D1uJSULQ}UBTkqs2hOdcr>iHC_j;f z<89*&`|02Q_~(D-OLXUHnK^TGnBg{n!PPLNvWF$;RvU~$N9n)*;PW5-uAkjd`I|wW z%RI-oxZ0D(pc{bUbTIB3h3lqoU|iJFHW-jtDUHXGr3+BdHjWSf{6GB5pa1Va{Nk^E z@YDbP*WM`L^je_@OJ3e4#YE=HRX$ql*c$-!ZezMa>N1?jsB{*CF~e<$hl^n_sb-9q zB5g1T`QZQUfBe~h`WK)50mk~ zi@XcsM44M#HyDwscjOdryWjjKS@u^ITxDu5ZQWozBB$E@tR1?9IwPt$bw)}s)CM3p z7=r4_HyeVs;xGR0r+>HUeVHGZGQ=y8ejP7ZN$DonnOH|A&URH-EV|m+%?ltM4TFr~ zSWIVX^8%U6*-w7B>2!!%{`9v#|C9f`!65AY=z@)F@^J2kTO4>3TwD)0R~71jj`}JU zW}5Pt%y(I2C3To{PZEnGZvc+_5$A{k2QOgoO|-+gN?|+S4TfAKG(G4h^W@sT-~asI z{a_oE_CmOdlGTUY01lT!!FIt2!udvh{)d11*+YaB=M%9Mg01k&k;pxA` z?yfkPi?Z9xv~)2q&glNLpa0>ewP=@Rbya{jvMC%U4chTG0m9KZOf5Lf#`%ISrbBGu zcYgTA-)wrv4-g5?#Z$7NUHZ#1jHL<(p>ow)wr%K5uz8muox^mV#G<|o%*0T|s!-BZs>zbkH-M+7QL*SpV0bDLQ8m2U zD2~rT&A}Jdo3!{^AFJv@ZJT zYNx-0|8pr_?Xwwf0~j0)!?#xCaypD93@6KtI^0na@xS^vpZ&&v|M?&P=4ZeA+Zzm} zn+<(ZCPfMpOEZ}Vj|20`iPWF@*?q8Rjj47)ok(5L!NS0sAa?fwodHD3b^Q`!R{$*x zyeU8~2b{mTnFrrsK(3#!u22Rt-*iZd=KRHPg8K8>pa0(8Ykm+HMRxEYhcxR)a zfFUK9UIi)-W-u)L^B_<~FfH>Un22lmfr6i9fe3z;PQpPTlHgZWbP%LjAm0`;!QETV zq}uVWp@p}Z!ZXeDXT}YNA_o|Ceb~Z@D_~oOWns{!34V=GYC?Y)gU$K1pv19KqGJ(% z?eW~+n|IbH-ZIDf=Z)PrNb+1x-_8JQTJx*XQ<=z-D$LYczuS~LU;_ z;xHC!3f6ZaRGfbpi0G5jT&-$84Mtfyr613*w?&?-WQ>F+f@6Fb9!jFXAQ#^TA`DXy zQm{QSdinxYd65qS_^18=N5MgJ4kr(Rj#4JOA+I_&Q;n9BavKHljxpdXj^7^Wy8yj@ z@%M>moTO8sl8Ys!XSu!!YeC2~Pchb<6rgU%>}vsl{3^s+uV4Avd+?WdYyK*eA?y)A zYtHvs^;Bd0r9@Z%dKLbIpiC4@JgDXs2%w>HBBP*4VRK+hWJUBy1eyR=Z9BhviD0zf z)sI_!!cD|-c)ZLTXuaP+>-|++@RFZ)iyBH!o|ZWj_WY`ZLlwTz!IgKx<~WTcUTBHE zNM+OZHUn=62(TqNPP1Etg!tA$AdkjJa6(0V5X|^`(%XZ8oyrG5fulFZTiD^`<)td51(LP25N41EHxm_3V1~EwcXBO(pGT2m z4C|~MMu4A*JUB~cf*NfH*Lm=&EX-dZ6&|O^P$>ge#KmopbDf1R@IiO#Lb$J+orTO; z8-a~bB6rg_-b+ALl6=9V3=E{qWOcA9t^$(j8~B9zvmuG$r!&4eHYaJTfh1XWtKiyxM2Wqy^eR908BF;=TD+U(})Lvb|AzmQp#WOTq?!(4$yP-I zQ8S)+lfOe1$H@84Wv0@o3$N6_>|r-(V2u_LD@c_w!f$~EmOfQQv05wT7rKEb%p~@F z>O!ay{P0Rm`=C=Cnw2%HiQEA9*OT*w1^f$0dX;9|`=LDp<_ByG4-p(F13+a=p`sxp ztf7TDYTk(FRyG*1FTg+tD(3;)8kftsF`k06PDiYP$U#${Ak%8_C0~eXskD8_^gJ{p z;)B-*yi^o(0ru5zgyU6|i=aA3LJgja+glcDCkNU?g-)?vj~voV2*bt55cLJnoEJO)f6q?$@TPEPyox!)%dk{>eQPc9E zW|R8b17K)UuH+x*{EHRa;&vArhCGQjt++CK4O1|(wS%PHGlDUbjGQXOVSw3#E@yqe zGt5W5^#yN6YK@4uN!EiPGb6LyGn2B`r!O={0&mvprp@++#xp$!l<%lMq1doKHTWj%c*!dLbKDWjq+5vq;{Pc_rrc1y(Xhp9wWqsin(ftJ68KeWHVm?wb zc8uB}n3Pk2v?C%ia-y1AqhV~uQR{^23n|#F0c;kW)lL>opMY6@QDt?Uv5TdjGO_v||BA$MZq1?Xg1R$C2Kf*y;wvepD^>7g`=buq;(jBhaymo` z)u+CInUK5Q#vGUG;=WMS78ei+)-$if@^bE{-Vq(NrvPxm09H-1?IPczTMXI6frBPz6Szc zQ3QI3 z_X0KNk;?Mous~;zhPz>KFu$3JTik*us&f5m`$K!_5z=aQYjCaqa4X=>yUBo+)WeK3 zE=|D5Y+*P!%2d!tWE_AF&B0wHZ~h*EK19k6csuyt^mA|c7w~+%68J0rg?Ge{IbmFV z4*;E}37qr{BQ!9XO6S;$>q6X4*NMGz^BZqcPrtD$`)NU_;hOXX7i#I~a_yq|`bg2R z$l%0u#IH25KCogFn=Ku3(T`7{x_~q&3K33-yxihLEY2nvPY*EMTzR+Bj5DdYI%64F z#K{u08+^g0UFR>;qJ!OPe&?N;4FY%+gMXsXUhW}hl2Gv!7zFLYMPUxnoRb{)#uvCl z25+s8w*TC2452KP7@CKSQeLH5dW%Epxjf}^$(0oNbnd6#iBP{8&$XUQpoMHN=?V1l zLfs?(c+=YFZl$+270tY+eGThy`x2Mnwr}e<+jql!HDCGqRD^GaprM^I3Dl|fn{6au z$7!6Tt*0ea(KVg4!fr3G+ES)(-_9p$)&&bM8WX^ws|hFRO-1#2J-N~!KxXqhpwnMn zG`c82Bqf-)n`UE?=x`wAIMv9Az=A_UNuw&3Tr0*=MNH%qS)sx*7MV$Bkr_q`dollZ zfFE;e-<})qJ~ze#)a8iv%YcZU0LSyz^vrJrmlo{<126n5MNdl~C#Vf5${g*2lozpu z1gEMRHwwvC+{sp|isUxXevIHo#WCVK6;Ur*^A4~bo6fN3JiE_%%+DHUYDsEef1y$^ z*h@@8!6U;xP+&)ND&;K)6DyP3oH0Ss8(m$g)0x! zG?pT93=w)PxTFA)0j^=P5OgDerNyB2?h6%m-pDIUyP?*z)W_I}6wv|o8shd92nH6y zVC4|}Im<*?P!^IdR0IT7hQ%+TnqgceP{>%PWn3uxAq5bCTz#SWDTv_e28Eq~wK1it zs-*^|O(YeV@feDVM6tC*e`k3I)P%WAJ@dBqrL0GLuPoHD`sykRRjz0w|I)MOVES)$ zL8^Z~GzMzJf-pDu=OQ{jf?>{m`cN90U@GE+u2;!vKB%_)!sh19m5Z zo0qpaCJ!K2AfKIfB-M%%b+%a8fVoQlt)={Odaq9WtB)WH{PQ3vux$!KK-namcv9U7?VdpNJ~ziSw%U zm|gv#iRd+=c8?d4XN0#8d@Y9^)+quj&0>)SeGAleD>R=08xc*EPmSl z-SPQaO2T52LdP*GYjweV?M|2Trl80>E8gy_wa&H9M|8t;F}b6*a6%Ua3-dkkZc-d| zHY#QSXxg{&g#PkUc0dWQ3N*}%+E=j-{k{IZ*S}f+wuM3ctK5rnaRCdrD@TQ->PV{M zm`c7WIV%NSbIKV+$W=}&Zm&M)@l%Pm@8U`zUpk zyFFrcmeey}>+Q8FmT)6%PkHLtWoFMgAf|taFZgD_ltEYBm3&z}9qYX&?F1-n2m~K< z%W2M?1+b_vV@cr*H$SU#9ZYGHCET+)-^-HB^9)=&g2Y zIT^uyY}O7aMpfaZc~Sc+)@|!vG1@CetQgGIw{AxQv~YqbLDo6#fi2% zF9ld~$!$EcvX_*DHY817i!NaH#fn-oI`v`1$XcWeikrQBl7g=W(hg<|y6w0buq3m4 zk(V5hhLJJr1Cu~uU-02v+S3Q`k5j#$w#CYW+(bw0aL5<+oArnfnqjd1EVFiTwSz6& zSQBsD`eRe(sIjlb&TP9$_S46vYAl9qix@*;Dls$xP$0L~r`nq1jTYEAGInd~y-DT{ z$T8c1WG}{cUyMa}Fm)WC(-Xmci?BO`=@~1X zJY93t9z7f0Qy~!L+2l4Slai#fN|Sb+*RJ}`rlon`Ac}w++E#n!&-Iq18DK+0at9;} zb@|y#mED&r_rVoQoqwcXFRbYVd)bE18eO?RoQ`d2jp+hAe|?MTQ&s7=z7Vrv!`*`H z#C8!~k+Z)AxUAQ3x8SmV+g-z}#*cRkvD5a}z$Q` zU(qvpm0j{Iy67eSqG}_cl!;5lUpk+I`+xA)nAwhMN|?rIspZruxL^{lwMsGCsjkDn zQMtZ)R-4FqouCiQRvZLTR#KTCJ=IJ(G3H`l@s_q-nx+wV4!Hvd?su2a;J_Ev>i4q1 zOfx5>@>;K!+Rg9A7`JBk)~a*i&Erh0YY6BY*k-&|&#}Qhh=BD451AxSM4t>}D^7)o zo7vF+<{W?_GjKs%Q?TC@XdTpBvD1YkVBK|T2y|!PgPurqa`&Pa^XPpmd-<-tGHmh4 z>eR)Kvg^)h8%K?^9D-eO9kmVpsV#t6ui)6?7`I!ECs2C`NTSYUq1EeD_vGJ_(p)C0 zIM3$wlqKN2NE7KAnX{1<{-~P3I95HWX(r6OM^dhkatkVneu}yZ8z&bGQMJ!Co;Lj* zz10`+BM}zqD*dC1hk!{o6|rO7@ft^f#kj37pbuopxzf35u7ENivzJa8r@S#+$YUSt ztGL<8uNCh|#)^cfF=@tW8lkTzl<;|E!qvoF_yjy7I06_hz2OZK)bD{@nz+Yh7EVMq zmi38oM#f@zpWx0kA0l*o_NHGk2R`{xZ+|$}3yBqbK?&-uLoSV%&o-*a2*q&-8r(C4IczWQWEPGVf|% z$T|r3Ne@1f9(JeQQ`JPgmK@$}_GAJ##A@a)!U)q0tiiLnU*~mk|BZ@^;{HulQJ`NE zma3-Z)Uel-y7gdmjdRG*j%1(ZOx#k0k{!N8S}^?sZ6M}>&SkGuSY_{-upJy#awx`Q zY^TId)cO8Pnuk_{&V-`8Uk-QJ+_nSL&Ct-Dot=%J`sBrTYN66U5!Is1Y{9~Y;Z^pL zW&#WHON%zdRNu6Mzq}TiB4$9m1ip|VYCV1(0{pfv(DvaIc+vENw3xD3Y|!83AZ>`y zLG1!aU;eHnO%APli22D%T!I%R)p?$FvTptY+Q>$isXaq8Md0lc~(b>M~;r(#0;c?xqCStTb><6nG8%D%2!^W=R07t}X z0w_AM@Mp9!h3yt<$YRWv*DRB|R$Obo9$Jj+5bprAHfVBh&~12g(+cAIgu3QfchX4u z(%-Homf2^>08%b>)Gg>`WQ{#*oMZ@FR%^U1sQShuczgHd-hFxJUD~@Z`vP|7+?U?_ zfgux(_HEkCtoCi%_HEks0fK#iU>_jZ2M8SP<~~4B`>q>g`~G8Hk=pkk+Z4fMA7ycd zIN|GZOx?wwzoC+w&vc} zeD{sq+nTjkY>4CD*6iPq@?~r8fQ@v8l4oxo-2?Newf)Uj%%ZQ#oiV|e@y?i*i%pgg zQ6HLsz1V~`-Ys2kvIBhgT-?@q!JhN(Fz3bN&_#9bm+O9aB8jmSyyw;qnu)AXXgyYS zBgkQB{tK`(=jr22welLExx66cJHXcmzqzWj8Ki8m1*=)*mXjO6wEQ7klD;T1k>p_b z;m}pvKy(2Z=bjmY&f9~gwDS-2#g1GF#`CCLAsXLmqQ0PsM7P{$`kh-0PIItziAEx$ zBN-fS6cFMNUCMdMDhnBDAzoO;AL>f7k$J=yUfaF+?CBh*3Sy8-Ack(YNI}YWv+06L z)%(UVG-pT^*#qn+NxgQeX)T;GmE=WM_Jz?dXiu!JyiBVzyVs!RcX)?1sAQcJ%^sir zoM!{;NKW+;g8i@^Tm?DM0{E*RlcNolu6iYsIi3i|fS{*qSOUc&i=CAjKyT|y2n>vk zdBXzU)ddS+Y;ZVwKtfi0hU&K^9y~g@t>$TU+0s`;?jbULxHCiL^W3)MMjIHKGVXy=TqD>4IGffvx9F!!(Vh zNF1Yhm7z+k1NGV~O^G*oUJLu8pV!Q_c2LMSJXQLvWWh5L=hZNc#GHEX2=v@G`~}i_ zeSHCs|Lp>LFJf~Lku`4DSqcH%74-HzG(k*1cI1DH%2;;h!Dq`5WGrQT!(@i^FuZ_?<6MF@<3V z5Vf32&BiZs(~s-t=;jm$CCfyKN;KW`p9 zFagI{jU?u+7+@dZqxM^v3W9pAz;w+Y_`(u2Z&28jSe=yqtooJrR0`|Nd!4hNJQ818 z6QIJ`yr_K@>oD7E|30+;1<%WNG=J1iYgx-y&4>R*#MeV=UUc^(qk;F`iw#+?%>v4!V#Y@;f1|Tvy%LNzTF_}DuYt23xuL%Ql{& ztme0F-XV&#IBx1S8%+J!jOS(cQ6@$U^6edGeu86pnYF}g17&mw2Aj;XG;_3oQyw-r zOM+7z*5z90_P0N4)RIdP!-f}_IxHoHM+GbSfTR*@DHmfV#F7za}`%z z_dZ?bV)!8nV$Lx-2!NkY%e-KAF3km;ze;YpfJ6NhDC z_J$_7%08;IOpILo%GZo?Tx0oeSa?&;JR`gNg~W*D$MAM?Me{t{zS@kEE)=8fmX*76Wa;V;ap`Ptmy*Zw)>24O+{@# zXkThv)=&069nX_=DpYczxzjruX`S+J7OHQ0%fESQLgm%Gs0xRaOPdAA{Ie{5G%Mxspl5jdbM#sY>K?Y(5XlSww`0hCCREGchK|AKCrrT4sMK z|7)#|y-ZTt@4C67So#zgBF{ldN)tFz-5gGO(?a`@+dV@A<@Lq$V3sPF9%t^0;MvD# z7l$0WCJc)-Oyh%KTq-OIOt4BbLnpEW{H>TwB zp2N>t*w!}o<#l$ai{eW7xre<-h$Ut0j6&MS5{obSuH>v7c5pxOB=z)~I%R$M4K`)D zj#=iFl8F^NwmO*^oZ)QbTja-8*yMh`@C1G+21AsDyOR8TBH2rdZ2h*58UynnxA2*A z@@t`DpPBhuAL&AbyM5ofg65_UGiMKFbbcZDID>dx{3LH9kNK!q`u?meV zr5{yyh_tH~c!33^VRe2WQxm)%xd|IZ( zhl3L4e_7@t;lB^`=Tjs<)b*-lKa?jEp)&q~FI36ofamjxnsqs-{qi)VQG5?_{QAuJ z;Oc`s5&67@^l&6_R}O+{%KtmeSa$pE?JnPTA<+II9I6akv&*-0^0aS}xs}*}u4lnx zI0KFDEmgzV!WM?YDj7&p*Xsq~N(kDvewb!ShbxPgJ$fSUd{GiU)*%c;3)!Vm4rcGp zZAYN%q$&Afxe!v7406ALFs;GZ?g9RD{T9G87qGVKb|d%xc}4 zfbT$L03S1+zV-v6yB@5A@Xlu=(kpqY>1K|M))TxL;Laz^Jorw>%sl)K%+);V^q6L3 zIHL5wi;tvVZDEZOSy|vXy^K?k(U|9IQu&ss8QqO*sqUQ*ZX1ZTi{-i_*_Vx3B}R(i z*m(92f>%TIj|%={5XUkb-<~A}oS6)RXyhvYc8UBiO%l-iD}%v8rU~F2AqsBbW6*uxHYZPu(mF#A}fd1U6fFj8QTj}5dmj~c@`D3 zMMoeR7DvGu9JM&UJ)ke?xtp|%BdVdcp%)(Cz8`E28cpTa&l2B=+uT^#J&LsE_SmVd z2=N^qS3l~ok*>E5Z{w|vT!&_1g4hB>Mi)G?2d7TBHwgd(~VbIFM(=`%$%8{0 zaVBf4=wsXIzfJACG=elkYGdEH=ZP8hAHR%gWO z?MHBKHBS^;6{vpoFjjA5@Zt<*+ldlG2~zP9d&sEAf@R9AN(J=u2b4mUJpdhdCSfE@ zCQgD09OG%pxted1^d_nC?dpZ5>s2UbVyI&HP}-0}m>Kp!yr7Q**|v!=Ng$j~hFHJ* zSm`A<$WJF=aYzh%jZ);1CJa+CtRT?Q{|a7iODJ{MQ-QZ78r7IREZCAp%!(3K!!)T` zF#RyVC2~*a<3v~K2~M&L^!?btB%CVvYEv`pD->m3GwAA8UtJLy1&hlv`o%1rmGpDc zQxZ(fz>s}Hr=&mBy&KS{>}fat1Aq#(M;Jgi8D~r39hRFbXF_*!U?&_ovm(K?42!Udg`6bWqR@*eVFvD zQ)78&jEzBp#m)jFWLZ@*olqlOO8MZDa3Yn!iF=aXp=A2@})rPEtc6QooByV8CAg1XF z7$($;gY++~hgBnre-)0~-#S_ETwRbu?N-ZpS&T&?Uw z)prL~K~-e7X@V0r>XzPDjj0hV2&FI3@^bhI$iXhS^%ZQrs$}Uw5U1SL;DE2JLJ2n( ziTa-YFvVMau&x;Y)X9K>iPW*RuG7vd&aD(*EgW!5ezX$luIkrFAR+^WAd z?NgAZL`s1fg9B$t3VK8`hQIMy&TPYa7g^y-+x1m3-QUTxHOH#FIz2*4FF>L+N+z1Aw0pvKHVn4{+;93d&htYSZQc}KyFn!tJd-O&z zSatyiqzK=Mh!(_Q`+47^zcKpj5xP&jmp;`LU0row+ZlL^x5BSwRa}TS*UVa$2CNafYotwc{if;T!xS zlj#Vo_Vh+%QC{H*IXIi3f6I;Rpu>m)W;Y`q#^dKtJjGBurT!aEICm0+|I4am3RK zJ9l`wp0;;5%6Vw(rSRi}W;~QX@<>P`iU_e)){nQcT@*Fegs&Ab@l983rt8u)9VOO_ z(R~dtzrp&a*x_u}7a7anr;eKJPR2YoH<~h3bJcr30nvq6yH#*Mr~ic$fwe3Tjm3>R zIeqlVG=QK^hLsFyrr;WtP~f4;J7_;{q1~x6{1bgS2|$Ai$b(!X844)!%I2A@fx~)xT3vYCyy3qD%D4qprv>&0tb&dZAXq z`DxbrEI^NVLux4Yx*Mj^t={c}Illop6DM+-j^lI)#@gWhki9;A974Tu+b~+$5;JLh zI`;{lF|lZ)$di|zir;m+l`ep0(=qHxAmsmsQ z?(^(0R14zMRaNM@9&Y7+FWYOzv3tH6USyKEn>vr?W#%G8=I3nc13jFlxjW2n^q85M zCWn(Wb_MyXB={Ic^D^`MUAle0?;n%*FDG{CS~7_yr?ZW?(8IZ$JAM4_`MfK#ca|)4 z-6Bz6)oOkwM=>mT!J*HcUUb??5;gAdHfBLyU~6`5nvx~9+84HNTK6f(Zi&-jWxnoG z-N$d@ZYE}v6MMUgo!s2Vn^D+++LP*ny0V~@iQ0`WOk4Oy{AazRpT6tmfZCi*xN?&2 zg{l9HeuOWuqYQ6r4Yo4l>&&rnp$uqgLAACj#gA_a!W1HW2h#aeyn(;7GM0I*^;y13 z+i#;M-ng2pWXaLqgZ_@7uSerPaT6yQyJfcOVlL~&bbtL5HftK@X^d>|^~ISXCoWpX zaz&25=Ox8gk$k&F*0scKA>XdGS|iQ~|8EDPUwkQUk%NNS#xey#@2}U22Qz4ale};#fntda~AG?2wM_9j{fRU2K3h zR7EDTTkUC`ibRZSQSSNoGZhjAEACk+-_A&x)!y(qT0f$Wd^T=`Opzi-ME6*V40D>! zf`{Z2zlHxdokr0kbR-Kq7lVnKzw8QS0C)1GW0|CSGQ0}}w0?HJp&J1Pd{c!fEs==b zus~mgiT~}Au-FS#4qy^RiChOiE3wBS&c~9Y&BdLg08Ksg+gfdR&IzkTQ#E2YX*@uT zAb63grj+t4O=OS4e6v}s$u_qctJJBAd-Bn1J?%;N{wL?iPkGk8Ry&xKfEFHq6r3iA zd(J^Svt?KUaY*{VVIv`{2fjeWQ&p}_T{I)9u?BqFwt;{t7$co5*|jJ>g(=niR< zeO^?@H`N(qbn&jbMY#top`pR(fk?&HsW$fMp{e6Cl7oN1>0d@K*AMBCr9NrIb@Z>X zKMty7ish-YiLdjo%gZ4ZccEUUPuf!XZN~Pr-v+WyZ611&Gq_VEEHonXeID>gUaP32 z%tknrc9Ywsk0;!48%5YH*V@Ks=@HIVatxv#JBV~56c)XM>;x+2+IQ4vMv5D(BaLH@ zUYU?|!q!dZ9zshEzF=HdT(g7#k%2~YojuLK8=yA`Mwyh^Z{RpLAx5yM)f*Af9WPV$RLo zjH(jy%xZbJ=&RHmKCgX06}Mzv=qAmzK4%ZdC|f^V$fw_Iv;ml4V?lSOjioWfV9&;kQ9UuDMN4Z@tbD<# zb-^w2w9~gpufnh`zT#_%I;)$XaD?R+6&bu|btq+`y#tck{_*F&Af)V*+u0xwhlLYLg$Pxnzk>DfP;)w+=-;2t4!w4t?BHe%0EL5<_i( zX8X37ulmVMpLM)crx)9Bz}#0?+bQF-t+l4d`clKBFB-ZWS8>@L?&D0gimh0M^5wka z)+cD+CHX}20QGfbxN|?XFKrOOy1OjiB>TEu{R=pe({ftB%11ky7508f-grYnbh;6~ z7ZP|O-tGnnceL+&0plzo!9m|J|Fz06LV*e}a1cdlV>!BGm-x~>K{i*{KPO1HR^oqd zFFeqJU=z9Ni=4KrusA8O+p8>FQCaZhY|C)eMx({HVl%WKsG0O}SlVEqGQLjD#(H79 z14FU4oRHMAI8EOK<;)uh+qaOL;7)YGVV*Qm5;+EgU0Il1_I{O+cy207jMgeSx2j8yp`xLgPxJROTpo%rOH~x^E=M}q8icV z3h3k7W>uunt?Y2QcJPkcU|ky!OLc)&XK39U?o-Xr5*2TC1>v&e*>eTqT0VjD**&AM zBHGbGJ2T_CJZ4L0Z{(lLGi zGM*Tnj2WpM&L0}@m}*JoXXPwS?4#E*hToO8d=l*VWUf{X^Qr4UKOmos`6auQU=ZxW zhDnNaYh##>6ZJhAeHbY0?<`hMRprS8bo2zTYANq`bM5dR*0l9yhjqUb4tHWbSoewD zI4S$yv5T4prjmNNcw>YV5Yo+^I(QEHsdj6-M^Og4y|sx=aer5ISDG4H z`iinRGyEKs2s*ElecRZuRhO#)pxERUU0J3Q&KI;@1HQxt`}b%Lvs3_UjIj{Flv|r5 z*f)wVK(5CREbK`bmyti%fFjGe`pYH2^+b^XNrp_XrRk@QirBDut$^LMAx0Rnv4X7- zql*d$(DeZdRAl}5kkSqkfBObo2}-ns0BdD{Gto70-> zf^PU92l|8j7JZ)5eCY9dfn=K*mxBOUrHtop0yNMObHsa_tPIjDVXhFbAIEJQSfASF zwd(;Z6JFC<;+g^n0o`YAI3pOSL%b%jMt)U|g{}TV`=%fC1GSVN3?aR8ESN!4A(#kelfGl6}4xD`5U@hFzC*%18;l%gD_%PmnfhTEY~dUq4Z7y7fU0$3O4 zEl1iHX7q7SA*>}P_dFq+;oIlF4%58_-rS%~4PQ5DF5QzV`c&rz+?is&Jf?1P5F^u; z8(tcdt!b3W@4fpu)BCW{U3~!;w+LWz758z`jD*)qh+ubO?#mLTlDn?pN5LW312T9Z z-;#3sfUi4wCw{aNs5Y{psXFWAPouqaFMfJ@gBuO#`FFa|fS;_c5}MbXx^#|j8&~gW zmviaSwv}^Q7?qwR=G5lF%U4&yM8;d!B(OPC8g!_qcovvAWlcYD^97-zLc64J9jAAivYvE^M zqIP>hoPuVO&EbWd2^inCO3OUHC8kE%e<#D(*Al! zbAMq6^fG_ljnWzPmu5Vdv!{j6z%h5`e)t-AKmeoT?<+OI+O=eW}pVCDD? zSUF`eL!G25oz7%YW{I?o*Q>U|R2g$4V~ju2S92XJaXfPu9o@c*U9`C+ULoGdJDNbe zNi{`g$pPHsyB^>5>%LE{!ATyO-mh9>SO=4$w=^;|x@w7I+O*YkL??P0D!SrcchFV2 zr9(1no4xMQP8)+Zogg9A1KAak7Unt*_zY+&D)BA?y2AhnN>VTP`V*MMl?KxkzLz4$ zyR>VUMbwW9Sutf?s6#XPJcQxEob1D@Y1`OcOAy4$TTTedP;*2aXPgof=-k1Bw&X$6 zN2vn0KLEXlPzH5!JqYH)5XehfE`G7eE{5fjf`qhgh;)6FX(dK&)Wp|Cyv~$OQ-Oc3 z4>#~*$F|Xjez@xV(Rqk>3EZ&C(j|P+{gR1!IqT)73EAXwO>v1rilqiJ#=H#i7LQRG zqer)>1|mLZeKq=LN5RMGjl4$UQ`r1HVg_f1TM*3bHXP$E3Fx!YMUl)9Rn_*38ub%* z4uwe!tWRS|cLj(;{d~l(=LQF*--Xy^op@PG&M@=F)bsI=>iMB^QHsGbc7776m#&psabtuV<%$bbOxZB zN<|QbnIw%XCC*R_4dUmP_UI|fY)2~rFO-6lQt4~H;7t)ig6zcHm(l^}sGqx48U+AJ zQ)V|H_D+Q}nITWuE)~`iq8D@HIt1R2>OBws60=T6F{Iqt1l)Ug&$*(0yzK{UP}mz@jY< z(FYJ&t1@T*gs>AMQ#dhxZlfTC0$O660n_v+$+ zhAyQR8rp6Hy^5q>fUpqbJ3-mkLDEwpodMk)c-IPb%WIwiqn~DA>SEprCavgygAFpM zyffw*oj7D2HFJiCcGc8(hHovo5gFy+PxZXawKe8%t8Lr!JJhX-HQ2s(lO3Yx)*re} zNsRj7qJuV_H=iiYK_s_F!3%W3vSF?fiTf-x$lS{t|t{aZxQmXxj|+w}ppIKWonf z#gpAO8EJRCA(aDjQd`{o^Gv}PP!ZVKwcbm2;i4yVkGjRL&0m4U1p{(MmvLT1e0OFn z!Z!$FjR{ixVTt5oO09b`szWQ5cr^|hq|8UX)$@R$*4;3bFNuzM<*~1?4>&(*Hx#KT ztnQ^%DB~#BcRcEXNS+YBt`B;lkG-V_wYn1B=L%_Gm$U2x8f_)`%aCUvyJt>B4VRds z1>hkLF3g~Q*MX z$-AW$t&mq+i_OW!-0(~EGO;hP>PgkOg(5|9x~{cA+x{_1 zHFc!@Ox%n0t$*!(Iw($(Y<4Uc;}q7Ik$JF?W`S^kM!jRoO{3ygL9K6)ZxY>7bCNU$PI{ZOTMLF6 z*fXXpPLxS#E6q5#HNJMm_{N@Ywe1FZrRsOnb2P}fZi!YG`IRq>^nKT>5b_)qYHy0A zr%Apz&C$6cy$Y*QFOwL~VdgIaiGb`5bfgx}H)XPkYW1>9gA;;g5%dKbO%VuwhluXD zQU7Tsh%`76Nfa-{yn-CWOT|LT>qMFd7kR`1X5RS1)95mHfwNC;XXf)Te}pn6lgpT# zAxFcgeF295?IIF-Y5Mi7%t#$<@jhBzmF`TxsWP!ht7NGzs3aCfr(B>I9}*e0*7k)S z#yI3khjYIG4wBotZ+I1s+^Y(sJeDo0JTG-Ck1%B(sE;<+4qMX^)Edqb3PD{TQmOhZ z7PlN$8QD!lt0TapmDOMUQur${230>3Cv5(7*%Z_Zqx8nL4tk2yux42O+NqlS0W2So z3JOq$Cdg!<2bmv7{HCq(jY5o7x?lUxQ*^Ld=#PKRmSULvqyHrr&0 zp)_%YD(inFLKUk5!yuoQDtt5FMR4w{Q1k?SAinS~;7?#BcM)1>=z^(ro+{^i@f=o; zkc9jSOazF9X1N5uWguJB)sbI`u^ePl+yypYvR)U?BUpy0>J5Pb$%n{xjY=TvKQk(T zx68)$yD5bR{Up1U8i&wgc+A481x-Pp>kET&EqAJ9))p13z6Fm{y2rwso&{FMSPhX2 z+3rvaJnZPS{-X}~y9N3rH8!{bj9hm1wAYqk;=(wTPPs@VbBG~k<`j*1F(#YMU&~UjsvG2tFPU>%Gr>&$22%H?>McA{y>Q*l zVvL%bJ*Oo0OvEo_F<$#@-sVeHF>tG- z?ueA6_G$di zTMW7TDWx3PR|%)6+A|yu9cWr~Hgi~Iv(H*kO^I^x8n38r7r%~jdMcgWuYPV>4j1e! zQ+Ni=pLIB2ZufXF*X^!=Y1B@Zo@Q`~8&uaGeZiR20!hnJeA)PwcMwrQUzn6(IhE|O z%_C9P=+=~sxXFG*$n62(3JzaD>uHj5d-@jnC=Axgh18E8{k(1a(I+lf-JsQ+>Ht%U)#M({iBSoei(Qmc`VZQ38)#+|rj&n@)iInAIiMi%M=z~Pt z!RQ>J=_we{EbXj2QEJ{Rd))g|)3$wpo4Vfq2nRv^izd}#D$%RaccpxD7b$j9J>`~! z>!uL;S8;kKXmSIuBfmq1_fQr$QkTc)1(tSE!7TirS49A{8$hJX$?wU4)ZP&h-ymKV zpMMZMoN#44t4kR5$}*VywMdFbtjs<6Iw*2w)+aw`h0M(H61S~uvxUNaqXj(RpbJsq zCz%L9Z9sKL0oXe78>SD;fOgns`{lB*ovu~R&!LB749cuWCx*fBG>Nv1ASNyDID*@j zN)6k2FO-6y{>7Y7s!Y-bO)qJy)r(<_7~9?soJ6sZ7KC(N=9s^dbAOxLCN(!~(>`dr z6+H5Obm0ZPw7y^Jm3f#Gn&w^z?Wk;i z5VtF_UIsMd*1Z(1C|woMU6nHTHR~H^g_qxzQQ~jj#iVpJ@2d&04qwwAWy;aCpJmF| zq;s)Y5ch7Az7$27d7F0Wii=#9uDJ+({O(1di$>~_)va3@satb~=qk~)ZoKT#9ZcG{Z6Y`hp(tnW^gW47M$6uS|47%I7o1{&H{ngwNRWH+A$byh z?JyI!2f^{xLGa?@ASmM^1BDN`!Xs{yD)h?jg*`SwL=TU4qoV8%fKm^xR(WySD4c1k zruMZnhdoJrIZ0yucHJlL`(-R3t_H4x5%tq(dwlU(;m-{52CvUfxP#V!LYg@I%d)Qv zzuM63-tD`-sNOn#ht#sRpvDyTGTr){ec@tG{#|J7IE~ypZU$6PL4L6~HlZ%$8J0KE zd9L+`6Ct%VCH9w7n5@QX4*0gpf^L-8c?e9I$IK1+cEsSx-wz&g3=&!LtFx2f{i7#G zzx?(0j^2Ooz4w0k=$DS(KYIV~drKm1>!Q7cPJ2s9KyW6 zpq*)^UGv>ib5<=nElkAiYCqC>OL!|H%$|GZJ!)yh-QcMzrvzPJ;8HBETS{frPyg%_ zYQu`17SR{W@j((UJTqTFP@~;fcgo`Ki`FW>h-gGgjFXfuW^OjjX|Y3?B282#v{Yu0kceRQPgvONG$P-j5ZcvR)`upN0R zB_-L*>a{RK$!hjuAL^1Kwyew8q(N(q9>ySzs%%0o8j=*}B4gCpe@Qh1Fz}KXXO1P4 znTkNs$Lw;Ue>*>-ZR&{^LkFk&<0Bx8g!81)T=zvC2^kTuMwc|zE^MX*Lv5VM=*)Rp zEE_c1tgnI0`aJi)U{#YZDAUErvB<_$5HI-Wi5*7om0qz<7BN<(xfLci$m1!-eKC!ifk+Cs>V|ljD<%r(V^>)Cn8cw zsAeM8$|wB8Os!VxzerX7_4Zx80ml}+gt6kn37aGxn7dENpuTN~vip6BPND3=#=NPq zRkp53SVUN1azp+y`4&u0%0T_3%x!-;;CEhKlgZ9p9i#gdVQ)w*7Qng%*1xBqUITf} zwamvfDA1X%u}a5WjKUOHr{O9V`w%^i2k*c4!F!L6f>%SdENB{Jbd8XzlX1OEP}8)p z1UdJv)WM&*oz*p9wb2i39oCj^uUM8Y=(CRQs^SrN!-*I#X*R}M9cp^2DDj)au}pL& zR^#_zAIBok7g`7L{y)SZQkqCir>s1qs2?-3!T=0@Dg(A zB@m3z*rPiKh@p00VC;g5vQX;3trK$}+;0tktK56w-hI!%v~d^9O&msE+5#oiT*>CB zP3ZPR30e%<)fj3q8plotScYkIi`{LyocP|v0T^aQIt5V)47Lo5GQ)7hTQSAn7QBnR zDd*^YegBt&0U-4@cpf~Q6vgZtj~_=06t1I0PQ_80jUVg2!#`=m@) zG)(<4f(?Ji4r5Y*qYsf_@|5x!^NHX)OJUb@a>i)7K>lFVc^IQ%L1?nB-r|c$syA+x zOq%c_;16~48pIi`iAQTl>6BOqZIv7q!J9-%Fb;r;2XP7eOAMt(#PBJPwU9E_eEXWQ+XJim5$2+_OXX>~IHS8?|i@ zs=$WM!k5+l^Ur&XU$)_sr?X>>Np}IM7u#GPMgx%7%^_RPDPw;}}#Rjlj*-ES;| zjMT+whJmCH5c-sR3yp&oYe(HT5X$5j{Ysg$<~A30z?JYX!=9n+-nvB0cT?MN`nEfp zm)VC%r*Z5I{9`kom)XmN_W4jHhce63%+VrFd06CVr#P&awUo)5WUd+S21|I&qq&?p zef;iyaF*AhFIncgC6XtV1~PoNYr*-tk?&J)h9$b@%FZ~;*t+KqIe704-}F&UtoGSv z)G^nEno639aaVb>^@Aqo)Ctq#AuY*YkC?q z!qn4;*4zdc%Z@HqWc`dWzeG2r>pm)#KaKoAwT>#4gloV>C)6)m7gYVx#op@IQQO4I zHpp!Qh=PSlTKG>;*PL;o>}a{KqDREOquDfdS3pP_Y#hC}+&y?G8{hcCN=t(-IQ8Yv zMt#yqI`n93xqwMdRQm!LMT#p-^k)kc=L@Z!=^BIg=N2Tjfo8Z5R->#zgoV8wVX=`+ z(Cj3$aDE%Lf`N~6)NqnwGTtg8bPM7MrZ^ZC%F&Trx%@VAN<)@5p*%6k9P`N?kMA(Z zCA-?1jp(Q~M0lvFttfS~Wfl zW+|$Jin>stHbk{m3EosDMuk2Gi*NBN?9qECvfE46Mmr$i&xIm`ss=XI{qKufh)g4+ zfdyrf+8hOG(#S@$C5ymGP)k78-Z*IZoj!p&qdAi!ydyEM-uAXwxNjRMLeO@nnF4E? zw$l)+uYF>R>J?3(%Qp3^Vvs|IQ^@t!Fh&z;P5{n!xhiX#^>4n7p5Wh=GJKOm9?2NA zlI*sv2!CG2Xy>D5G~Ot7Dv}b*2g)sX(RwNPh8P1&eO|t>h|}U_=Wt#x@vG->+Nww% zl8FU`1Nq)0j(j^)Op>UAbO8hdM$q6u{|eUjD+FdH2$U9vS&l%;cw=P?>)i zNaoE(U#MhhxeKsp195`>%SsQJJ1{833h@S$yAypk52m~Of+|XL`Rx+a$&SSf_P6e# z`cw~HXR`*RRxgAvj9OB;YbN~eq;fY?>9*(kOLnDk$?02ATUrX)7T_WB(%K~G@7MBH z+8gHCIbq{t-dOuC_8?++Zf@HPZJp8YUUQ*i(~jw&-fRZFGbgrp?FAb)M0I&N!#^ra z7u*Be09NIf<&Yf@U8F)k=KNV@%VTA55K%AloJ-3hHsZ+Jm7BR91yAw*_K_%K3KY^4 zRh{v|gZe2?pxSA!jf!mLSQ7Or)-0< z`A4yAT$D-goWYDH~N;slEX28G)^<>5E-FYwbmSxZx8`xIJ%ZuE-PoM$n!K*rV^Wr zXw&)39AC)7aW2v@jR(}hJ8A{^eEv7yaV8sI&jRm?C3m%Llt@qHk8~o`D2?Ovrm`$- zAhXpsjdHjTtm)&P62e!fn>i~Cy;q56?OXKCc4W<0{ZtC=P~l`_2l0)#721J8)gdfl zFgliL;ZA4epth^c&t(xFd1L!qFQ#O^gW?6hM1MT~|FNhn#|4KF%c4FI+9FG-tbjdB z*#=87)5HDg!d7|-OI0bUM&n&;l=X28+VIIb_@=57yygwkX2cDUvFsRtPBt4dTaY zP8*T+0n`?SlC}HxGN~O{^HonWX$Ot~S%FLihSs-XL`Sj^D((VCUyAAp&A=8U7*Kox zU}(Gx1pP!YE@eDAv`0<5VbWfbhPKG^<~=cc&bJND$GsG~(0VU=_$&0+a>q%X`GW4l zaH3!bNfR&87Uj(-0QoVt6UOc=k;)~&5FFM!sF_4s-a=6plQi?#)YHDO3!Hs%&!$)3 zPKm91qh=asV!=*Ga-0l~&(0qOCajXl35%EuzRVJ# zalTf-HTeuvZGRwouPJ$%PB9-HkFL2$DiB@!vaDQZ8&x2*oW^=i@4J@RaYpa_toG6R z5|V1D4NFKCAzwJ>ZXv&#G_O;tl4@~Hd7W<>cIkd6c!y8$g&S{0ZLD|Ri$2wtmR2>b zHM;Km)$nWzcO6sAcq{2)87(lH-Mfs)FHy%77~n7h9m~-gUZzEG0>inSJAJ%K&ydCM~bzjq2Wd#@7T6>jKoFo0KZJxDn zH>0b{%A?MYU}h;yLu)bF*a9^6LZ$f51=>+;MtHsa6m>i8xSlIu0uuU#Ps_CU5LTfg z{wK1bOvd~J^46gGH$VE_EWia7Z~ckg1N-G>5c{SrOmc?GgZt3Gr8<<`?c`E}t&!Eo zAI+UbujL*)(uxDsl4})}V94r=F{bmtS%V(6Kwt1g+l@vbG?5L;Eg_->W?pr|@!kdJ zOW=3&1=Vr~+miFQYmPNdk3PI=b5gXy;dg+0tvrWwVEF|u8Mdf5i3ZD5TmNA0TJ_I4r%>W;KUlsV3!_tz+CN|F*Gi!C`@lN? zwcH)_ci|$_hzkQjL1`~Zt)#OrSoZP4U;`|@M-BFY8t;(r12wx8sA)CE_aU2mAF`p3 z-Gn_>`fHXff8ElN2`u%-bn_^zd&Pb7V{RJgSaFKC7V@}g=%sD5p8LgS6g8+-G*f0i zo>nTjY%d`U9;zdGgpMs43c7O+uhBy9DTU!uQ)){(b{@ii%b2gD3=Ej2(*!>73D6U@ zk;Jchk86Adkz2ox`*h&HvVjZC{8V8Xi)@apJcllJ#Kms1Z;QH8Oh0~@!>P)s>faY8 z(Y#-M2_TUNzj`CUwTKMeoE&D-r}J*_M(+$&bHmol{-b$iVJQ*z)pg)I?anw4&NWub z`!W6QqD9Q_fq7^9g4W@AKa>h5vfNFf!Lq9=w(7{%rvf=HC`ZO&JiNgibS*(RX&7*0{B+?lPBPKXQp? zENPLWG9IZo-XfTWN9|r9uc&F1+Pl&kxzlk08wN1M$y94-);FHJ7R6&nHTCf`09kX! z%vrZJMm4#>KN&}daS?*&~d2%eF}El|?pMd?|rBtS|(F6#Z8 z4uTlzyZtNdQsWE$9f*0NMuI^*4%ydkKCYskR`W0CF3i|=bJ=}xG}r)3?~%HN;Ha%Q z4{h|0lSZnbskS!h<0sUK9Ue#!r}?MW8>1_sYCWI(`075sx?(Tp#N#L? zLq%XyDH0@2;7n-lV*rlYu|)M#cZ6in2S9x?nI`OK_yarfk4hEQav~PL+LX;0b^`2x zl2s<@4RyG&aZKt0n9hKg>sH=Cx*$7%ma_onbg5Q!K0+I0Rz^f^S0-L7a}Mhcyrl zFgO%Rg$*6)>c&!68ORZsh~|hk-g7sOw?Hz>B*%XDw!C}mA^pl_S(I7ag~z+^G&(?^ z4p1HDf4#mq3ry=FLqR&t0?CjnLR*N2F8);Xlx>?DX>*-L1dHHCr<;Ud3+k^G!#ji5FJ*z9-7uKKYsQ4`3ZXL$8kEOW1`bSkXWm4fs|1s z!4M*ffP!+o#)Dl|)juk~ilE{LGb8pyEL$~vju(X7V3=SiAJw{t@)TcRJjWH_ENWhF zJS4S+S-g&dvjXNlc>Wwdn+omy$$>VidsUZB18p`*7pYdaK%j{Lc0nI9xK&Akcd0G< zD^W?QB?PS|Vm(>oXmU-{hI}*~C@Rnq*-#ak0Na`v6(Ey{v9wnTw9bL4k|Wywlps9#{U)_jR<|KUu(N>M{O$Z5y5)DhBt|_|V_(n!h6>@x)z+R6` zzQ8<6WPv&LhzZ6rk$x{SDpV17il+CpF#u^+98@6=H12?FWQU^+%>D?~FPxzTwsng< zVb#46!gBJ=CK;d!|*nY?L|i+t69E)M9!5d zdPk=oyZtnn9RyE#TrCn%PU#>!^V5~O2MY%d5(ns^Ky+tiHY4{tY9_Xm_v6Zs`*};zN&P!{g!fsPE;C6~|)t#R$X)W{! z_xW}Vq#iqnbQ5uODuC`mVQ!SJs1uG9U;l@+j&9j9G2Q9N9ZqfpLlM4UykqJGGSeu% zaW5D~nFJYt@$3b~v7U^v>hO-TbV{z8B!?Ge52zyyC1}C%_k}0y^)V+f+=Vhadvk?}$V1cnh^_0(s(rLtC zg`rPuDpA)|`4l}C{0jaOZ1_X_)lg+#bd4P_x3m5e zn_^2WF$7ZawXa~8%cNTY!R7hMuRYg4cfY-QB3g>k^&hz{pj@`YEH?t2nH>Oa5em3G zfcYGPFij3Q6!O3qrlX0h;<}vs9YMYL#T9};>NjJR5H9Nm0EU9A# zd2kqTx}$9mi}6L+DmZa$VlwR;SBwEiA2Q1NiRZoEb6#vb2PU&Olh=xV%1M0({PurT zyUOiLa@kyHD|EdK3w|1_-e)Qha`$rYC(C_s{3(OhvSGC0T1z?X(svmXGDcsV= zAmQ6-TBX}kzmHy4fN3jNCNzAoG>%)M6NsrI2MRhs?Qx{g6Ni~A6trb#y?id-bUbF& ztG4SS{R{tlWg`0)KIWEjTt(l3|6NmqmEo!wDZ6q~O2LDp>kHc|Jiq!lRKHpwmfT{C ze5>A1Es-)}6tNHeN0sqU^1q&dfINyb6Sk?Kfi|K>GA~vE`@;Ul8?0vwE$T4(nCEgj zq_}zuVh5(i-Cl5BaeAYC^?@pV+*2}Fr@Wt<95C-`U&K1l_ZdJmLiY@yc53-%`zD*O z`pHkQ6hg^#cwjH%5~W<_#TGF`_WdGA!b)<$+{yj#S3nglheWZJFWp192}z zxP1wsRnNV6_^OHrJfEH9LmKtrrNP08a?IXY1H}5`w`E&so2U~wy$R0+k*R03C7ia; zuxhs4o@sqmnHKfVsnxKyN;dnOEx1+8R&bM_8h?Xre_sF2CX>?MlbSLtRZN)!GvP5i z#Y}J&q_zg2ixb*4q)iE-xC!FQNLFynypTf4l|SpD^&4VjIV;1W%<4zt0JdaiPJAui zhMq<4CYTPdRa(;4MJl4EF(XQ!3F9)S5r*WKxDDQ@Bx;B^z1zCzHPRWbI;YwG%-5zP zg{|7~bNb$!Z)ZRxA3TP=;V#)~c_tbM2C_6Q_#qt(XyuQSbSmjr{&3CUe+x|YS_Q8C zk#`P7Qd9?8z{mmckn0rWAWPHO8`pueHH#@9ayd>{_BY;P8U$v7bigfVuDE#*IBj3Y zvZB+xrhN_T(c5cS_f^Bf|8}u_dUNbdN|4)!nG_M>$yEw~cBFDRkX5%e<1MS}Sgc27 zl!Lai_N0f?+Xm(x(=L0#^kc*`sPM$(OC4}G5NRm;KxO+pZN7eW&(pqgJk9!2x-hoB zy5@YB$_$J7O&p>A(#Oeb17cY|(VC>w{_=^M%{PhS^@}jQh`_@0HNu>G)yyu%%q~<~ zkAkxiR&ClMO*GVO2af|9xDL3;^K#l8!jiNdgRDQwBWYtPH5h&E+=5v4DMAJ;l&j>z zMuf`RuEC&@KB9}&shxqKN7y{ZXJ`PO%ME8f6IA?5sr`-p;*#F)pxu5ciz9q&ed9Yv zkne)<9<-H3=&q{l2(GZ22)xAdZlg~9MqO!Xh|m;tu5Syd zpFg$%jD?8CyEX1yG`$KDz>iylQYk}qJ94B zK;3J? z2*RwK4s);V0jriBU9crihm7u{>M_?qQ*Am2p{zbY1q+(Ykt^w0`>T4}Fp|@k1O27M z%FzJdoj z>p!$xssE-t1M^4m_AnIT1izGTXM!*}wDC;%6v^;#n5A!Iau}=R4L)hu-66cS{^uuX zpw|8dF@xgi`@y7`#)l@h*2np8+l|hhW_yv)w$2xk+~tNK{^_Bo#o*zOtMp7E_d zX?Vc%cD==(CO_t~(0?0Od;%KIyhQeV5>Qu54>rrJGfXCL`ONlm-D zlKK?wRmK1Y>#(m9-aaSvZFDFA5Fk1n@~z5?d^=zfZ`&cT3JQVN;bM7d+G_%TPRQOz z{*3=(9IJ|>IZku-k!jxYJvBQBJ`vo`J6BVMnnA6nL}*$Qkp@<47C(V?XXr=>sF5}b z0aOzD*`w{NWtG@4?`dDex;@?NWi-M))yuxJiD>(@kgs@Jh>nAwew)*W})bU&lW!y-X zsgj)WQ%=0qwJ*LP0m(N>w{N{5zs3m}Q-x+#-P~i^-1bE*eKO{pI8Ti)F9w1SvH5nL zD`!=UUw!;Bh;oSXi>`yN(5D?u018fo;eLs*tr!cQ=Ml>=S;y}wuKYq4A`*q*4DFSB zuxtYu&uX&k7SRY~J6en}%>vX-{H=n*?~ay0i8h|+Z!!iPE#XRLz;IHKySKqHWg>W6 zPD{drqeY#Xk%Mzq`Ms=XAyvcCYM#=N!E?&~xF|iXrk-TtXdyaFPzJyD;)l0vt;Cvp}>$sv(y{x2>hvaA-A78zA9+(^$m#d(UYvP|irVlGUfVl94c%Q0?_TRnn!WQKowb)~5#Xpg;l<@?Te_0>o?f0fnm&va{JPB2 z;5ZiO&(P?5S;k=S+|eoqg}whG802}$clUT*%P=o)V`o~l8P4w-qI4(^pS<_pFS%L; z4j5eC<{;nl;4BH#Y?fwJ(0s{u4l!`O#$YhcB=IhftNkdxolTrBmqvs&flTnac_)|7 zPSaF;4?a9mV^xSaxRlwo3VqY!v*(BJAHDBtMWKk~RE5Day!Oq|+cmtv<6@rly_hH7 z7W3rjy?z%1FYvgSp2_TqM>6}M@16O;*UpTlpw;KD)HE}kUncg^IUfA?4%5V)zjHJ~ zb?qa4&p(&)O6*m>axZxpf~zk=Lc^0@uQcogEi~?!24c);C?T= zuG8y&#+Ys9%KN29V?Nnjo1fLZI5~fQK5(^~7bkd=Z9EhIcq(Q`zL(-mhn-=%%Zsxs z&+|VYT)J8T$=_RaZ@ozA`*X#q(F4pIz&wD?g55S7|v%Z&C&o2(oT{$0UI4}iA z#SNx$U*P?sdGHce>&cNam|=JaMVPA<(SVkS)p=})?@^R@h`htnjW@(kb;}XA%`e_+5ghc zW#%g2-@3Mk_<~2?*VW^a{I12}shVGlucP!-Ox#5xcrdR@!c&pCav9)Ie&;S_h8Bpc z`Rn2Qu0a)7cu`8~MKjaJoSIf9O3Cxgw&k?NUiGJ}Ry7b)UT9}~&J@oe0_EHu-EsaOs-pbAerXIOl znH@Pek4wsJMxL>)<+)o|wPU~VX_*!uUSc{Hl?C$O-k11+*}+;qkHh6+1zjMjq5ZGx zIF`bhmINy@0|zwl_V(-f)0eKiUC+f$M)j>vW%fqK@-}!$KNE5ATnx$84O7UJfFtNz z#@Y40Ob=g{A!2^0F+Y6r-uqv71VLkGU#5Y6Rb`UEt9D_{ewMq{m3|jY5uh|zLEmLd zHLvl$);`w7!sDg9(NZMM^hX!+TIE|4E?+m>`})}3s_m%3!`F$TTU!bW*&Pdzq-mBY zad^qa_MspDPJL4D)){=gxx242HCDir=ePJ(yshm$&(easJ3Cx;HK_OXur+!(--&m_ z1=Z7T2i`RymvNV;X_^;R?AW*KariwodxFb3`b9?zq7f;qAFCn%QTMYP{i5e}xk>_G zx2|9EvM%7q5q#umO?ou1OyPKN^~!0O@0c1#$P6nc(oj+Rn5(R$cKK}%F<<_$8@rsp;d$Og)xnhZH7Wa!=Se(23>XnPb zl*re~EY09BdR*Pt{j3gN6P#fao`fD1vBxF8Jod#}Uc&$3=Q5+Nv)kb`uv|~fW^TFL z;XSlmFYISwx!d4Iuv||JX>Pe&;!m(%Pb_L~y<6s3FHg3_v|gsc3ARI}2lDOlFaTgX zY-|pIt#LI1U>l5W0e~&>IRIcgtZoi~?QuRpU^~oj4uLK6!pp&yc;V#)9ZiE+y^-5v zgs}SUFv9xkx5fo&_1oZr&DC#-1;XmL!vgE8-yZ*iWLkNVtBC~zLNx1CYf{OM}jSI+!tX4_ZA{OMuaSG@dbT|1y=`O~p>L8*GL zPwqrr^{7{g&J6OktdkR$c^14JkSNzOPG|TlishfEyxL3O$rpNw4g=wLSfZyO_{sAD zmK{!2o&)tb-q13^G_@0Km~Iwko9_kHTRmU4RBPuDHb=h|E) zIP0|?@5b6$=-bbFUS6CXe(ScFILlbZ5d$94|4Pp3y@%LE4#e&Z zelT>_m!B5KFKA>Y!pnl{W!s|5YV|!V*Xur}Nt9+Tg*Oe@(!bFYr#~M!)wS46(5tE2 zvfk|+-*rSF{*coqnja;XF$}k)f{ZudT>Wqo3m}&GVZyi(D$vWZ=7`lZ4T;pJ+B9cuiPG-*Ms2IXas9$Yf05pmL&mmu|Qycb>L2 zi}K}QJALMKr-04MICz$3<#Y=h1zrf_|QGA$nDuFvvej!f_V+;*8E0#QO<~N&7yqK-nq-J*XtJL#~p^V$ZuUK z*LpOs2k4nlt{h-El;64IrLH$=_SPW>uwyPbSUoLC%Co5$yYhV5@wTSiJiB~0aAiEh zSivBU>uEe*>Fx5_YgfGhaVf8p7%%vhex2X6isu)7t|NFz{V2^}7ZSUQ!n1zbggv`* zwn=b$@D^zsbn&@QA3fttz(dD3vr)psN#jR#MbzK5Y}EqYU(2?3kv{Ka(+hp#cPF2C z-^nNbck;hiEBR4d{6ZW&GCF8h9|T^B*VWEKXgulh>npT|p1G5F@MFi7SYEx`BA@`n*4n3owzo-$6KR!=DO(d<*UItDttpntD^y1_{s-EFYEbOOeaEj|E$#TVSE4)n)<>>p zokn|Chu(}*-+#z%jfNwTMHJRoex~C<$Mr4OFiFZcjh!1g*2IMZN#OkY;zdN z>mGbdTf6<)qf5`K2g1E7AlER#$5<58K&co!$VOHnyY#wGVz zxsW+ogSnq|s^KgdJ6dN0v$)K+v)~c-N8Q4* zlk)8$IgMTQqM8?5)^!4WHDBzbIrP=0fkkrdo8W2i)v!~n2d<gLz>v5GOx|*$RbJb^E^R zPXJPIu@Y(OOAjNc;P_9(%oF+mDKz$;Fvlq!81um^Ukk~I{NGa5near54kxvwgGMZU zRVWSr_eGEe7j#JGnT%$>+(ZK`DE@t~5lLVDE|X-f;Y_Gdr0+En*-A{95TtL{oM4JZ zDqAFCQgp;!Br<%cT@ta{{vcJ%eV0|=4P@QDw%XZFWEf(Gohx7aw)-;5LB~y2!;(;h zT>B^FTA17*58Dj1Kf3Tn3kTDA$3|kF;W`j=E4o3+DU(7*zDmt4w7?W(-mni_fIT>) zBlCP!)F8N}>dX4PjZmdE1CHQ*(L=!Yp|A!oygD}nQQ$6%1e-IhWre~J)E)Zr{_Xi@ z*iBPS*m;u3R7rh|U)WpeFEF?>9Q1wFm^*NxY3TM|`J(`U1&zrD;#Mc3@Kr+Lz=a)y z*WMg17=am&K5i+I2t&=qrOz{@2P(f6Q^{?Q3Yg*(m76L5T7gP18ND7a!X)np7yiZy zBmcSFi*GzoAEmE!9>Ln5622UI8xT=oEByVDqHcw+?Y{&oEcU*g7aj3ze>-3XUUM_| zkQ2xiyMlS*@sQzxN;_A%n#jUUmzDiw--j9}Z!tbV3Oiv&-aN-qSf}x_+a38!5|R9L z&xYNz-b*$T(JWPonlAQX7B9OOU5}YO1c!B~g6Gj}U-`Vt-q@#NzU+0)jPHTVMSB#3 ztL96uryC5>scRERl>eC0ClqbN)&(^2&I02KW_AVpy90?Z`(N?58w z(8LZ@&N8L7948BO9L}hP4*Yl#W`WaD8)5{*j$as>!c*0{j;D`=C8}+bZbDPkzS(*j z)NDNsV78tHHd{Xgpy-F76#W>0q921&^iu$eehNy_vj7x53qw)gYe?AV`A1QOwqYoM z?+(2Nwg4?G1zuD0G!FYp%U=#(V(QVSFA5OX$g%qdgZXV<4|?95kPkOPJ~LX|XbCqi zo@2Zf3NGbSx?QmOj%FF(i}?6{=(g5j z_2$asXaTfZPX`+$LxQi_vr0plS#y<$yV6(9tpUcm3D^2op8-T&>``cNW_-d(3u!4@lTdyI? z+kNfmx{jsxwXdIG1s#OzH=?V2w%NHBF?+)chG6%$?0N0AlB==*XHCXJ(&H;Hc^^Gt z@{-qAUZdY@!IOa~z$f`w$=q@=XSi&|_3*6I_jO(f0AqcjHJ^%3PfoEaBB*j0cD_g|umtYO~-Ac$fphIZFP{QvrUij+GV4;N_ zi|$KbyPOad8_swRc5@E}=tGglF#f*0NCz$~4X@8TFFcwPfM*U&T}u$X2xmteQNEm* z2Z##e#~7mg#a;Ij6)FlF%DLBO001vb*0v1;T zQ-?w1g(fY~ym`?> z5tX3jOiUotDfR#?RHXK@?Q=Ld_vQrwsC@<3!wmPB=ACE0s$?v{ijZevN@E-y&wW*7 zt1$Md%6cpiohulY?hHv6i;7?x9>cdz^Cj(V$LbU-&H2?Lcd4&Qsz%h62C z1$!^!DU~Xj_BsdffyetkcW>17MI=Bv9{q1n6Eo^wb#8N>Wni|wQE|oITaaOC>GQ(Z zSx^LPZ@bn|s&sXF2yo4?qg427kUBj;=5JzOXmY zR$bPrJ(1D#VF>fOg8H+%kul$Bdc#QA`|rD*ljqN$J^AUWFPZ>kP0)arw@5=f+79r0 zohLtmp$MaaWp9_KPZE zcwkbE)IQR!A5MIQTL396<|0?V&WIscVX?;*#%Diz40kQW;F<0_nTjIzM1%nrb|m17 zp0NLLGzh$>@GkS!J^I5{*xp|sh9A8xDiW-q*#F`+9{YvQl=`o|W#WJFTG+akm@w+- zSK&(+0i>WZ^{Yr@j};#Xu4aFzN{?UVe4KNSt&|MF4dN7LHK(G-*ZSvHdcv%O{+`Hj zhesb%b(@HID)3vLC_~W4%STSLTUpH5+4ARt(D^GM;*qYfiomNr{#C8Fiw&lXz1Fa8 zq9mAJ^V%z5GbLWfgf8bo7rBh8%XCpwDd1?433ClDMG7z7;Wd`>xxhMu-Uo}Bqj?Lx z57CN|ZenRHF!beFmPnylp%_ouIexi*_jM^_!4671vk`A29@XpTF+3+HQsnxOshoWW z$aH{}X-89z^t;DL6j+aC8sqy?vt0aEN-TpOmjz43?N)#c6G1#l)Ga&=sEq|HHT*o7 z6-B21=lED>@f{v+eQaOW$B&Qh8L)@gay|iW2qp~ih-wC-1wRj&EZDr%1)K33cmOo0 z$QftuL!XFWE1ZVC^oMziEu5|78{bJD?SzG3ZT48jUkXE5&3lP?<+Sv1T)MOG-0?Poc)Fc>7He zZ&$=y^Iafb$Nu<%^#uF+EXm-14s`Fhp+zPayE9;KPurxQvS9 z3|_!LZh$A?6D)8L!KU+6{!T!*3(}_XGmJ8H-ZMSECNdw!;A@&MW*g$QFz(^%w9()j zViE6}{#_PbSviT1*j%QUQB>yopO2ovU;NJfRi*J^Kl|zI^kKjEwJe$7;xmyzBz;c-bHlK z3reS+8F4w8$Vl>paCt~pa5}~UXEe<~(BJqp_}JlQ8c!s@Z=$WqQ7c?KW9X@nu}cb( zBRdE`Z9GHr1ue@!V_}vt1>TW~x#jf*;IQ=@buekmL@rgli@aT#C;f5`C!RrpgiX#x z&9bdx2R!6A;9j6U2*oFq>iETC7e-x+iOA7lx44H0p-a52 zm&Wc847x7VynYUc_ZY??O1PYpP{6yCnM4m$%y=?+P?ZPO+7X^)38CIuF8HS?@3w{o zXVz4xY0fhk5D?cXJ|nB~*~+%>H1IbS6Ip1=7catb5THlJ#t8MO>u&DAS^tc*s8M0? zy&{JXa2AQYKp#IV^GxYr`dp-OrewMslLClefd|2%{$&ocrgrBM&S)3NkZMqw$tgX^ z=TIC?O%tjwfuR*#6K8@mW^e=ML`jLc2J30dkNRch&r+K}+?;e*@QP2EyS5%(i^8^F!(rW;Z!Jci)aKDDf4`!^-~RVrC4vg`nh{IH)=L|Rd! zNb>~O0L%rMW&j3&oid0z?eAcE(w40f*LU!xT3LJ>Q9%GMFo5A{AVldhD)BJcv*XHc z8UIRFx!X|OO0ZmWvy!|pw{|c${+Xwpnk4)*E*hXQX^}bm)Qg7G-?ZY6TG7_%Jv_JH zmYmimTM$tAoSc_)#^*2x%rbkm^d$>1$211=`c@_hu1UI;1)T55c`L|_y`q{fjx5wi z>{cWRRVdwHFlLr9#X?N+h$pz;g4}OnFcy&R!rzw7&YB+hldFNRpvLhF(gJ^y+Cowi zUvpvMnvc34Wnq0(TGePO)kF4|-%3@S!sIGsTF%X1s8`LeH*z||^BG}YAa>?rDBk@E ziVooAy;U51ONo3EyUd=Cq^tB6kNb&TFO;$0b57Os5sQwv)dRGz} zQXF9B(KOnD?pAVzXK^g?GEZR0Cn}$thw3UPYqq!AMXb4QNl5mo)tAicH?_W_TE`li zsmhlt%J;Hr@Y;#3#b{{-VQrCic@efxSGO8LArOcO> zBbRT?P-%c#v7>UzfczYcYqVgwQu9+*;$H>945mz?t>NXK!pf!=+3a!fwb5yVS^Lx; z-&~HbS974uvV%xvd~r(sPk^^2xgpspoA9|z7Up-Psx0Oyizxvt0lQ!`pL%&kd@NB_ z?Gk;o%fI(CQJB4yX5Ke@jmfm*(wQp9nvRZ|R9CF{=9(SU&z-J3b!v7I24n7TFyv+p zW+xRIgu@R8ddT*}YJcVWGn3NvW;5$S+Lk)M8J>Y3d=3A4g}<(^&w_FJOIV2KQBm69 z*z}D$Vh38y#j*{vMcuiu4OegtP2(=_2tiMQnK`%O4=OmK?iA7DkP(MZp8R)HTeQO7 zHqG7gpq-~T3cRA)=`eqOp1HG7OnJCWQA#6BV_^Y3>&`fMR_BG7+oeii@mUm6;RJaU z5D7+QLLNp#W8)%cp)d{vZ!a<>QZHnZAn*rwW^NjSHP&zQ1^$#A*#YHm?TL1=60MaG z)PRlhf&s8n1gxC^{idxZj(T1#H&Vp_s4Hx65YrWBP_Tu96+(D}3Jh~__-Ka*CY=|E zN3fcr4gH6<_=%S0u)T-uM?-NVLPLUS{%gUtx^HJ_yUFKskRCkh2DnfuEQ6YLJaC^K zAZTVkSkNhi-zj(=DRr(u=@WUba&%kp08e4#7!t|Is4oLn6wy&DmU9z(zpTYA$Jtn9 z!gz4-XiN(15c!*s{x3By*&3ftrqr@0IYzaIKMon{2K+CmWUPftdAM;}>0X>;vE&P|o z$jfH;7h^~?M#5w=#vERatj*X5(_%cNcL#pqb0g_}-)#@T9%B!v1N|6Ta61#}l4+)H zS-4#?+)mNPdhv?QJi2P$+QFsq%dC16_w7tkCow915;;b)Fz;|Fl#dfG;o>Nu8(d08Z0-f4vJV%Y-n3qMGzfQ_EA%NJz+8k6-%n1-*(q1 z?X;uDIv6*m8ZlW7gRO|Vkf=UMpctzis8f|EBGBu zJaK1QJ_Y5%>s;;DU7~e95F4t#SD~0-6CQMe`YvqPbk>7|{283MGHBR#CAG>O^ORCE z_WEI?1*ygSu(3)7_vfORp-QhG2*pWwf4BZ$mXl}I7d`W`YfASpy&sFlLW;{dxOQOV z=wXV2!dS{vnx>Y`%F8ymN0(d5>TgY4F9iV(`exK0V(=H2#NIpx|JckrcdMOc?_4E2 z1*Yxhn0i7-xf~1QEEG25gcBgSG1!`n5}9y#l8&s9xXu{mf)|*vr^Fny$$vL>W~h<` z#Jcntx+7(wa5a+J@gCB%4LNiE3#Swd4y8z^=&n%LB{SJvGxJ!4jPiH{MDCJyZY!)o zXUmX`$vZ%Y+%VKf$a#vCAB>P&jEwiAqN<4Ln|^9bKe@+t(NXQ~au`}H>P>{CWoS?! zND(}Do4Q-iD@HRDY2Bj<{L+pyzY!N(AziNQaPA*+z^P7*yAN#)Zo@@V6`-P1#U^D= z>eM-q7?oE?^|`RpWhxHgV-z+vVQUxV@feqD%z=UU){3b@6u7Y|fOa8x>|(u2S3Wpe z_YT)4v5hK*)e3n{Lmz(vm{t3`#VjAM3z89-2c}u}v_IpLHzgh9NtoYhW_WeZ@%j1Fr=Ut!RRHG^9 zs#TUl5BwcD1lkbzjoSW;Xjrp!^5QagYL1YRtqKyI)`=x`Fo##LCS@#&ThtsFF;Rkt z(uRi(RSfQd)HDe+`38abJXw#Xwk^kkvYhk8Glx-)8ypOmt0>hJE5}uXpxTDv#CIyf z_&FrNfd$mKal0_CjqEG3%{r0|ZCSOPYn2;q+NZUHipZl7swzv4g z_a4H$L8k{je)&RpHo~0sF)0N|t z@kA>OEvUGv#Sp2Ihn?=E_@l^{`LI@_~MB`}v{xW3B)$$}b)*h5Gd zccp>#j}(_BXDXM-lN(WCR23^bLgD8D)}vxK-m!ShGCbp2b;hVFI3)i%h7U@6&Tz#-;epy4jYGTtW%(p!{TPZr&B$r>J13+b-*}9Dd{BJ&w3K6_S)cz$x)js7$a0wUEMQQnRCHZC@W)HOSBX^dC}GvAOVsnfdCUrgji!^HV<;n%gvLVfAa+k zNl=12DT#HFr657T@%iunzVK@L6s8GNf(qdbTU+BG+fbk9(Z&Cs?A+p9x~fJoji1$M z+2VS7%wIYza=z%2a+TL<#JVujE`=X6n+vFDHXPSFrF6b^IA38ndt>HFOCJqVamuP7JPW{1n?Sn{;(7gjS=+$LrR zfw&;py20!Wderk6@5%qfH1-&;y|OhFEHFk*Uo;fmR{&UMjr^60k$r8*&=|Md=&d$( zn(=J414^=l!7K8;h-+vPye3zR$#9tjQ}X+~&|VcXMqaZFGuOF8x0~qJq3m&?lfXC9 z5B65JTwZ}>`PfOyn)W0gzr_a1mRf%hr@cV!5JULf&q@s2bHBm)YP8-#$z{=Li=UCV z8S)P0a3?il9ba{w9aIZ#yH@1)A&UF#liA!|h18&g3McvyZV>6>0)$8~OW~PNA0$(s%L4hH3=co*B8Z3$!q-^}D>ROQ265B_VQvk{F0QW9_peAl; zz2mv|1tDxNMHD_AMT{R%r&7Ng7tIGFK8XiQ?rW?&wl3huDoUg{U&%fYrI*inWh&9O zK+LJD1+wwd`d(7kbX$7vCXIU2vn5rOPXc(+NpN#>bA?Pq_udm}NJmAtkOfPAS%hoq z9$BjPsPJZ(?pD=Ng1K&VCtQ3hYfjD^y(65o_>($oWgHghC|I(qc7wCB3QDch3bi== zyfOQ~)kRW9oSbkB)wor9r$HJoLuv!FXYP}I0MR?zH6DDSDE7_?8P`kk9|&)HTD1Zc zXkf+E1`7!hzfP>eI3Z-df^*fFzPP6bd5DPRp27# zAY4?jXa~3-vl^87JZ1-(SdVNJCCdaozxqmCiZA@*#e%usJuQ962FR7;Z-KO=i0Xn& zn2v5>??X<(Zj_ao))X+r$U(Atf2Uer(KrBB`6ij4kH&RtVlx3Ptz>QGC(-ZYv>qc?eHi#jDfvPdxa(b1WGZ45iKkJ}k{=KM5M-i}22 zA~u{MJa8nTa!2Z|V7k{~g9N;U4HQz2O%9U(=th+6FVQ&>s{280x1CfCPyxM_G&(*F zoFYlSdO-O-$n3}F_#x@iC&Cnr$Gj-N7EIenT3Q*uNW+1WRidJiv?Hv`xT-v|RgorB zNkcgXMH*PQq{#KWjl`{m$&R`=F%TuA8O1|LTga!P6bsY8H zs~?&0-^k#SdZwPD6RuTxz5Ygw>t+Uc9xZf|k@uYV5M z&2Iri8x|N1E}(kS?+ z-|M{kF-PJhO5neD!{3K{2k)Og^;Z>rJ1`7b=l52*!;Xm|?iDxrV0Q0xGl>nemz zL@cFDgzUe?AZS7zoJADzwqtoMIgJie0k7h&r@dAN7X{%DY4Z%Q$xT%7 zTD5Y!ZiSuuFjt>FyQN5KH$DWQS5n&x6 z8Qt2lp}bfZ5V_g6R?305m(fOMp{_*LKV`CBYdkDa^^ef;_DUwXLnX9~yDysl88 zY?#YH62JU<_s)fa)Cc77>PdVdCuNhy-9tEZN77PCY3an1+y}|J-9dE^AXxD>G915> ztuF7(Wkg_KglKhKYlm1OJOzx#kF08!RiHXWlyy>tdTJ%9;*^XLQn)3@^bGj#GRH6A zOeWkS1Z<$hGG!@$w2BLyBMng9{Ss3m#x37xnkJ4nM>oLYs~HVct38T%G}0dgMQgiWXAMw4{uIi@3K zfY!B2Gp8AAJg>zUR%bFA7>ld&Ojnd*HS`9qgA-7J+GjG|6 zfCu*F9aaPA5<$HX%~5ywvu!5rnb@KuGLT)C!%BUA+?7_okm&J&orGpUc64*iu=jVw z8Ny*BEM7L)))TDjCismGT?0%tatfG#7_91bm`#}Ogeb{v+F9wa_?$0vva_2J*qk4Nc;t^%{^AuE_?O*F79RYPKN$ek zxWM3Zky4V(zqJvsH`VrC9G1gsklmi1JBue_OcvKRk;1;#$1d=pj~#jN``tT2a_Xl2 zD?WtUC{CV!xPIz?OZAnTK;9w^m#3#il&ZLr0Qge=B;syc%J?uaz;OPOPF6tkx6w8z zCJ!^0bASOwnX^{zJckAS3j;A|hrsJ5x#pp$rH2S=5fU6VHn^ykgr4iDYyXLid_(BPHv9%Vx|cw zcLU$7wk0>Jz;?>w^Ow2JoKvL>NbpX^8%g)I=FqDU0e6j`!0o~Mn~)2IVi`mP;ha34 z(SGk+A02c=&5pD5-Gg`d<-VrV{4EBcKk(cL)q4`e$qZv~{5aEjS}2)JrHTsR{LNkH zi8Sv}@YR8H5^Y9-CcP7}6;@nS*sKBjE)qnlm6x=UPg3;iw!$V8YKu`>l9Bt?vNo({ z3FAFY*bbj$y%g7PqEok=uVQS6`n*hY*b zjUOC+utCIj{FU^-q^;B6@Kylx%c=KC8b1Or{2A8 zya{eqj~eVVrh2gPWC>3&g=rG*I}{xe-~_L!PR=k*+;M&4>TkdZbc&-P5Km6n2<;EV z@3RU0IXjN*!s!IS+isgpC50x!*H>}6p%8)UWSX0?I!eHj&gKiZPM*>Maq1vfsavUM zCG_fQfM!mS>UOCKlGNUL8kQ6jRUdoPOAw@E4zOFVcQY9dpNOC=>Wp1ZQcRD^e{~Xt zpn^X^*FFxg4EvSb=ZUkzdAs>Az%@7hg>!5)DYlcE4AjAnrT3hMUd_`Qbm+|rr&X~U z4P0>%elmZob5IMdFLCUd9?d3(*i$?Uz{K;{i$fkigda@h(s8%qUfHd_DN-5C5Btxt zr>K>NfqGuFRG0~mQk&ID8j;3_isvMFj6uNQ{Hr1lojA{XVG|cbTT0yA>o4|TIWA~; zjP=MytNE@G5p9tif7;M#i-KA=9s2FB@BjOsF&I&=gJ%!zB$;`BoflC;s*!{dNt*=C6q6Fg+2 zz|EC$AuV>?rWQJJw>f5OweVg&)H9D-OE!@6}KJ5Ua1;B!prAgQTj5l|^C7^Ew>6 zG36_@1a5%F4)=&6IkX9Q40Qi7zCkF-)G9|>ofcJQ$0tW_YeH9~4+hW57+XJ}AwG-K z{K=bwh+gIsLoF*98ELue*#I4;JwWeDIdcN^G<5;AdasiJeIjYm(PJ-CsYS)zaob8{eE zN|SEVuN5X6KkZiq!`i%b7a#|j8Q*R%=(`YS0Eef~eoV5lvVQ*!ZZ+nb>Eka*lkEwO zex}ZFs!-^`b$vydc{}ut{du;XW9vB6b|>R-RWTs#c*i+_Q@YuhM{wu5%Nhv0?J@p> zrN3AA^RoIR8x{9Ex!ss+ABp*mQYX;gv)N8MDysz4#w0s5@ao``z7vLavf_c)aah)c zUXS|o%jg5`2)UCHi((#gls3}yK)Bt|?{!FlgEjx^IFpxgu@uVwBXIWbcKG36({UIj z^S&<9A@)yQBqN(Y)666(e0X;ksOB(g1hdMv=ZK|jEeGeg8J4c8qzIVN|L9@V_bN^& zG8dOr!AvWTKXS%0#*y*Y6=f^s95phf2$IP3{@1r3Uft4Q>NKBGxRjG($%SFQ#g<+b zXS;Js5GF>kbHw~3lfEJeV)AIIvZ^(kdQtc%_8NLZEZnJT$W+6 zjHR=fc^3`QO>kGiQm3z8!Dr+W)qYPM?zEoFo-^!iW&)k)iHrT!H=J2T$dodW8szag z6f4YxNtTly*bU0b!(TwszV-!aAQ_fq<0M><(qZr($YPaWas(!#;SN+v6dvhfmQ*xf zh@%dam1K%l)MKTi5OR%#^X%hKI4n=++PC=2P4EjzlMp2W%O4uHOuUBDJ1Y1pct&{# zTln949Yt4I^AhSMCjpJ6THb))2`5l zy5NCgoKgVH_!_YV3l(WPRg-GsymZJG=Bpf~?mq0tMVr)1_0H1twd}CL;7%ym z@)IChDnVfa>cD+SU1D|TSlpSiga^a7ST{=sn0(jLPetDfvu!nKZ2De&ckowyZo774 zM@h%?q4zT>ot+%d=iNQ$y*+RSqtkY=LUO=c2f;HcCl;3>$;UNVN-qTEgLBd%mOb=sh|$kjL4F-OTAG805$YCjHk35^$0K82Nb zC{J#Ah~hr`KsR?+AvLH+u@ga$8$?Q`0MRJkAbU@b?5TSaJ`kQ4ghe-ea8Ro0vy2 z0$vOFu`pK6`ARnKmBKmam04}vFjbmqPn@c zxk4r)*PD^@RDYn-6+mfN7U3FxoWPbAa3M(D331tKFMAd}N5#$fT0ld7(Y*nulnW0Bg`U}fmH&a$I_S>mfTkRCR#d1EupyIu zZ3eM%u-I>pFp5Ur3vJNkHfz*jdRqkZYE2l#=y}0ZUIdQ+29tgRWbMXKQFlquRRkh3 z;d)+ zl-Q4K7A4CBotpYeT#7IJdFVWF#Cux$kPVP4$KL`*Pt{d-wF7$}kPVfj%Vko_Zf;UZOnE7-JtU5n&WI$VE zHp1W)udS&hgG5pkDk*(wz69u0Gt?81MChDD?x ztIdQ^rSTFs)!G|MDw@@VyhFEye%8ph;f7)5m7RTe8sy7)PQIQ2u)9@>O~Hq9_`vy? z*U}_8DXmtOtza}ZnZ+)P6y*oJ(V>mJffAay>OINoye_XFK@{Q}Oe7kEIY4%^1Ypeg zBuO*p3vpOfE3ZZEyI?2!HCs1UhO4hF#DsZlDiFUvo4u6efP^NPspXmd!p&3Qtz%I7 zBkj+Jt7n*2!SWM+nc<5X!zD*AVqB~n*U;VZhTe`1qIe9)cZ_#$u~f@pir8T)Eg~1H zv_LA`$m?p3dI|Ozr?CEnTFX)P#x)Q$oD%rCT*bX30-l$Kx7VjZ7+hT?si;c6cB4qj zkJAWM1N?9Z{X6q;ccPZKHeeoVaRDkic_Ah^2D6g&24l$L%c@gy=@Kz_QtXgx33uG8 zKp3|1s41ZyuO<&7o{73rW;|~oA2q%WKgOGaguTV4%O#3Yyv@nBhn+4nC1IMp#?Dn# zf`<~7LOeR~A?rw>!p9-dIhsxJBLzP<`9`EviINEJi>-dkdX0wH-{VN&_J}q~sX<;^ zN1D{Vs#Ob-D^SBfD{dhNgIer&^2$C)KSpVsT-xCZ)TEU1n&Y_vx+cd8Y%BV$^6W2i znkK+x=-mAAcm)$`srGX@RwuKJFEo-csas7mA zJU3(;6qiN{1-zHlSCTFd@fTwy3f)q=i#hgB;1)1WKWKjGP!NIY^@5#lOE&J>>D(hKCyej`#`1 ze&V0~^6TBZK-I5f!u-m`meI$K+FtGKfgndj-M4?yLw4=8v0BFLgwT)UPyMg2zH-yp za6WMGj%#&AsRtpVm#NfAM46b5HP&QCC5YS=hWz!aA%H1KBsv#1k9E3~`l4@4hhv8G zDNw0oevcl|s0hj1CP`uJ>B+(N+FGtST8(nJfoh|iui?DNOPz-2&AvS{o7#zOiwA{< zn87g#BdQVY_#)7UYGej)sV9icbK9cf-ulM7P?(;T5UtR3=Y9_@@o9bI9U~H)dI8QP zCh>8jNB2q2??rH0N8%zqzZU6;W~l^^ zw#D>~pu$A;#y}nuqEG)jZUVHS=?< zv!$wSs=CN@9&u8Bt-4E-W<t~k z+(YPpz{c>7-3=6+_3Un9&8xc!-sCKZWIl~G>9>Y`O6frL{W1{4KaKsh4??t##uFK+ z12k2VMAL6Bs)wp+Yb4n#&pNjnYS`KRWpWEexB3?Acs+}?fnnn4KjI1C*3C2+4b)Th z99Av(G58g)8E>O%_u`nX1gt8~VPmzj(yRmEXKj^;VP_)AwB@m#PhGwpKBJ zDaDB>G@+jJzF>=v=xXP($$F!Ws33v#CG%PQ^%Rh*??LT2ToKYkgH|Sb_$R5GjgCA@ zS2Cr}ij(=mYt!w^w}VYN>&s_eUOMz~J-F$UsHw?tD_`)OWqsyvQjlpat|eY&szprA zS@zUz+CTByhN7=FjYa&)D%2kK8ov`Nj@Q~vyB?thrY{&}2uvJlvvLS-j@e(QnbXhk z*tFBa>7Ditur>m}&^47U@U=74A-wdlBPZDb&(7EtXJ3ndr+=4z>IYYS^)1H?ADDyC z5$TwG!f0qh0{gC84BW(SU7U|T)OJ=!300`*Em;HD*HJ=yLQZ5CV|Z%2BO+%_l=ck2Its zGXxe_me7MeXp# zC*E~zq((CL#;Ws36rGumly37I<%)(6jUAKf2MfY8J0 zfRj#>{xQC}W`emdq|e_qr@(h`q24q-NQZr zcgcpiCVCea<|+zGhPRn;VWxr$uR$(xjpHQ~bVr#jB}xIh#e7w~nnV?wmss-9=1IG7 zIk4w1K&@BG>0<}Skbn?cif#7fw|Dk(-O(k|a)Q7Nwoto8E+|-bJydgFd!DPh$@sCP* z`p4&9xfy;wJ})y#=_4JjJAK13&)3fN+Z9hEy}^4Cmnd>Qy#rNLjkbKco0pYG&0?*Q z-ryE%5nt%Lj`=2I@@f2h1TJkG1{WS3n9GLqXu}#EbLgho@H&pL7lv98{_$nxFby`V zyvip5R)yv~U8RnaJirNMJhnHS|6V5;kI4(28o!Fev|8bKxEty;5%RoV$%j;hs7Z$N z&+vCrc1js}5!`HE86UuLRz0I?sr^re$7e3|1$3=?6$IP=`81H>MOCg@O!}9kXyGy1#`!}jp0W+4` z*6AVvwCVUit@4!4HT-)M7L`gu#(FoppD81X{55vQmAqY?2Y_i;<5Y9!MjMqg&M#MRqPv zNNP6g0Y8)ht`?Q`g8QZAvobCo`DXewKTB@n8&*KkRIKP$-gFk$THl20L5Mo*%4{om zyrkectRSngw{=~Zethy}pxddb#&ndyj8|xD$1j9K;B(371Q^{doOzoOAHL97#mRCt zNCyTbonokWqf6CaX$=jgP7bQ%Qw-+bGjeo4CnMS0IeSW?YIRyve#0f46)1}W{%=q| zm^)O6`{>j;UJ={lgvgEVb519V{MjLQZ@Avj$bs+^2TL+-nUaH`gdAgnUswh043DUW{%S%)<>SnFW<7;fnzgqku}Vdmj5hd{IhN zbC`Jutwix0a2va|C-GQWg*rsWu?85I@JAMv!8Mn1lIIPzdOA|RaFV!N1Ew8L+-X&X z^A-Nq0aC3nV9Tp!!htbY0$k+Ilvijh$mONLH z7BFy7|HkrOm4~XMSR7zTHek0piC)pij#5vXdxD;Sh!Fl8?`5o0 z9^PWz3$)y)I8E{Y@wTApa;YiE2Br@t4eJ-MaA%1I7tlWk^uv%`G8hJNTW1*zKi4IW zjVbNH3Js?)Z?g+N+Aqn^p|VEekIyi07?T{wzssw@8d|t*L&W;4b`RMjmXbw~<$O;J zODIR?P4I!{ccJM0iwtJV>oihb(BQ0wMPwf~x`j^UkZM2-v%UND2E^ecc>m_@>zB8| zi-$>Ycl-K2h^sm1(3RZR%ofV%ejGi*uF>qy!l3+??mDrHxj3Dr2(waGPv;_Qc|jE0 zb0TZ|Xrw1%zk^B)%Zdc;-b%RH}P{Dv;!tQm@@y z&OgH=C$I+mFUk;a&M|eVvHOrF@0d?-cF`YE+IalOY2G5Y43reLEI-`#r8SMM4 zMVnsL{reYx2+i-#a2&;6``lvxZn8?pnK9RPnZjy$-riF-4*-I zO>(1Q^Y#uMJXm{ARxhiDt!-;AI~1D3X)bs(qkVSN(a+CxnUc~yM7fydc^ZdfyE7$V zE<0jUSji;NdmSzoAkbM&zzf1n4AOp1CW8^#sFNJr$IoDpeaI<2We9Q()iU5pn1l*8 z!j|lD`U4E$D_e&p+=`fzOI7ayiw(E5wLksKAA&W_01}PYGA&wt?bR;5f@SR^@y_OnNIWS4F!FN9BOA-FPP82#J0Jx z94iPN2Bf>Hb1h?6Eu+i9N$LX$SU|!7&)d_^y!%aO!t43p8X;w0Qde7Au@h_EC8z1> zOVH!38^1RJd*RwAXcsOA_K3BMy~yu^olzWK6%Vpy%$48C?Yyi#CZjmIPO|G!8?Nwe zQRbgt@0C5S=g$*nOR&tVcCFsbG(#G{R6D^ha2IE!rR1q2JiL}a!0-%;u@DNKIbhy4Efi@ zG{42XhW(ux8vcQxdDhrn9jZAV+_Yn+(j=%eK#3M6P|owb(5dH?$wy(}isM0{%JKW~ z?o~I>&sf0J?m>k$tq$4LS!7EKnRSyiN@GUR9c`n!og-&b<0Mi~RA0on>m0ra@g$V3 zg_S|QiMVxlMpoD+TvFu+WgK8l2fr}?%Z2&TOJ}@XL}*_QK!?)mA{m*)s7*wb&+~K= zENj>rOz)g<)^QnE69c>DxX=|GVYK3stbBJz3B~bzVSDKy(fn>0YQsB7Pdae zfMuLvDR@eP6*HFbRHCO;Pu2*VEDj^mR3OPx0!v@nNlJYZ8iKj`OVdV13G9*t8t#f3 zpF@e@S^JR{WgY=m1#%S7#fjhP=QovuO~`czs{{)MOz#HazC8N_llz}SZ+!uHo8M9S z;xR|WZE}aCA4%>JZkQ@zY~hbQ2N73N!CMi-TsmzG@ua!M%G53zkABl;jz4Xv5L~F0 z)nX=2TMKpvo%hhAmDSLK!;)0io0~|9Qd~jmedKP^T6NF~SxAo@9BF2JSy4)!|HWp5 zw$ecrFC>%3bb?8CE>${>e9ZEvEJ&rNwfX*VX5!ur>mzD&^U)%p=9UiPpnXj`b_gj~ zBK={d>(WU;V6_tdi%0x|rW_}aie8QgTYvUZYjG*ASR2BmTmtln6?79ia@3>EQX+AM zV4DuGV7WB{)@`vousdF;7*|&~coz(1z1idi@(3$g=orcBY9pp5b1t#cn4T@{D6)c| zD}QMhp6H({OPt@X?32YEHs}s=?*e6Nye<*Q+UL~XgAAJry8xv0^7vVYJUa?w{xR63 zSg(nJ|0sz_f5B=}{#f!Z@uxq)H-AmQ7WlyuA|RxyqQ>4-zfc%A+qRY2iPmQO+*NpE zP(yLSkwo}(6{>BgAKJU4g-cAyAn-c3pWik{-(%FqG1{eY$R~YsZ>}70pvNUaThF70 zaMZC(^S1-MkoYOtPVp9wG^LdAyqbeM{n@9qiSjwNNZD;?SV*E6S>iGDi z@BsKD$x$g9txuX#VF!ACtc*5@zt?eja@8jQRb5A$j6 z5gfi?JJNO08VbqQSJ&;~L5B%F7dM>a{nAT6Qj_=r2D3O*M4D zW;uGl08uT2*}s)N##@dd;ICMf*E-5!%XB12qZ}&@4@&J@xoi(77#HDYWYWTv7u>*J zIT5J)C43LG3d@H7V(TdQH|$Kz)heDqCFk?6fZl%ZmNz~Csy9#RPzDAquxwPNNI}wL ztecw|C0MJ|J=Wo4Tu?Ti3vB6QM_FdWW9*j7svg$=CWp4+5^Wzj+TKr6KlM|B`pQiP z?V`?jCd~k4$*n?u3p4_IQJbEw_*6dYWUj1*?y8j?3YMz0ozlz2`R)S}_Py06)3ZDB zi;P4UD4hux6cymY0xv93-=}eMhq}p~)DQep#4)wlPzvHa%y4sc>Bu}R8}U84tU*%f z7|e5tJ%8f9kx_gTP{KW@*ednYO54J_E!vMq+5$eDrWCd2OKBZgZ<2;&s;=sFn3XsG z@DC4WJ(g)(VyGUwa}@DHH?Ne-y7999sGI|K)VKBJS+t;4Y0&Dh@ihWfh!t3CIDyof zwl~hZND5GdHTOne5Zn=%abA~RZM=01JbzHD zbRT;W>E@Q&4a>xhi$otoZ?KPw0V>@tWk$RAL^(Va)e(`;NNtFHA|9H(C8*kHol|@r z?iajy`#^k;CqGjX(Otzcl9FQScnX_(NQMUK_Jx-mvugldWxQ*jYaQFZC+F*1f z4B6g)uQBVe+K#-hC=;5EN34w_UwS~z6FNpYCkDb3K9s6`K9R;QBOaBRl5#S*SAG^q zCS{n#aHvGnF}xA$7 zFXp~~qWj`0e2ctUox}1kLGUNMYQB8FrUttD&p+ZW01x;P)|7&1EuQ~(xyPa1-@E<2 z(huI@9bT$UK>Fiw%oBg8k7@hZ4hea>G{KrnHRjhX7bf@kX7xAfk^L|pq-m>TKX*Ge z1gy$PNh21)pmRf|WLkK4NGp#dh3C0N*WAj0#u_3C+KunJEKNP}8Aig=?UhPbiL?#X zP>t!ydtw>%{HEhrR!#4;qI~u`Ro3SURJm}zl{|R-Zj;ljc}`%nZbMnz$Jt{YR(8np zzHIRMZtwuuFBLvRf*og3;iFDkaH;J%=h~k0sXsxU(;5N`M$mFet$Sk4gM0yV(h`$? zH$Y=fsdUNJteFFk?$GgX7&OKnaoQ2(@Py{2kFT&eS~a$7P)18Uw{Pko^?ZJY^&hy2 zDqPm5S~OFAzpz?FW?dzj3IQO?t}FTL%1O(^UB|?6^6&0yl5S+@Qc9vcjOym2ooI7Q z2j}?==Dkh_QHm)x!N0i7q{41)`MSO9%7f!IdG$<6PE4p0dEO=|@st43&-H+$gTrIJ z7Ab#iWh32ckPchahPW>b_vG;_Q|at8_`MPx&PX^wXI>5~n2#BbHcC1AhJiP=G&|^R ztE9u#(UGvS(j@M!EQvv6Dzm>n=b6~=2%=dR4LpZz# zUc!PFq3VGGO%Lu(aHkx!D)O@igRXYd7P1etDBPCs$>n>Fu(iTuRk9|NfG+ig5PA-Q zA#Omg8kA5Fg-+UvB1TkzsUyXP(do9LC^yv53w9{_9i)zH`!z(7@b+<7yIR+1?Ey*! zIGQ0}F+7sOSSBGBWcq>-ctMr$oTnl9ITN{JYAZp@X8R(XCn=o&=RMksZI@wU=PKE3 z2;E5#a`PW`m!`kb7qHx_V4z;?30;xWV)CNWyh-LC0X4LF8v z0z`|rm{7kS>chkNmGLL^jPRC$w2>stP!d~J8-hhlJq*Is*Un^LL*bYvS`y_vm<=8z z9z?DdlPvwyR*T)4IPEc@HWSG0hkOB`Mj4gT?3OE*J+Kt7QxP(zW?zuegpQ=D8Lgd0 z_4~$Ud$_VI_|yOjIOIrl_OglF^3dIWHjC+fnZ*Vst)Gh%O}oXBX+(L zPSwwZI7#CU5-9E7fPbu5*9?TlN4zRUvy6QMjr(A)h}^V{;9ql#juoag-r+pPYDHxL zy1P18!nNSQpGSG-QD|56>UU*XAH~X?2^dW|VWwu(0JNJNa}E-kEtX{Dhe&YA9DdJD z*+b3~ueoUkx1-Lfj~k9@h|I4;J0C5m!A( z5f_yu0`sLSRP)uj4280{qG!~t3!qQVR~s7dg|%K-tNlgG=@*uoT3RmB9h|_;$i9c7 z)vsKIl zy^_*q6V%VgFiCN-H^B#TYQehVzp#!v*QN%h;b&1;L^g4;?A4XL^d|{{cc0z>QBH#Q zZ{EIsc^kZVm;`sXukYEsxl_RiRa%OAGTx6pei*1rk@>B$G2oW&3NIt0(bBCH-V|Y0 zrt~vX#8vBRToBi%xL}oiG|^Ksv4bkyl}up8kO=T0Gy2#9;DvKlQK@3ea6pp?rH_dt z+0mu0b%HhsIShQNbH0DthcS9oDy zH3iLh51ut{ahCz;@VF(;U`n!>nh#J~8TJlXzY5AcWupUiVciGfYQnlB4nH8*>>N->DVhynqFCn<+v~vB(s%@ zg$)=YiWfdBucU=Y+b5J{mKjdz{{;$!b|>ID@JM*~e6y19=_V}r!<=-2GE8Bg=`Wh8 zj(^2P?k(S=;Kpso*)KQm_WWwVPLI5|rgAyQOfl|6r_F#K+rbyOrl#5;H3%TbV!R(Q zgHMNydo`~xm=%;?X#BVoYkq;T(Y#Q+OkA%IRs*Zod5nq}U;ZvH>&FmPwj{g4_0Yrk zhat~pgP&NB?)jNK>WP$ZQ`oeBeZHtbp8l}kt?1b+_CiWZ`-=dZfVE&G?x^>2O?x@f z8>KHwU~!LOW$xx#^sMtNU&1n1`xx14bJ2jE8Fve;Q`o$$uS2>o)IjtW@X$D`_Q7^% zUF97&D`B(IXdjD*(VG~v)~B;Hl-B3hX4p`gI^AMe7NAy)*J1H7%AZCL$@GQOc$Gb& zG*2ig^Y7h(c8#97BV#3&DQb!v)WhN5(z&tSqtO>!^Ss=9E??w(hmg2BkrWCjuuk4f zKz;|ePe9gC!5nh*^#QKmzW79#(bLr+)*X!}eHV^2N&ByAEOWUJ(do|Lcn8)zT*sMC zH#@FHr0hbBU!>TbNLlcEDRsv$yrU}mqKfc!wMO(^qc7I-Zv*=C#=6CU?F(*piJ&uK z55o86qCKK{Trlt2we8g76@*Mm;f`E6bIRZu`bb4{DZQ!A=q!7&LPrJW*?>|t--qCB z&(+@P^SzE1>3Gm%Ybr+EQ?uE3%Jt7VP#;g#QNS?mT;gsLO)!1L|c zM{qwhJ>BpCbe1*S{_ILSr;n2|4ohx@VZXe59j0kqEVs=cdNm-6c!@okDIa~GE6iLq zVAsaoB=hi|N~u$LygtIw0tT>GSNJ!5jd#3I>7r89IriPO$hB1+2nWf{-iC`sJeU56c7&FVXFCz<;68o^ z3+m;}k{mSzIi655E^fvtge@=DVYa0j5Bj5?udeLeIl>Jq?6Dt97PQ*mw-X}%^e=x1 z)=Ns};!Ha$t+6{#CdsYdJQ*yv2&J46?z3L%YPTrP@-;Rb;xGTG?(JZ^PA@SiR&|jT z2ied0+Km{EwEa%)Lt-Zcmp{1DE>p+jbEz7g3K&VR3yl5+Rx3m0dh zmvs$RDxKsjBHYrH8PlJiGSym>$683MDhjzudixPldL)SE?O^i`!URJx9y`m|bHw@d z+_D?8zOc^YS?6i2?yaW7LWOQW=6%{fa?8@UwA(QtreKkBv$$LJpo_Wylt630Li0ed)k%livk{@EKlVFV`h^XgLuf^p7|$Eq zqX8T$-Xyl#FVzko{-ZUa=5j~VhMKqiqUqs%x2MJ`gOnMUhU$aOeOmjzZyOzQ^3!uE z`mCFse&JFFiTeDM746*b5x-3h#*QsE^ZT3MmG$hp`Mr~4(94pJFPHEFxRD$R?dF*3 zChEjikb7Py8HRR~M`|zyf3LQf){bP67Eq|>ok>Mx{JgOL+m$LMf`oIQd8pn7Hs(J?ho#j=P#!ikvAI@ho=q#(( zGw!;iO82qZjD!iLGYoe}m;(voVEbedENW~zmgZsPCfaa%meEt1bm1Le7w@q8FmcGN zrF^&Qx)6PtJ*k?sw$vA__o_PuUk(|DcUSyvx9N5hw1e+@v}s93+GrLf-R(V_aZRIc z;BN;Sp*w&7Gn}S>{qxUB7MkL(nf=)iWA^my&#Fzku)Tw9&(!-(dHilms_!t-TqTvG zSP#`gG{hD=$W@7dkxKaQkFkuPxP`RrYRc1)g%qHjwG_Pt6v9DwF&hRo+J( zd-5H~uk-LJHo1bb?;CR#gaf;v+CE(5aYLYSQ@+sHql2T8WqNxFk%9Xd(BP~&nz%6(|D zPB+8>cUyBxfdi~~5rIzOl*UxmZWvv_bNZ96o7G&7`N3lj)^+#+lOA5w#olt+PSn-m zp(klHNBNhj1e|pomUV&knQ+MCg2Ewi%CWN#5PpvBzeo{F)=tD%A9QI9B#_?bW&AIH zI1}KIJb?r9$}pEvOnaXN#PHA5K^5{_Gx}ULo0GogJVT&K zc!Zy+yBB328%AB>sgL4$K`qf{+W_C(1uX%AzW6fUI+X#P+{>Qs zNV_x3t5v|0MSDd2!3m?gnuT&}c|G#(N!;}X&D>cvX_SF9GDK9=Qz<5O%s<_zaeD0&s4s9Ni#}`8R z_6gJl@7 zaQpH(cFHJ`dY*ID7*P>5fWv>lzLEnBXWka^yb3jHn7L zT`kNrxr5-Z%+S)UngNrfDUDv_p@8UHrBjR0(+&J$`|i**WKX`j7vTo`aP$sA?!~tw zk$mAs6;Xp4ORU>VyyKsai`;Lz-BPT%T0)~o;1K6!m`+F#@>EjPFp_1$13x2w2YD{} z#K*`9w*=Q5yRG6tJrbgson`tY*&$?Vss!wx8T5?osuCW}!#P9`i_t85mfQfdH@XaR zYpu#$In45E`S8GA?Ie)w>`F*L+^e6s+6QcV1;*?AW4djf@IaOjlq)?JH;SZ*6^VBo z;Em$9QuAnK_ndhB`T&jcH+gl>LU;(MZX$W8ffhB|0*1IHU0DV-Qsb_${D`uvr;8e&sJ+(2mqMi@L{qkR@Z(iW|RFL3PgHu#~4(b*YCKd7s1s|PQ( zxT=~c&wAB$zPN@BAp*r;f zbrVrQ2*=8vcH%df&B05xkRYlHlZa3J{XahaCo^}jDJs^-2t(D}iat-2d&~#~_*aE> zvgy`xCHO*C!fWs~leu7I1V`~>qC_|&M|PWKJ|p%xMIEgQ z5T3~_NioMAsT=b#$aUkDHnn%})WDi(VicDYXS5uT-2ekJS9%Suh^(59%5Es55wY-R z467w!Y-b3%+r&m=uMy=1#!mC7A;r;Qt&ZmO)>6_eFTO}1eV}Z zVefgUG728!Vnz-#_;&>_zggv(a`1Ws89ky%cgLs!Zt3)4r9HLcNHBfp2v8%_3vwvv zzfVP|xK_)w7!IT>x`mBlMT-1@GYqZ>=WHlQPpfT5;|K9Xc>2>y6OJ9BxGZ158KR+k z$DlaZKN=TBBt(q6QEC*?SI+M5Dxc?|0qBNC6NU`zDpCZP3sB%MF{q=-AFY^wpo<1M zN=ktm_&1L8=8#)rx8L_3f-pPrp-7I|12B+$pr277yU0i>r7m(IMOXK3%2&N77*^kJ zJ9y(NQwsP5Ru+i@aZ#s>gwi_pVc7Kt`vP%U$0sFBk4JTzvqn!8N}@<)oib8gQ+7uitFS*2~L)F@BT0#~n# z@Juw5JGB^ODvh)=wcriyal(P)O+ z2T#c+iNCRq@cW~%pXw)$4F1bcK${KEni+in2(rZi3)VUWsc@ z#*djZq_XbPr*I1r8G)D7K$ETYKUD;fKHhS=mtj0yZP2cXd<1H=eRW`+nC%;M@P z#yC>OOdM?)ld;N~g#fnxb!gWeD8cz-K>QM$++9E+T#hN~Y4S1Vp((TGu#6D(Cop;Q zoD$3=^=vNryRu?q&GI^2>@t1qsElktAy7v}Q_t#ukwf%wDGWO2!k~Wg{HdR+)K_jQ zoFx(97dZO66}db6q7&$GxP7YQ0&aXHC9&FkSR@4yxG^G571>lPz_k71ac``{M}03I zTI_2etq%|utoUWO3hJaeE=G%PwfdfnenWRrLBf%Hi9 z1(bi0L|$2Nl*jA_NFuh}okb;wX|rKvAu zRK3xA(*Cs68Gw@`3DwH1SN(ZVLSf{o<&YxQ>!8$Q-sNRrfuw7EB(=OHz^mlW)Lo5JGB*NYr5(={V#C{=Zgh) zvA_mdVCN;VU{kfF1qe$GAO==!|9I2ha={4tkcv((vmW}Si)frJBh-_iIW@-|v8VBc zWnNh34wmV??DK(&q8BZdM>ROc<}C}-&nj-m*HQ4Xy7GyVyu#A5IS+Dxy)2-QDM~Ns z&#umu-+xv~CNqv9wUot~G!1ELzpbtzc0iSE3CyDI268dFr2@J$Q=W2oZ_kXoQxS1& zF?Yinh5K?xoNKHOwplw+b5-!{13A`MaD0u0>zlvTdjjJ6N&`EFiT9u?uDqi=lXx^MAb zURGUal-M@XxCOZ}E<9>G2(?zO5Mb9e$x(dVB}2@5J_DBfiC*+|=WHus|4T{cq`J zW3GENZKolGzEImi$K}T)qm&fOyLqtNz@htoKl(|JO5j*OX?n+uHyBkUev?izp84v_ z$YmNx!>V>cjY?MFzIM7FDOH|oFFEFj^sI@JMXct8tt1@P*Dn0nZRQYwNrI$;?Q`IQ zxHB(KmAi!OdsCgu`fnx|FKTSe$mKbWK8dXgAIYS#);b9=2?~ppFhiM3I-ZsFe8t5@ zi*TJ_fqKkoO9j{#IgrW~H_Mgu_9V;iK zX%Iskjjvh}pu}o!y89}mL*;Pl+`%re%xJf$iXXehNUg-4Ul2-!N30vqG+O;O7e*x1 z`=SYEXjW8pw6H4FCIMHDOOc%}PNOomKo`(PaGU28y43x_tfyeS!d?_**L!XqTG?GcVF+YHU33|b_WF#Y14m4>)U;(}J+#GvO#y%x>DPxz+srrVNo+>&; zGEtx~=;S-p?{{3_kENg$uIN{IF4F5Fy)M%0BE2rs>mt2QB)txcl&{Io{VsA1{f zUc(jgb%I(Z^0Pdnt5K(mH@;Bg0aXpub~Nm78Ub~QS!a$$e|6jSi*{koI~`>Z#DA-! zCF_E^c~c`+ZAp1%Xu4}nL%X71A2&$M?+zUtA?CR+?H7y^(*nsCf@q>1n2Y5-m+}io zT)BhJd)n}!X)%r0)&pa-W_hTG4c>F_V3?3c!@wE8%!Q#_ew%Y)9<&*bR!FjDt%iD` zl3kvZ&!57w-^p}v13&w;wHOteGoSYNn)3jYo;gEi+9oOS3;;<}g#?|2xWoeJq+H2i zkcEo`XxJYfxV`rYX9!Xv{VH@(Q)4VnVmVg9o{7az((;lr%{|}m;CzNI(2%VtzG7nh zIDI&?Pt~%`-{gloDICp$;>98+rRX-4AuU3gk^1{E&(Zf#y({<*^4I4w8(hFtFduT1 z^n%AH61;V<)6FQKu)vsSi8r$?w@;G5|oAyvPjdvPaDDQIbl_sDqiY z1>{JnNPz8YeUn%5UxE+RB(eY+jDFz)B=T_doGbFw^OY>|*%DD&2Ud+?BeoZ5n5=QV zuPuT4yU+gK*6SQ&FSy#pDc66Yo~F24AG+c`!X*_cDPJ7`xmkf4K5B0`k%OM(kkkB4 z<-cM$PHlsn<7|xf8_DF@O7>LxT-prTw{jT>rZqLi$#P z&#aLtCYeG;64QewxPW9%6Im7x`8dC5d(+k|?mVBeI z9A54vXgP>iISX3;+zs{_hk+0Z=+Aoid=|XEzq@t8p!&eVmmgy6xK+vC8FI(RiA`|_ za<8nCCH;aOS5rL$#}_~mk>o&#K;0P`nLFEr13JQpa!StFJXqCh&YgI~VJN_Om|J*L z2;gAPFra?V;|1cnLS6?#KmG;uoPm%zq$+Ynz-lN%MB0bTU?hsyYA((3EqWKemf3lo zR>`Jyu`g`Xcp0V~aeTYrMttW1C@n1D=p5qI({0BVftZFcPcgT_ZVji!n=d%cQ0dn> zi0mAFmZC`JgPQEQ1@<=?KcTI4g7Dt-FqB9FMSRSB?6rOuqHarRU`x^)C!kb3z!sj0 z(a~e%E>=j5&NYw0Q`Qco%ZooTl$#LN8)Dhm2kZ`gHY5syNTzYAwxrl`3KNc2Ng5UY ztb1WBFNCSE;dczhxy4EReq?J3y-7^F!37^NQ5ra?WN`TcZygpNXOv0uU5=sw$JRM!?h?Xfmtnp-7G!TkZreDqe}E;L?PC)t(>l~z5% z#dU#0_E#)5J?g^d$X|3jLAWHjmpOX-eViE=#4T=jePRtn(cvLQo(gmZ` zE~AIGmT(e=fTkqr30O_s^i!#Yx}f|%WR#PVZlWEWZ)gzl5TG17TQ!-Ppf_G-`qE^y zN?%Ad$|8qA6S=8A+zAXm&lec;JX>(GZ4`|Fr}e+eAuKO?ioQ>S(9f){!`xVhbgVEBWg`$Lh=6l=mc|`-&uN5> zrO2ry+Xg~Ji7G-?)XNDjnaS7}QfjLChw@AS(__{#yEa#2r0gjArl1CDgrJTgy2HzR zmys8`;&`EjNi)WZ&I`$9Sc7LtJ}`>s8NNm|4>di?&lf;_L31uDG15}2nysIR z`~q`(T74K%!CMDmWgV|&f>SR9Ek0B8Tm^pTxJSM~!l8|Maq2*ZMoBvJm36sc$3T;g z_RGGO2Z36CgV>xW8**85VXC~aH_inbQ34kJR&~ETjImrfKEktjnPgd_S|!HZ(sVrc80FdHA+In71ft)mUBeH{;%7zBUma8NU29PhmBWr{Bw|+B4OaHe6ugTyv{t@dC%iEV0); zY?C?@!eQW&QqRev62{7-G{+r$yZ9w3%C?z!7*Q`A2c-cq?(ACIKT0PbQX+@J|fL9;U~8f^Pojz8ehC z{>Mdq+JrO1sYYRWGKGJYr_r#C99P!0Xl8e3|pOALXC=!BJm* z7nu?Wvv$C8Cd479tqYp&L+Y^q(UTsdiUjh-X@I!66RjonsJmZLD@MY|-cJ-Gz~($s z(;2yV0G@(JChHkvAW+$|6{nB$19wi+)F?aZiT$rL1l#XccDm2-?_?LMOl{mc7c|<3 z1-@egg_D#(5ovSZI@ho7J=yJ6*wFBI+xv2Tn~0olz@VvJKfP5hv9^osa}(B#zjdJN zV~Ba?%+Udhm2rl~DNMt7W;mey+cCDC(;ho`Y4F2`OVIxd8?yW-soH?ZIAZko%QWnXSarlIV!)yNTvZ)T|0r{K6EG=IY!QwNcGfF#xB=D>)yuKcvC`eRCPBGfZ>oIVEa^}ljP`-@4AsF#&K?14 zCcDT2srRG+cxuexyywM?Ow(h|pj+f9D?lwPcm~+aWs>+@l|o$ch>9emDqDYeA=?c% z)I<8t68^)HbH%;xdPk{QH8Z_|(;`m!CwiJ9zaVCyhs}O8{%MmI^g!#`4%M%2-Zp+T zU|-IC8=|$`1O}f9Iq=k>0Ujz|D}fD zdR~h2DEjI+-Y`Cx+jnwe%T=;@I1i`L$`qFt*6M;;Ts_71R*NL@V8*8$2LTsr9i5s|}TCgQll&&n&CUpGvL zKA;bRdbF3zyu)Z^o932ZShnesckcJw*h*W!Mqyp?Wo*>kNNf4}S!(jqaV)QMc!tS} zGkK!{_rG{Q4r)`b?1zVQ_I|u)Yk7#@+oQDI?+sCfWKC6_503jL1>c5e#8eyDDv3Lt zz7M;z^pLz5=pVV(4wBPbkE)T!OX`ev9#p8Ci*UO(kR-{PN5d3h6)(3^5ERZ=N&IN0 zb@~SnSPK1q2yFjeW3ptm9UHG?Eg6_wQ;^7=D> z*TYZ#?i^=ll9Nm(q`vtxV0XFnkTyicjv#C_YqwmQskTiU?v0G`_AGjyQ?F0Tr=-T^ zGzzj(Eia^d?T2@-thVMd&9D$a;qo$07f1u}TBxBF`KCxbHv7iev!~YttPQ{gai`EJ zi!pObu@B=s+2B8Y!4sxwTrA^ECy)#)PMziO7U9@B{++TAHCU+C2OqQii6k^NS)mdU zwQOH7om=Pg*Nv{{om1`$7Txvay;8*vi*Rnp-TKi0M8^eiYISVUdL%PPlpZ+wwv=Fl ze^ieFa{z`eYNM?)s+a2mAD3$BG0IpU)vD+AztkZVUFm0|s72!#<~S4S^s*Ppwdb(A zpw>PR$WwJ5D6rEkU-ZEt-OKrsKcXJ@XI}`-oAX$P=pSyedeK}^kj9U3svNR?aw9BT zii zSGRW4#i=+1`>XuvRkBV7h1OlbKMm6S39O?O7oj5@M{meY@C&;^bf3K)t{8}&z{&8F`#i}8 z;qPQp_%8}WjXA(5tBbH*a%j~j1RGo{<)+JsR8R6zICA#>iD%tV>qyjBnnl>3cRxa(7eIWBcFf z5WwGowrn{Z53^Rcyy6|K>@mbJi664ih+neCz=?bQ7Cz03m{ePk&$BWv9{ur4(6_@SeQ2#^|AEm!G z#F2Vp|LYvW^5nYgC@SRV`VYL<+PRkGd&#<&4T9-BxQ=bt4L+;3j*r6m6vbHy{?RU# zuyM_oAH1#;BP^*X66_&yuVelS5I6AN=$OLvJ~HTVwPAect9j?c&;RbMVjJw+yo`SV z-h6rwDXxUX1a5tU9pkm6;+r|B^e8-chM0m3P z@BjIKix}W|!pW!q_y7EVl$%7um`woX_m>HH0)>HMePiQ-}#eaVQzk z#-Hp2&KFkaUl$iMAw7GOS5n_aj<0f+*C`%vdXY#FwqHjOp4Any<2kpqO1Hs0tf@9E zp0BtkZBmwX>@O$LM03soaQ`)jo7J{N++P6Og{_JUoX|cW)JG0=i002mM&mYFi#W!9 z#3>5sYVHddGI)lg;yI@NmXw&eJR_Jl*XaVI(<$%wqA%`J-1YbB@%`_12-4|}@#9*7 zEhr4h5A&j(pVx~We$aQPf)DCc;cxQnhb)0*TTLrm@PR%Y&MZLJ@M@XB98x%%CcaQP zi&z}ceGTW{T8ppO|Jnx0?sSf#4+DE&an_1-3DqMGe8H(F?=hsuoVC}jpN zR6^XFgCO18*Y5Q6xB_S@>ML;}Si%IEc4)btDV)e_2Yqvs12>-m&$r@U%^Q?5f87Pt z^sysLYPeC+A#3%>{`Wb&1EUxj{c!%&kAnJYtcYRD0Z@ZIeXpoui2Kr+Vn3C$k4f=y zh%6w;IhI0;v}l)dzjY9jIGqa;?!zltBL(32f4Q_nX>?d zxj=Vm7UQTBtokcr&=*1ASG7-hc9xmjd6fHl|L;AK^^7cd+i>S)`2u0yHVlTS6*r$_ zPK*m#YXw{r8|n(g0QgDFjg6cYfcC*zpo8=6VyZbGvz0-hzU%+3_s}@G$HQuciA$zd zWQQ(sOMU!UeY0~c)Pwuq>d-#;(LYB~`^E{M zokz#6DS0EJmMg*q?e<~7Nrq>jh^%5O;jxK@Fk3ah4uiCD7CG|0pVa~HXVI5q{uLhc z7j(#_dntac3_B9xE-1MVi$zkD6==o$T2g@CWL6&DvwiK}`YW zHl{bt`MBqc*1%|_I!@AfjDck=>A8~t$6R7)DucxoMC|0MAR

Rs$fc4z9j-0sw!X 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%0Ac1)?=72dX7M6gtc$gr=It!J^GT3oUM(k zzI?z)oXef%_Bi^ho}&OcRR!2)jqR^{j`qqbaIFYliDT^c?X6x;~wYM@bLX_^#(TFEiX^mQgg z$Zi(SL69utQ~qo~8v~#*ZCy>+FKJE|Z_^+3;N3Id1>DPMnay*5wTJ`h=^>UUKcpoj zNeHscZPd~F$CqXxw{ckciG*%Lk5raMcxu>7h_n$NP~4fPxiBi@ym^?Vs<$=&i$-Sm z)Sr?m{!0vGoupO>Ywea?30;oCg}OjFsF+pt?i)i?y}$cUZ~gRMSAxKI>c$-Q*YY0L zSXgg7Hjt;M^a?o!R09_$D3r9oujN9z^ZAO(=>@zg0?rHkBe|9;P88PPco{J%(x(&~ zpjD7TSIgyAAH^A3ZAlleY(|7^?EkYk5wDB9Ci2nQOC5dU3^#gP^|E@t80aOj1b(vq zl5<=9q~MN6cw737Q_|JVI?1D;1xA8Z!SV7WDeI&d>pD)pTK!<-!&1;opaB z#XV$Y)lAwID2ElOGGtQtQJ8O;>dE3~(m7Hat;NQ!A17q71666`)DPtuQ^-N_7 z%`#$S!P3b{KMG4>=EdyF8jX2WsM+YpDB?U_m5}KyIBgo|<>DoKFm~e|wmMPhAs>M+ z+;LXpqDZmws|hqg1`}RL2cU<|qmhfs;^WK? zFFbYcC?L#|W3S+M7!SBT?qR!kx5fNhrVA8ZI8r<8SV}eM0dOOot=Rp-{OBk^XyDy~!1@B<)7IpImY|77X+-6U|sb=9lIWN;8@bYlfm97xn zZI2kaM%1KbgxC)uJy~OH6Rsy^lRe0)AvpM=g`dpqDzP=p2x19zNwe;1f^-S$GSD@y zfIQeTHbRXyeP+$7oB->VJG_=z!wI8KVj4>)8GnJ&r>&&x@w+F#YYCoZ(A_eqHc2%0 z$%YiJ@#W5o>-VR>|LuQ0{l@c?)x!6FBZYF})Jw8ELOSq^+Iz02e&XqlK*>G*nBY<_ zwBvh=`_&>!jeLsXfBVhvf8QQokC;bu-hQc9tyb!Be-QcPAAYH_@()oe|K&SgrSl{% z793`ye?I+iZCx>05(7XTRduO%OZ&Yl>Z@P9YmJAQs*A_i)17>hGv)HA7poj6#AMy5 zE-tW!k|Fafgzp(k+EI2J>3yibENDm4>BkO8HFfJXoZ4}e_C0MzD*Mzop& z3-rCo&Nl{PV{(PHrlA_*;roT=(JJwSC_J6sw|CGcJa(+xRXySk*td<8CV&5b|3r+0 zKG2efnWL7T3XWn}EVj3V?hS;S4%y6S277-R>+vst7ytEdv7^?gMHC^tb07e&qllwL zABn;L6B8TEJ`M>0f1Q7JXVo^-|4b3Z5tH9Fji(fjzd_#F+HkdbemL#5dgx|6imyabOyAn*J>ZgP#)$UM!C@W<1EDJ>@Hbu&EHZ(?|spmr9(CJ>3%5+$)Xld(I zF3(gUd7z-XCQo#4uwM&AyJ!EDHs=?aE8|sZOcS>=X8b}*PFU7Tp`4WIbqs;K3n`)A zK1#~#IQ3K1UDazNA(*gx4ej8$S=&}-Lr7&6clfC7*~k1dmVRr-^|(WX0;2XLXu=^H znntiKEd9_SKEkpN2_WI2w66{gtRRnI-|4g4LLD_eEL7N8A;-`yw1{XxLWfW_Lsx+4 zNVI)Y4| zkxER%gCaBV3Z@*xpd#0-=TH)rCfZxf-b4wxh?E&f2n2gh<&e`S+Jr$8`6IC;{(2TNuH3MBySMt7)+`Qv1fb9j#KYo0tf7DesDOdzXqQ54ky*b0oWEqQ+k z*s%^ISC|}tVe)+*&xy$;#)q-)ws4F_wtwn)VwR+=DoN<*X$e@yB_Ag5)lI|0r=}yq zpC&6QXSJX6K8y<~Yu+#6V3bA<6K{d+EJDB!xfdx(5J4WZ_gNqK?c={jPCoAgyG_Qo zEl1hF$eCk|a51VxVaiN!IW*ui)3O@NOL^szk*j%4_G>W7N>0~R5QzqMph#WxQCbXI zqbVyQjeJ#f=qz=C2c@Grhbn9ot?pFm&vaqy9*W z=fZbc*^X0ck=~_!p>z71z(%fu!{sQQO7H0C(<l!dK`NceIrpVQ z0HyVcqMvfN2jGb1X8EDP1~mFelj9y(7#$~+mLnOIW&w>~pWS3)*kWDK+4s)r=vkhK z0|IUZUyDa#ybQ7`3B81lA8Ezt6ttq6Lig{-y2gUdHogH7JtPblqp%fBEU2slnF;oehYL zVuqp@UedUwpWhoRlu`k##?EEKT2%-4j3r<3*L&8sfLChot3(=-@IknJ|FJ1U-pjp%&3rUXB z*Y2Zp&tbIz!1l}fw*nAs-F5;vtoD|Sz*mL0slTNoP#WzKge~m5J3?(v+0hSSG%m(p zkEE(@(#=Mxu(%HetsC!eyJ6Bljbx1S^Lf-O3~)@gSd(*RS7vTdF&DNr`oI`8-N_M) zqRMf!^dpFe>ddB z`D<%K)H|+M3Bm3B?K4kg@c%^fG&(wG&>z;BS{^AA+P;4msgBQ6$x4kz#lw=}7i@;AiF=u;F_B}eYUlsh9*J@Xo(4oyHc@+0jztL6&` zKSi}#WqGNl(a5^-y=k8Lp+`$2y)XmX7ew8Oc|c*pZ_H02NvKQ~df>cDqT)n#H!Y%4 zgy2=4m06H&+aFzIxh4g4vyJDK+w02h#`Gz1wn5xMRlY3`#mMtj9uF+Mqowt<$P%#N z4_6n)1f<)JD%;t;vVhQyHcfMKbX-c%jtTT-hmqDr`JpG?l0@9(1cVsRQ)T_vsri54 z`os`*OnBC=GIX z3?(aOBcz-$ixDFXv%ANTx~ug8EyVq-%cDiI^j-@!Sz~A(j>#C@Mw^#f47gTez_ldux7VU7gayjR@!tA>&GNIi zaZ3MQMYl49VDQb^*JocJ3vBHCvQ3`!e`+=esWT~%uqQ>>j!Y3c0J-B#6GGWSj zSlbZt<`RESq9cFiZ2z_&h6Po(`NhDh51QlVv`xGPooJ_GBSQ@I9O!O>QnHNc7$HC_ z$jV|b`xvJo>%hf*!9mk}-JRwYG2sfkpqyO$B;vAfy|YW`0>YtG&C6k7hfHNxIa!xt z&%9SDmc2mcSMX6Y=Oms*r@?)Yn6PTcWNV+JeaLqlU;f1VPR3PoUo$IH+jcwjEs+3~ zTu??#$)@u!>w{5P$-=*=GZ$Yh{rqEuYmaT=5rhq$s9m;zPO%C{9vzbN5!HlXkwGBZ zwgy9^&WAMKFwd&hI%hET2SAtxokn@%hj0F^0Bty{@VqwT+`vy?^c}DEQLj0zU#u^h zEY6P26h0tcZ_L7Y-VyhMtedMsw>jQyxIo#whPgzI!`8fD!KO%av&jcBst8|Ds5H68 zO>v_D6jPhJ`pmmX%^A+Bm=Z?YkE7LsqR0Zi;*FcMQ9bl73jFLH+FWgoSg&~1QG_Tg z6bteSkVeE9>)0mukvFtiMv!#DAyWlZYf?ZV zxI@O70Z7Q4X~-$Is?Rm*RGUb1O$@GJwEPBr=np4enB5J3aL@8c(!kc$(T-AZ?Jdh` zlfLq+aK$P(Xkj*?{q71%TIO>J!+Ho&et$0w;Is1(CB z98vjx8s(Kfl}dO=M_)K+6?-qN-U{1cCRb)TJ`9di)0Twizp zVvm*%Z(HTCVUf;o6sO8HEUvT@It8jQhFgk9!sOLZ+VrUQ9ls+2=sxK^S44getHWF} zU{>^yRgQ?Ib&*h66`dUdf35Pk2qi+|A=ERr8kj_LpSwHHBW+al>MrRzZm!dZ(IE!A zLQ_fZdt_*@>>N+jH`(BnfXWa>KnVnj%VMibfta}^7GA!0g&^xFeefGaAepFRl-|6> z{1@5XCVCS+OC1E-rM-t^AjnqtIs5^A;2VkjppvOsu+%K`<6vQC>{%y>M1n6M?$uT= zlCRI#2P*g+P@JCB0$~zXj8~ycX;!N2g%WG<-VxetdG{LwoOpk&iqeW@B4nqu5V2+C&~_br+NkHAS9uK{SkEGZS1QLe^Z-d8L^WaX~`0;MZdEm$%tp{2IUB)s0>`K zu&+#|oohVlZ$}+=l_#lbcC5YOhzwUo5m8%8|6_zoZ(MN67R$$gj=vr$J9byrjcheN zdiT_K0s5SJ^U&0rZX)@%du{bk<0Sg)z_C8ohH=&83l#;#RiJe^8`cg7g*PL*(wujU zP1uFaCco>3$g^?b;gV{nrtgQc10&92%>Zwepn!LRvsCl}z{E}mR$Y_(AAYH_@(&Oe z@+|SDNtT72=T%gBB)@@C%3mq1O3B_hI<_)IYapC0piNk;T^q6<%pN{E50j7+exeZG zb-BRRGIC!8IyDb~#C5c_gjtyk29xJZXErew2CiMIfmWUkG%pI0ij`1c7%W|cUE&H+ znn2#mbuuoTI06VcnwHpiYcnOY3={G`k5gR?n5I{`^KqK`VZFp0Czx3zNCy=?W?Aro zD>@kc9iyKZpKAEJt{=pZwp_-JO9nQc@DNDHRc&9!A*9EEjR)m|o$qCD2a-u0~ruB(5m9|eQ2l+T84k|3s)BX-c#HaS~s=`@t**Bya*PbqJt(o*2O z{~19MKHq58bs2NcAxDhIh9{TWAg2Wq^A?DGOm#XjeZUN&Lb(_iJvF; zCpv1q{&k=S(;slN#g0iOy|N{`lvqVm9l~FSP!zdNi*QGsfEzy8^y`CR~bQBai3*` zU#*Nl$LcKp)MuNH6o$$v*C=Ix<(1hO;_;n!-P>=PHJ|yMPOTV{@^aMGy4g(#xLg79 zz{HhNV42WPjiXsthKd46=OonORm41&(2(g%g`SSoa)}=F^;f@CF-Z?mzg}n~0mz1f z$jqb#pU9G{D2ExslwdObyi~{PQx58(?I~<{^Jhp;8GKCcZP;ruMH?-R z-C3fAXqm;hr97psFocJrml4Q`BC7JJZF^k-tt!1PhKW}MB-y(u`%pLQD!U4pS&kcJWf|tKi8o)A^N3%C4~nu%)p59!bb3& zuaJ30&+_v6Vfz9ptIC5!2}sD?iYDXj&1VM!NC(qz3uC=%Ueg21VL?L}c)~vvi+IK7 zd}`}ytuU`?M6|XRs`cBiPk;Y4=3a659ox6HZF}rkE%%~8YRV-kmyP0XlIghVh^P%5 zyP$aRDPP9)_H)`?QYlIiTBtwoKGtP`N$tg%^zz}zZ93ajk+*#6d$V!9v|TZM2ly=K zliYg4{~BpRK7FqIBAs>Pm7i{kN4d1GaK6v&pp;CLrI~ce2}g=Vb!4En|EjZlH{pKv z9&WA9{UMacWOF$DRwlTgZM*`Jcf^UCDA>9R`M-3cWw+6>D~wnRp@sS`TKKo|)|5?y zmz*)*x9=PlHv16PlO*;h&uUw^+KP}X^a}dOGk7?B*K4H@oCMDfL9aMQB@mZ9=-Kfb(D-+L)kXIXQtlp1W2%rajAz@5Y)e}1$-)nL)XstY5W<`05 zB#Tzm?fRG}h_l7z9xhkKMFYfzH`2CA$xGtDttrG~I=J}jeT8GzZUg;@?)mScm*n4U628SJK2uCK454AU40jJ+Xw(PJdQ2dQ0W}55Zi;nBvdXgaBj0(^_S9A zg7mNZ1`-OamL%MCyNYXeCsYqMd}Om3ae_xU>STbOjyhreZIj1d&lKBb_k5!KYEP6k zhvZfUY7xyy{8PInEuXX3RKIsIfzRfbtrI5em#;Lwrxr6&mYo_C?d3-P&ANNr__ap_ zZLi#`VPkw|U%y)R1!HHQT{=qk*Uds%Ta`GYrzXA{ZQwO&UvaU^J{u>Ax5$#vvdwBS zTN`~b3vH=ZHA-t0TFAmM$zE`3Q{LaA;^2U88^%w4KDg672d zaor0cq%C6y(uA8vO4!C(H%=~OHL0QY!T;}^4X$XCjYtM%Z{a@e3c~11z0!>q&Y|{k8*72 zld4gIquw%lFO31!m8g4Wv)2l+MoLR6i>k z>NSA9_kjoZUYfy&d@ugmBDb2JYuYvBye@}z)AJ)mFdc0oMIZSJ;`uq;ea77D+og%s zUy`rH;+xE88@t#wiHlj|5a&GYsGvVjOgmT3M2)1TkMI3ACI}p7-hYcjj_fe}qZn6s z+8{LfC9UeTD)nOB+Ftr?oCt2`fBAbI`fV96M&^o5bKLM;QS3&iQj>F-9r_&)>0M*U zA@}8ucNlc`4(ELur9<&Xh5V~@P=2^^{v>FUgp|`0P7wnzC=s2TvAv&>IP8v^>bDyF zRUFS>R%JNyYOjeaI^&e0L8)uPNlNZ$ir-+!xx1V7fb=7%4@@=u_2Z?rZnxKlm)-TE zb{x8~gxRhf_-o2LG=SAnPFo<^^|W7$Sk@&9?MEzYXVDBVmfgmPy+38~eW2a<6A$U} z+=twN8{dI@1r7WXZ)8k|Z-I>jGsvK?ajga&{yhd^hnup*9?1?Hz0n%Mo2tgqGZWmw z1iKl}ewg4|F5K{3&`<9N+HjNfe#l+6dB#aH)QpqF)r?PR?b0kAN`t1Ed+&4a9gXPZ z4WdsZL?!p9!@a>P8PR+1=nbsk^?}}CQhIL@-|p%SXy%}z<#2WwnmMTG?ZC#ELC(jG zX~2&>AUC8oJfDuAdYz6NPNe-Dj~yAP!yk}Hf=hUhjdpNG)*8atZgi-@;j* z#gJaCqI+-dhfkc_`FX=LiAFugfp*`-9&^ZzxbZE9^enmaDWO*8V3W>PL(X{+mUrHj zTKY(S2kwKE(xQ=5sstlcpdf;d{2URV&yJa%?5=~wjLhqrF`Sn&xBcT!LJZ-8Dx1V zJ&!$^DA=MaXz=etD2N-xzr#(#LWCRz%;l&!?ra8Oqvi^m5?W8Ttz=e5D-xPeP+n&7 z;DxIvlCTk!D*M4?R@u;~4w}A)xvv%|TSHRUlxRv3tHn7g6NLhS3VGA;!tt=vtyV9@npKVo2AJ@CHHtHu_%f8+w`SM|koK z@08a|ChD0ssrpor-d%JC<&xaQr#)LHtidv=g{oNY$0*{uv#Tm!WyP2U7n@j5#qsCR3as_b-M#FA0fB9FV-L-3WeFF0wrS8`mJ> zo&k4p!T3t;ysW7eu)+;~s8TW;BC{+l`I>X`HO{r>e%1;@05Vdr@oGnpSS8 zI5I9j!03>vl)*(VQKx7FBUiAjRI8EQ!j)=6GAtf4C~7rHjXm9`y+tnt?DO#`74|we z8cxPu0)E}4ZCG61+flu$LgAdN?@GB0o?+pJY{on@nz-BN#T6IASr_j3`^TOhMpISw zA24X~;!UQ6v={t4oejjxUKCM&8wW;0Yo6f<`Opna@N3$P$7Y?%lRd`UJYGKVs99T+ z-6!oAjL`0xKijtVD-?W(8uBP3P(Q({Z*8cGE864uf01P4ajq`B4%WOa#;iSCxc4j$ z+=wxuQzWR3S4K-_;}wj&TneiJrbzsSlre*yT$^od?&A#AhBtLLwqr>Z_9-Eo5@@0t zlQf1vyJ!YUmwGaseV^#r&)g?^H?8+ajgVaMY5=QFJJm4>=ceVPaRu6%y0cd^2ulxU zFSi-r+LqSx!ak`^3pn?OgW|Q3$@JX195b!}(g*iL@ELP!>iMu!wA@H+ip>??t|$t5 z@Gw}_hNf%bs0RZY7tpgUVO~?3X8`ZA{G$z>>`P_9`Ks`2ZPn-qNA4=l9hUF;yy1D? zApPpI!N|W_i*kjTe@l}Cud*B5w$;e9N%N{6_zsID{P>9ur&j7=v2IGo38H5T4X#m_ zR?(@aoS+(T*il{}ox_m6;}xL$4ZIr{;9cV)=lI`rAWehyKwHW0+6-)|_w-ZLaAk=} z)OYAYRMY2Z=vSX`RMTMFVlKw5=sIav+AZQT^6W`^k(s56T3J=ogjmTUOXASKZ(Mxl zT~|R6r|biVUR zrHs;W#Z0`<<{0}#je><#J=U`-6ooVVK>))DYAov`7NIR1y44j_pyRnlbd>PdBbj!0 z8!20-i+)h|TkOLAP)(NItMY9(nEI!2{D;IRiubdhqv+#9Hp|QUu3>kJ-9H_&I;N); znmc+N%S<&_(Z!wcGbfiwm2>)kr~HPPC?V44s2In7cu@#T@RK{>W$^h|?`R>-LWL_6 zc5$B5+;9D~JeD)#H@~CvqRKtTf{vLZ=ke^2Bn^CJJWAXwv0Qflvm`>)MpSp^k(3qv zv!{Fq6JB9&miTk-?-i9N_+I&}`97Fu=X7Sev*#ELU!~!m5e$;7IB*2_e6BTw_UB{G zyy!^BS}i*_cEyGOk5MFe>*cF}Nv6|M0 zn~dV{QRc99weqN7G&#y!r%ktXo?~y8XG_5_BspRX$i`$O&4swkvmm0?uuK8^i$`)0 zlWnd&K7{3+Uua$+{i17(eGWxb%|jF&)7Ojpf;AiT49{=r3XGVjmjaM7aDl1_$n3Aek{7s>QjVW^8kL$XW&FreT)~V!(&1 zHy+!5SVl&i3p)0kYL~ihbamp*D_T(6t&!CXt6nRs1(@;FhYOvt6LaXZm|r=5oYdi| zk|zwG+sATz$m54B1W6w*mLO#Yl#4h4sz*A-m$?|=gW>yw@XAhzm#C3dz*nR1SRgI6 zY6EkoTl*51rbESe8CR|`#0tKj9R`F6+M>|`3JC!S*?w(q%vjMC7WW5fB9CN6dR59K zOgw*Ufgzd5Fvt-jD66I;WrG^_3NLgmmk*5@f?wJlzc8~pVa-q!5++GPBEE1UpmI07 zsf6{!=_y?~N3JS>6gdQ^9}ngh(<|18`Y3AS2BDn$tCE1zvI48T+ItbGMiv{ec7zvf z%JVGmWuqMzD@BRI?GlJ=v)H(=^@as_r0nLwf(7>?%lX)X$Q8x~cUsj+x84z(asT0$ zDl7kRUFA}?-5fFUy|bu`9U*3pFvfXrOk_2x`j|F4*_rpDpsz^|@M5LVpdc~53&?qm zSfQxFB7u&YoU>y?|HdcQn-xK}qGwK4oPHjZ+4A1;3KZ4!uk_$P>~^dJ>Sm!MbD{6W za3W`_s2#LmD}2`=+fbEVK}D}lCw95t_*;PWN>8L6;cN?wj8Twt8Br_3LO!tVP{Ya> z;Ym!uv0WmijOPnP0JsP~l_v9Bp&b^=S4Bz0rr@D{j4m7@eLmrTPhnF%QO0aGKp+G6 zZV{zhY0-n{0)3c`9o%Y&tgI0A&C1GNtVq2;jc$CTbx0#M5$-pe6j{5ua}uR<^x|!E z9M!*9&ez>*wRN$&wt#uhmoz;)>MPWrU-TbCS%aIl;RoD*EmruZP5uG*Pv&*SwE2T( zenm87?v)SCyBSv@=p9T&_S$}V3cTpIaR~ZHGPIhK?&Zw90EAfjA0zHJzCz^>72E@g zj7zQt0?XP?lKX7-(1eW`Qk3tR6}Mm`cDeLF*7Xkbo@7aO=@1)ZwOexooL$yG^-xTQ zh3gH9mx{GRWE?tn68Vc*Wwt`*l3z+*6Ra)2RPo9sY(0X}M<*$q?k2dj&JC%FNscKL zMM6c)lnhY{V9|9eGD^O|$U+I^J3$$}x?;WQ(S9oRDx+1uWCiM<1Q$xeAZw6mbf)`0q1M zH(fA4OC%RdkaYj4#Bu3P58kRuNe`ID*{XnHAwyr2B!WN874}(Gl3;uwb^tY*bl4zGls!s+5tV@}DrDX4@QOdB_ntj7r7&ig4Y)z5QzR_fJ5f#~Ha{;> zUkj$x6&hJ7di3E=5%m;D8*-e%BIeqT&4R;6PTpPlh%TqERGwUB}kC9{Tjhvp|=VNUgBU#XHL`&e<|whvkNvG&dQEA9y1S%wqm!|EZRj=PyI z+B_X@!Wf_$>#t1Ot5*7)%%f#?3yUO4c@=vtc*j^5)-8KXq#oB2kdpemUBvSPR=CQb zCC9v3=q9%LD67zkIDxUN0>6+gbWYpyl3t*8d#d1{s&FS8;SCm@NhY55YW-xTwhlJ%}}WDWh61f8x#JQd~_yy`WKwn9c^DONPq#cX+SU zTR)J~{8%;zL+0U-kG0`Bav$h`U35??k_iASR5?vP0Z!B+3dQ?o7ZlYLQ8R(C0|3i$ zBl~iiViJd?=UZ@!${us<8t;WW7+0L*#$S&V47?>=_5Ww@-J2UXvV6h+Z^Zo$*c-cJ zX@XL9_dIrI?E42TsoRPvyQ)j7_HIl}#D#z)2^R>^cu1v=`{DPz@&OPeK?z7wHYX1@a`kk5YJtMY>MH)4$`W|~U?A_jJEm^G|cedTqM#ty^*1-Bq1)Hwx2aP2WHIh>Y=kwYY= z_Ac(B2v_4eGO3LqF&-7IO2dRrvRR7M0i{0Xn2ABbkDfmED#a@Im(LNfT4C2dw*y|0vk zv{Y3Ha|qeH5z^_m1mXCou(>qFCcuUV!x`&sn3APw+mrwuo`_EJ%Es$WCISK6Gj2iz zA41=bwLlb^!#hy{QHVdtbDLq=5MORmqJSk3m=`7kQRE3`x1f5X2^|vi!u$?XnA13E zGE>oWgk_rKXS)_|3a&Uq8rR?g1Q%b4dXfINU9jc|x>{liS zX4#uO1wBZ~bCJyEcg{3$7G=1!QyD`&6ZY_(t9-xE;k$KIYeRV*Bi#AjTR*&uJ@v>h zOxe|sU-!!zX8m=K?Acx>gp`<$uw7(ijd zV`Y4T%OAoKRkz6Fumt@7i(;A`NfEA0G8}Ue2 zQKiF$z7MG)@7bup8^v^Xjy_75UR!C!r zv9Q*`)N-!Pk|9_lU$i=&44HM3P1b0}YP{5KNJ0QdxZ_b&@q_6-H>}p56rYpgb5eYc zP4QVf&Nmz|cF}n|yG@AQ(O)4rf5@Ns%wSU-Wg(g4PI8a`(>FXQ`V!~J+dhE7jdfF8 z20!YO_cZ6qOmySx1VA7f@g#69-t$oAY0bq>u*E{`JL`*x{oQPSygm1p$MEo|DmX3b ziWBhA(MGx-{9Aop5i+1r2*2qBSfK-f$D8Q(OCU)om6D_=*MraqrQ~b6?Vxng7<_3_ zR(Vb}59Im9})dM#$<3M?^Fqr6)s`?@(^{HQo8L>m)dpqM9I~Q(}zUNmAfB4e13n8R?0lhAIMWuX9-M;7}fp?mS|ET zN=*>PG1*Qg+s^)jJwADnAY~qvnGxH#Yo7}j&L|ayVB62RM^w2mxNq1CLQJU2#-_N2>kV8YY)?*nO%f)7z*szHxkdp}rZ1()B&$&3c7q#=s8RFxxZSu-U+V}n&* z&ch;Pn2Fg~X2i5|!tRO}Gr6zTK~6~eWetwjov!yv$YixkX)xhph|c>GT`TZd@sLzS zSUj_Vf#rMQ+-8I~U(RxYWJ<#Q;?!4+z42G9OP^L1JDyPpiOrIT+N4(SlemMQ;s2J) zI6lW5%Fs+iQzL2j=fe;oZ+{Y$SuT@d`V)!eSyO&XSyVzTIL4AFpBLe3!On0wk$kZv z{`X+~I1MP52R<9G)##wd`4~pj?%c-m3*XR&eEaJ-H3=LtM zYvYMX1CsI=0SS# zY`T@X%isnBorqk57=~I5VC3$CLw9m0MScOxQL+~ay`z^wFiz+1Y4AOdD`zEj9^qYR#qam|bVPT|P>&t+JAQF!mUd*Hez@Pj}w%{WWDM&SzL zcx54xx^b^@tq1nB(wbhqnlFr^YTz=F5+~$nBt4Ox zU^+8!+L&J9!)*^-z%c3;i#BPE^oT&y4og89+w@ID2VjZ*KF~s_!8lkLvxKVSIHxCH zF6%7e+8UF&V+iZ5ZR@5+?HjlcPoOw|8vYoaVet!f0$Vdf+`xP1b|t@VD78(h_=fP8 zCL((VuQ_|E32oD>sGfS3fAmzU=#Pz=Ve_$z3`xGm$r3SYv* z2`p*BnR@VLScF>Dae@$6G?FCC7`4alI{HS~Z(!2Pjx+uIIIC|3FIg3Y>)cY$#3$lS z{QpuHNf~o!bRXUsH%oiU33goWr=23OU~{}Ral6Q^X}28;sV`cJ4t+pTcJmcEHPq6I z+i`T+0GU!qBVBuQ)b~Lt8xY$ht@jW zkXigLZ#2#VweV_vn6ib-&phtrCX~8h4!D+ckoiQW{Nj$o$JfG;yaG$`dJn5$;B_&& zeD3aklho~=H9TAEGq!3Cw&tV{5ez+~cjV~kdXxmsHaH6y+1-qXqT8!In}%hDIaKQD zZBQQP8x%BQJf?Z-hSpp>&WI2}IKz&Ynz6tb1MM|e>{BpchiD5V^CZLgpuQ8Q;*0)j zHe)?bBc<$K0J(GcCD?=)(=r~N)j*L{lIoI~<1mp7BN8#b$he~~aF=c|yp=iahz2+( zBdHqPq_&yifv5+ztvZ$@BpK&i==i|<54S&E`JyFao_FRNUEb0!J*Ye|U0w2M!c8n) zO;!pJShuexQ%eUOU9|SmPdBfn~v1I+TV3AlKyF&8kjW6 zzTEbLsT##$))ZwXc+UwoXnRVQN;A-8%ENH>=-Lon7Q5!a!HQpp!i7a%SK zBkasBIun=_c~<)zy~Af+QG8iX2_q*q_llecrBs0Y=YRgEFx0h~@qhi-e~G(NSNS=8 z{Y-xwG+2S?Sfqv+Ik-ITZ8QMqDA?jcI>rf6$5hQWQ=-}{Z;KD7c0+eKws2RHcyX@b z2Pafs@BIKzC9_oiUMyGi&xB3Z}J&W|FzeO|076LcfT# zV>aERaK(E68MBBKwxc4>?XVbQRT{JTS|I`=P(Q;--))uNIKTH0_`@#QR8*P}VTmO* zCb4y~qI~IyeV%a}NB$(Bv#gtn-?9CoZKvjXIN1miLt=9&7QF>?muhcfh9{4(CXY$p zE2eK($UHUdDqOMYSO%a&TC`WoI9eGDDgFhAGMreL0CN$TtwgX2nCSb=X&IP57!qhP~dni>rD+9%9(een;onugtE*~f)=UAuu72WgSMd4Fmu1&M=x|d4- zG)`bnle~}5Xf#I~M+evHHxRqCG(rr^Y*P4UyY%P59M=%o#W;|RhXUg(?3EZ}2X;}C zJHPg8t-R)a`=B2lmbiD~USWhagvvOPe)L&9(dG z6-#nzn$hx1>4n8ZmBfW%RCZ(Ljtguj<+hTpi>;W|rEINj6z*<4#gAXZw=Atz6U(1{ zAHNlwsbxa-ef*kI@#NL`?|mNZ(JyQB0Pe%pJ`dipsA~$oz7I?Atr?2z`|w}uFms!C zn7{iDV1Y7a1bd-Ro3TSG7Y*Y@zodJ+?B4e$_2MAv#Q`MzD#x!$Fy~c#DIn~>DVdgv z((;>*mPQ`q@FJT|oEOVXdin(-lb(Blz@z70DDUXm7m7Q2_Lah_Yh1B&?^H^-CVl)= z!_4PLiZPYoBcR)Tp#GEW;5(_Sk9y0tECs@2g(JmnYdzK#D8rJxlKLmdp9&DV1ZCjdcIvsb2p1;H3QB2nn&|8^A5f06*CuM>t*Iz77sIX5%gYW-Vxt^#mwc9@iOzcRf+kE zY2zv7ZO&~ppI6R1!126Hz8Y@g-sZd>gLuWPS*h?c?>1t{D`t(cNiTCo?B-#n{JHQW z{8)<_Xwp|Sn3s7wc@G0k{sW8{c>IFU>c+!-fK|4=%-@8C2AaN6w!O^$S8W>%GkI-l z3^{kY>}c?GnJ)I~D$;2oywXnu0E0k$zp60ig4GChQR>7Fa``k9KN@v448Wv4dofiT z;4~ZgrrX>NyJq#4S_Eux5N+*6vc=XLxf#it*}0y`KfKKurb8W~)6~x)8fHVuk!f~J zB4l&f#tY@gVahai&FZ%K4$ts1M16%3%YioK4GlKrzT7zP|2fo@LBC1; zO!@y9YR*69Rp4>n|1ht<9cbo%AvonCxL&?hnV=lVDs&k^|$Jg=D z_FM((cupPSB_n|$+MOE;#d!uQw&?^^|54>8lYo%a6(Z0yv0$WT&rCp9?zkoQ>lGpo z3V+h2f0#zGUp4|So0E3ZW7D39-GkV>zyB>sl0mu;_Uh?o(!fUS`Q67YcqTV``XjFw?uzgku>r2#P z@D~+PCvT0>4x2b4B(Rvjhq2F*CD3ySUr=4(yy_)9iMtY?^|i40;t#W9Sl21{7K=$h zf-?MoO(2C}8~XE53lmi!1RUr3y6~9ltBPuRJfF1}mBevf%vFIE?a@L-Ununw2ju4( zQ#@F=`TVqvO;~s154qgHc)I}wc+;~Lli|k2>EUkNyawcVH4l_xb++8X&5Iy_% z?&=f;qjm9uEuRQyb{zZ~ECn|V<$FK;B|u1kHGaV)`bzi2dn+DtIm zB49{TC0SV9Jfyn9vleljQ_@CBvy|G3o0=EHR0HJ_oPe53@@)%*jFi^5D0g| z5ZsxtnTZRv9N3Wd9HEgXS zT{n9sDd|uxsI^*!n4bufUY2=;eL}nm7~LJN53S>ld?7vRD@lz*2hvypGkcr^Q%oJr zzwjT$$n%2f;+Il==rk5jeuVGONgG7gQG}uDZtYgL3WGV+c{I=w$Q;PSEcNZ>bLkQD zPfVl(l9kRgOIoI;id(eQ5WYr|`y$XrcnPEL9|&e*+JTcwMa2f!*R*qJO3Q8qYfROO zE{~KC-qZUtJ|6sAcL8;;>v!GDp??~x`lq?)4vhGY%hudU9i~hdaIr>o-z@cu-x;QR z8*QsEdOK23_g8cg6MWI_u=v5sY1m<4QSk_T)CGsM6U&AWAlDI7PNNLF0zqVfK3;th zlHa%e0G-M`-j7h+=vp{tRP{|WIV;cG%A7B99j&(US96R3o52}%At{f)I~1!n*Xq}F zf5R@ao>Yp_REqV2;x!pQ>=wBk?4h0opsXY01<5-k*tI~5(r zr=sI6W4z&_h>JiX2fLpEZ8Kw zpia}O94Zy|HVZz=h+9`;(=sqs`)Avk0RBMpztZD*nLVs{dl$W8?o%Gk%ghxw6tG z4LBscBaXO*CcEDBmh!ANsO0@KHoL^W*PP8yiZHJ^v}5%S_u}gq=XR|PVW^`W+QXA% zk9qp24Dfaoij&@nK4Y*LqazY9CMQo=%-LXEs4%S-_}Bz~EvUJC7|?rD&6CMPl7cSQ zcn77+lv@f8yO?B6b?DxTCmQzuyf?Jt{bg3JrHzf2VQB}}93#<4R=*qRm1UH#ygBc= zcX%H+^f(}dKfB1-1#f6g9Gg6GkS6D{sr!V8MvYtZG{i(>$~d&zo^j1@8HOJ8M%@03 zWVO$x0t;VZkqPYmNUf?7NR)-HZ_%A+@Sv(Er7|m{wZpT;Y`%zy|J{dpKVT!HpTfa2 za;7vNCmV@G5fR2OK`QtY4u^ecf`>(v6? zubFgHB2Y)Q#Am`dE_R&PJegrAIHz_geMI;)hI>H&MSJK`{e_>~4<_HJ~q+jrgM);|pu;cxCJ&8&TF!tu?X+q(}J|N2k=cJap`OjnCA z_)~xpD5u>VukG>mh=rQt zyx?{9Y9+7xmK9`fj;Jzel88^-Wb-sB7vxFOKVN*lbMBZfK_ue|Ew=`bz@c?%@4oq| zH6G4XZXRRzcb;67YuK}zVjP#rdfx^hhu)9c%H?^Me3Tg(d&a>3^pL)Y@)KrXQXANM zg{6k7z#gSRsvcE3O5gc0! zi`J_mp|9y9cZAv>^&?}^8)2nk*GFH{=LWsXg~2xAfQ%+JdI1q21t`fHh-P13R7C$( z9}14^yPL8fjTHthNzt1laZ!z<(+~@A2>PPZk-_O$ECH9vm?#byi!bUN1$NCf9k34~ z^e~?3l;bu?Gzk(!R(bYC9*s7$Rb3w&Z>HfQ&5na_jsRRnSH6eFhE%#HkZ0G)LB0qC z%Teap)pil>IlHX6&v=Gy_mGy)8HshVYQ9p_SDy+mPtGZD>rcb67|w3PFw+OV;Eu6{ zM~wrB4S%UiIR;(x*e?UZ^LvqPYTh_7-JFfOREU9pdC!TFnf?V}5f0{$|^)hRA8^9@eUfM;Wl zHQq2|@O~F5s-#ML*<_4Ib`65VceP;(F3pyAP6FPSNcnt#S z?QH{UWxZU61>%m{hIFi3`lKFeUN^ti3CMJn^)g{cA|M%L%FLuiKA_BlvaryX4CuPD z=l%lo{x3{dF4Sm)`hM>ZDh z*btnqNoHEv=`GhX%+dA?=#Yr~tLr-$BuZA8jCL8g`KNa5h`JL*X-!Fs7^P;Z8BrgC z3J()>$RkoC;U7M!2m7?I6^szcidV10vbnOq_E_6t7jcTQe+;#iW;J5tu0RZM5MGjR ztGhr^PEiSzt==I{Lts9xJdpM$CWJN+J1G3Ad53>02aQ4i0+@7Vddp6EP(VmAMQp0; zKmFn2U;lt}ugMugW(zF?$f#Xri=O983GN-E4ER!~7&9n{W=k%x5KU$62(`-4E>diz zMHiP<34HzhI(#uo7Y{Iih6@U4-u5Fn{+Q;1jHiMZ2Ou`aXJ3 z|JBdRUoVBp;sW<%6>inay}2gQ@N=fb;!~JWI1#t+gYl!0rOZMyDLqR`Bt;)}cw9A( zY-MyUp5p(xSi}h@HSJHktB!}gkxz_9O)%S=9!qh(S$5i#>_fCM)esXC)fvP{DO*?R(Cv(h z>l%fjVk>z&X2Z>v+QCe;yZT{F2OC{JOvKK*7xW9dXTOU=r=Z;cLAx(W)~jEL2w1&z z|ACJU%kU^Qa5lJ}#)UHm>vxgC`keX*&KrC8ur%oKeiaIs7POnHs42aD7N#W_A_=?k z#rsgT3T6%cQ1X^=TRBU{Il~VqN19HwlPo#7EaBs-U;m+G4Dq3-lf>=Fq;!@%B7C{6 za`uz+6te#av}!nj^O@@b((w*Wt+D+={++pqjKe=*p$hsBkp%i8#btEAQA-ISRx5hf z^n$aed?%^4(yEJ%^4Y9Z)hqm_Jit!BDa+!v>vK#Y-<;+rFgU{^TG$p>Rx4lo<#i$- zH^KU#)^Lg2+2|%Uk!zmLR{5$ z&`k&MX%z-qh26CI-C-@%#WrLBb4HF(gN`}IWu7AR`sYu##%ZC|Jt=51WzNj6ZekLJ z0F#Q93~{Sav2>`}iM6{)u*_qXV(aTNnP&pA$p!h5`ZfUV9<@s@wXRABqrjG@N@CUB z45tm+4ZEi$QJSlBO$bJP73I^Si;bs5}NFzxAkL<2x>qK8W047cE=a~5TV z)j5bC87DIut|EX8RgB_1>EhjE!wJzKWlmM|E4f;P53@CA3vdEVNs{IjZ0~E0kROtM zCjKZ1lz7f1zl5+5-W;OF@PS& zWcZ8LWm8LgPIT5L$tm)bSMZZKg`eU7mdhB%iAjzapEg09$3D&R2L{zHuscaS*HhQBU@9NbG zla22Lrl++Yu;g?+$7L8TFt<>ECJ8o& zv*RjaZ|!HI4AQyEq+O7&WjSmCgk<_pY;yUdns9l@tVlpfN6VfHBy82=`U-g>6;1X2 zp!C9XiEAU6DmG;w#9~TFOqFsdhSDp^v?NMhGV8K!7q5C&r*QVjy*!GD?o1)`!hz$B z*!9DKS(7~c@OKC*>oe&=9R#krt>Ss|NE>}yl_VJrY_NGFYSkB&<|7&iETTq3rg8g> zb*r_P!A*v87K18b^hHbQG?}Rj?o}o_muEV<2WlS%9S4bZ8c45OQ344yjcZ126$&nRNpPb;tQ4MoV%K&39hgvNoq2V3&&ZFXCIuF^AT+a z?`~-_5A-(+P)NB&9td7LP|I-nl40@jv%3>iCD1CJzB{s41`8Ty)RZ23*tzC-ee zwj5^9=s}zBB>LVCZka+m*>gh<=x%?vj5k7S+&JvtC}RW^5M&Y@Itb3lZ)tI_(Rw=H z?kYiqkcN{+#v)%L*5uC}UyA_|H3kpeP)a&tx4@AXaw&z+gA_2ixXeG{hO<Ecdo*AdlnYMEoDWFMH;{f*Viey;+p0cemGkeas6By`R*pK4!^70%PPVP7ES&}!};TBM9^>7IfC$JX^3b>$gSWbWq>KKNV zk#O>@E;1st=o>*?sjpgK@rFyJ3^`mOs&9eSuH8zzID);Mq`%ZfQpQRDyxI*q;IK?I z4N^5{YS7lBsGFKY_QoRvgC_? zQNDUEFqYGYh9mJIf-=Y~I6s8u$Jtwarf%B7z>DutiMHOe5{nYn1}o`s5b+O27fTHo z5txFr^)V=eZ$T$1a=0jp`2Nj0k*`If)8H{oulF!w2VNs%bmlFo$_YR2NzSvi0Z6NJ z2UGbFkPxetBC1D@F{wvM&=CkHZ9#$zY;sNSO{-P5qFFtDed-Uj7yzVHi97?mKN zx}i0<8Ds<fARHD}!5 z<#5&n#ZyV@VP@2fgY*!u@>LQYkLq^qM4xDCZk~5T8f^4$VuYU_v{C*A$o&fjUe`*# z_*zW8GkzQe8rBC~s~VWq+;(}EV7O;ZRrj@YJMntGx?-l8_F5%%C|yS!rxsW(peH0L zwpMKQam2EGIkp2#6@%rq*=qg+H*r!OBUQH(wP;~LP_+t5=qO(Zj`Cs1Dd*0cCn$dh zc^h(SOTTi0ES_l!wrO6JZ-u69N;*0me@Me`lT~h30@1ouRpn8vYQ!fEY_B=>Y}Nx)$&0{y6m;| zag7poO*Y-2C(!+MyVm61IgGZjwgbZ{)$1Q`uRaJUr2Z6ItKnvzsw;gvXTztg}kX5m>aB)pcj9tID zE|*SajArC`k37VqC}ZrhI^TFXW5*|*${Jr*)_6GWI69R#4nf8@Uf$TTl}}}k4Z@zv z9A8Jld#m4{${hQZId-Y~r*cQ$j(e9ocH2#-vd3*@kKHsol|Ody<1poq>n2}vs)FSC zZLEUqVA6wBLmp0rKUGDZsv`SP{!|rtz^cd&G3BmJeyWb_r0t8VBiD}7fr=#8#y!YukB>Ze-CQK`CLY2T?5hfwURF+^Ql&HV1f=`E4i+dc%mv2szK6? z#gdJQ-an7hyAR)mvazijW4zo}L{gQ8u0NYd!n4#)`J&q+NPE<6@7wg-U|#&qy?;Ey zk?Ey7eo<2EHP{<@l`r$UoCGM$ldPtH1KAuE+FMI%un`N8L7@JEfQdG+m$MJ9TK8{c z)v@|jPlM)^gSE1~rCL6yqTJ;7xeR{P9n+g5v6G2%NOiD->!(1Wl|rggEiu#{jUr{~ z@<=6sd(^A7msr2D`+IgR*Zo$fX>knYYu!urT{ngGPeXOKUC*Iflla5#AEN%wO%l|k z&Y{bo49;vSb*2+iY#(6~UHJnRBQeP*f$duj56e(tqvf=Tb?c%3{KM_1k0ES180_k= z?cexbp# zubd%`inOLP?K6ziI9$eYuIw{VYYFsd?aMDs4y)ZT_8yw#h~e+gV6nKvqejW6OV{Dz zYC)Pgr@+?K^A=9^zfcex{&B~~&nO2+7iHDgD6QjOy3jb;vOiNWpui$tO*2XQfz8@F z!tvad3CA3YL)z-|+7rcU;P?--A5~cP1lbtZpL!?FVOgj7eD(Q9tRt&TwyS3a8}lcg zZ?qr(dcP#9kwUpySYS=hV`s(`rgv&*KmJW004LbE6|>$`qHiSw9ji=0J2H`$7k~QR z=V6O8bQ-S{NTa`ZpE}Kfbf~Bi4%&B+S2Qwj4g{Zo94y1gUoSoN)`nJp2AOwPg+;Gu zzWIo;ILS1vH=>w1h;#z_WhWQCcmr*t)L$^xvxk6g+&>nvFf&9ZEe7O#UZ^>F_?$lA zOu_c{^hL)N`WmBT1;*=1h6g)H_dAr~o&51~N!~Tw3*~v|%w8_lyM`N7wr_aB(Mxz| z-rtm3T4j~Ldekn$odkX#YSQJA{Dx(M*EYKi@s|tC?RMRSPF|jP$FQ>L0)w&%(!wqc zu|Wp35O$=($_8+x`<+`Tvt#u>ngO@ce-rVUmUf=I=5L@HhnfA24CiI;8oY0qsejpbxNdQZMs&Ks*Ne04K6LIi3!Tg4O}l*TU&b!v~d`xG&Mp7i~aVR(X_f1KsLKo*oC% zA63kfL6%wSw%}G*sz3?DHO=Tv)KLT$+&Ex9I{*n`CLdmSp}19eOxPHrWI&Ve=>BDWtu`dXyV=9 z5=K9fy2|M?rqmWm`6a+2xV&H|;rn`pZa;>`CsR$Jz|lBnIK={cf8f8kJb+DW*e-cm z!vd>gZ^5upy7joXx{| z1K7{&Nqw}no>vK9E+>xHdVb1_ZBQJZWy^mk5DcnVGTS8tD`21) zxZv1DYVgitY0l@*@ledBq@aCPW3tXf+a-nJFG5Ks0)ZM8XgVyW}T+( zd_#@!WeD`3kUn^7)aYgKMI)6Jd79#deK>6_pTVbQVKpUK1#1ZySS%G{JfyG1TOAhP$ar5%p)GpBViX7x#jrjzM4E@=t`4b3(n>K< zFO0|zmvh8);71SQcoe5_A4-nJ`qQi#4q|neQ{aWb4Wlm*V1B};>p&jyPQw)(l(^l} zTQkiAQ`C*NCDtBzTI2yGHY=d(QWs}4G)vQ{utjzeAE_Fh7GF6Dw7WWZ0)a#pwz&dQ zXqm9f1Q+A>)At4nZQ>@sV*PMYClwFK#-Bp;>WrgEuqE)cO;be~7Z^AS(`QhUCv0A> z3RPKlkl+(t@U`q^vK8Xa+tD#ayDmxb6fi)qyx!LBNBOhfk64V)V!xGqwNkx=^|*T6 zw4sU!BqrIY0#mK^-4!;EtZLNcKv7OCAgp)M0?+ToV^j0SfoXTeu;U0M9V-XLyAfg3 zk)dK@J9uaX+1dKuY_G6bb_Ojjr}#q;st!xTYm2>*Sp`AIpOL@RBhi?Qu*^g~T+*>0 zx}nVJSOpDt=|VWyne0u%l^7v_Wg+57TL zgLxjN9q!7IDpg1^2P0WMa(sYHXkLv78`=;sI%aFI5|eXZn(|2%7WPP=VfdVTz$t^1 zIjV%A%Oe#q?8qmgEvLusp7`$dIpq|+;hdswe*LbS&Gb)0p}(|F)v(pkb3>$ilth%z zME^;)Uw&u%t|%FPNlN(?)&Gx3pdmTeeqo}BxpvbTIh$|Ww58)|~dzi1efHU*LJx0g+JUX0jofrA}-hnrg-+dD&<-l9# z+K%Yu3lco>5y^j``QK*JWyj0xE45tZ1vMzR7)uV^+jj(qkqKMBaxD3<^|KEnD=;@9 zF2bjwKJjNg-jK7W%P%h_-%@=b|<;-oI@XQeYK9eZiVUiuA?VfXyr(vJHbSL}+Pl@k1RFFL<@!wF=5>*qsx zK?NXB)^iOYztk614xDL-WQa{3oaT-PH4i!QNj$*|l@xB?XP!0lK0OhOUe=F;#>s_h zq|v&O4NJRZJwetNd2G|KL(=G;6uBrbViOMnnUuTEaZD7X7(ui1pjTefGID#a&bY2` zBo)p^6AGiU4cN&RW4V>`96xK;$S0*|8b+9S{AMV~(*=Xl#2ybyJ;_DwL%Xr|>a@L6 z;U-1Mu_j|SoEM5`jitp#vZ>KGEnW5=esEQe;R6s6oT&+(w*$EluJm|Y>A#8iOw0Zb z5S1o#OoJb2@;5S^m${ov#DOONXFZ;m*(ce+w7)e2dYL~AaDgRkC7PT=`Ko`)tKgFv z)XR6|v)KhJp~HN_2R)vj*=NH}K0CnN$M7|O=o;JaS2++9Lw}Srw3ly^8nlanx$UDV zZS%-=s~mNSBup=cKDj^YkpsVHco~5@m$ek}SA55v9?#3{e^()xF7MZdZ{78sw#1x$ z#Vy<+HkGuQq%${r_y>2s_YFCPUloQ~Hyiv0Vv_R9v@Bsw=@azqnpa=PLeq@8v+`Kl zUQClOm@OnA&lt_^L(<%bFx5H7%g`9!|PgXrs?j8%x{t) zyQlh=w&8CvHuF8U&c&=|JFixZMi7)NLyh54>7o?j`+ewDNie1ps^wNM6jizf8k%ZW zr?VtYb)_3dv<^eARamIQyX+cR9)<3`EtQ4q#L+Pc5Gl6sJ`6r$^*~$~qa+V^5wCv) z@PoBkMvMM6Q%{;l!$g{0lYG3iaMZE28>eUYWqc{;my;8^18-bNXa zXXXM#v`(CGwMq#9C-+4i_*z8Y+Nv{np^uq}gZJf7<_V)#y|J-2xJ=%-D726%;GKGPQ!~FlBW0UpLqmJfr>cGd{#YC zcS@9p0;x@!p2f*;$_HQMV)UJ>?I*2gD8+lS9dKR)2#i7LWcg(eVvWUQ+Pa2aTCY}l zQE6TG*k}jVX|flB^;i^ZI#9*niWy=hD{-@~_p5M-cQ(r%2<1PA%OrjNAt|dIp*|gB zqMTT(tfU5NED=k_4d2>1jlW|Ux~~c>vF7uo%a?fh(Yc|=>I}OW7U+n)S~@989Ay@y z&u=CtrhqVlV4w42lx(PJ7a9Xs_0y5TXxtA7rTs+_h!8Ae9kGUKd{Z&){j<^=w`IqI zLN<+gyM-wt6X0E?gGJ`S?Gr{c*eU|;Ja?Z>7T}dSkUd5 zdx(Z{RiqCN@SBN2n=YPOcIs6*BQ%^s3FaokZG_cg5^PZEM-&;}&CzD3cwt|pjXr0< z$T`zVkZkOrE)HhltO$3sfR-6-p+y%DfnD$rJ`fb=HN0<$dER1>%AdRKQD=-#@ToSE zNq@Z59R0NpQ$ompR47yq&*iUOJOVZ>$oynyx$}jbj38Sh&7UR&NK-Ez00Rr2Jch4Do z8hHq;k-Gk)esT9#?IJe1lsdavQ%_&jBhe2r=5f2bt8RALtFUC?fFA9w45lc{N zsSyX}_He-?+y|@&PkbAMX_*I6NO>$tg)IrQoSQe=yr4^lPrUo#!{_(c=&G5gaJpjA zT1_~4RF!Lg4PiS}sQ(0E92W$fSji!6{{a)Ns|7~vL#h1d8JV)TO&^X*GJM2S0%rJw zIuBO$LzP;1|qUO^PLyRvg8 z=q*JrUk1A2jE1ZbyAu0NjB%uO@$r$Rl}!UW{9X1Lg2P1^F^jWLhLwzxB+o0hfUk8z zo=6VphrM^*sk_!hCfi67*|kLbt$-oFK(kT`Q&>DCRe_KpY|bvj3>5#S!PPMhmL!4n z$N>tq+jqC*)n&7LWa2oXL$WwvYRi=v)6ejK%Viv&2MNj$Jta*cd13e%CMmiO@O$t$ z$7!Kx&2pIxuL+S|o-MVUBHU)78yta2l+TNBwIE5QPcvU^ivK+rk4|9{RgEVHUaQgQ z!}%EIG$FxmMi5@2N6Li;b*@XoZk1M_b_ZxH1X#0%-+{-L*y|uuI)9Af{Wii~I{krS zB{n|(;A?5Bq&lR5ha@eki4EsGstL93yqFb<%3?~3shoyXn^wsax%y{zenb_57uic@ z=jKE2GPuc*TfwR*!sv_U_cWQQi|9E@jr${FHQ5&}<8W&<%sTUQ64LMl#GwOD!_oyT zCJ}3=sz!+duA(U8kzIosqH{;zd(fHjTE_DYjNz?~A$cg#@l0A>5)5+pC3Pi^O%=_cF7m|j7ZiSZh|778)?zgFwYN=K%{rY&qdI#~(tD!!pa zChAH!5^{PMNwcF#a%dy%vlKzzjMA_`yM#1$awhBkjEy`9aR&LK9BdTwRDIoRTYoNr zOZ0QYQt;j?1$5d;?@Ew>v}3V-ZdA(=X3m&ard`A{lf)cM@WiT7IWSA-_htA@W)+9I zf=F7|z!7;iM|8Ggf&rUJaCv!oPE{Cuztg207S_bssPs2nQt-2c4TT$pT7yiqV^c=L z$;(jMY!%5x-v}}bj3<#=&Gz1eAH6EDyXt0lj##AL&V^rUn94pGYhI$%(}0@HQXz!W zhG-zYzD={uZK5q{_G^*V@vpbPW5IYs_ukh6*j^ruZBXSOtqIRe#4x5>TU!@d}Jt&A;@H+Wmed0d&Q zQRq$YPon|_Q~8i!0XHe4eB|i(dXxk$K#+L}EtnY(MO2MvgD<+LVOcRI{o9~C&NpCC zemstN>W0>wE`aVbB+I) z-x+NNy^Eif;v3N7Fl*={McvM5U19}O)gqlP@O|WHx*;Z88&w0}v+o(!XjXGit67p( z+)b>ZK>1oi_TulihT~bu(@Z-RlA5)lX6rhFHpfN~ZNOM;PDi(}wo00i4QzV%+^IOJ zj*+U{i3*3n3Ts#%#GOnaXq*DS5S)R-kW)+e@Mfc&MHyv=v*XPm#V5fid8vxreNmJ_Vhx2YCSqEnPv1Kl;LfBEws6WjR7XX zFLhZtQDva)11v=1n5o*s;ETLQFT#|xK6>1^iU%de;jT97nwkeo*d-Ub54$(QAWqwn z5rrV-fFK2&&G|C|I|pv59~UqIib8SVQa4D&pVZtRU}e6TT-_GmM9^$!k*? zO_*1*5PcC*XHWLwt79ag$^JYP0lUh+j*nQm65D=iIl2 zQ->yOSG6t7!Tg(MwPv+=_xz!E;YKo@x6|=c<4$jsMAwK;#f}@bK08Z1^&5tvRHTXu z&B*8NWV*7q_0aH!rBx@_nRz#T%|g;Ks7IGas`%(xc;N1O{i^Qo*hSO(O4n?nHki88};Us-e()T6O zcXTbKizL-i_(^WKBErZJj5|?&kd9`k&|9**K-%vn$vF4{_vGsU!EHCAFDD_H}%W%P4nJXV0SV^VR1cxyP?I$vs<0AffXmO2xdmLXgrv1RCc|zPA=O8Qq#7 z8d5gou=$b~GPal$2jME|5^@w&%ZxU|v|1`gzcBg6`aFd@C>jXF@>tBlB+>oGQa! zNIA2Dcl86ZH?VB8``s47(R`!_^@pZ)%V?lI&(kr%&qt~@v5YzfU5`qdHx}h2*^gDS zABVv}Y=zM)hiOO+GIE{e{TRFEt(|w_8kOX?QDw{NwBwoVPq{Ira&tZIqJ=_G9!+oa z_v^&(lyb6xa)SO-rg_DXEQm78-3Yk6*uXi_eGhs5MZkbCB!I*3DHGQy+ICX#AtVJJQ%I*Ro z8RbRHb?Q#7BDJ<+j2?KQK^ZvHWeFI$Q51ThyDl&idKRF$2fKZ;ylB&XV$4- z^5L{t&s-snKa~_|W zoiEW)fSIDPZ3cCCOirJg?O=^*UV$+FHrgDleR1Nwzd_d&j-au@TL#P@l8;}4l)v`=6GVP67-Qmp z`v}ufxUw%qewzb+^(S7TtB1WT-hPCfF zHEP(aQNy8*fr!!M_KRU;qXE##*uR+$4I7TsvB0;1BG_OXcCb&}@l!{Dhqu}hU|lIV zwE|$3s&6ZRF14yd6Y@n)QQrETf+Go`8%)u&8MRM~O%6^}8;=LfOS$fggs2+z5#n^;u(V^r70Z?ugBRVN;*_w&W6uYrLhEZzDz(XYI+R+0{`)O7 zT-(hf(GsU&se-RME6=ytL~aZwoQEh4eUYqcXX0#NJ11I2$veYS4c~)XSFA}o|2o_* zaQH~FC3{3r^Kfi%EeMNMp+<_Ic99WGa*Rl9KTXE91g62Cf4KehF@$5-&L)r)hN>>I zlITfVhfKaSe#b-Y*sm*?@8L+61^@3`Bp4~NeW*rXM|Y2~X@8KNx3)(a0bA!At&xz4 z%dr#f-VybezApH+R)x1x1diwJqXUj{YCKB|33F&P$pJ+8T`#YRx13tVh%)GJ}mhxX+F6O zt7w5x1T#wS5eJLBo__Sb4q#~?(Ly*Xpt~3s0mUenIR$1gw=Hw2YP=;B+`#W+ccVxV zm*{EQToKPy$CQzf;Cl9v4y-6$*=L7c1v_x5eFxf+e!x>{na6uqVKAY7lqau-7pl`h zPwR*%6)iw>-G@x0C_Y!LL37P3PORi|*XK35l;U&{rw>hEHJU2Hh0cg#a3%LP_Alo* z#<81i=H7u)77YQLbxW=!a#THQ<%|!-l5=+QEEU(5s1+P?FlGWBqYdnHbE@i3W06B2 z)SffTdvkmk6BpPjjh&EdT!c>&?R88gA_SS%4A)|Z&DqM9Y@&v-=#3TI#q#ZmUmdVC z{;D4v!=_=R<<(2$^#=J z)-{f9FX0Q-4Ds=Z=>SaNXIvcRA zq=Eg9cP%~|{IFR!>4!gXmI)<8540734D}J1yxIQ@)3xyv#Yt2Ac70=4HMxz{Oy52F3_H&HF*6`O`qt{vOBj zGVAame;i8Vo8nkrW?i+yrLUMYo{wJU?0_K+GjofR8$dkaj1uZW`2{!W$?X%!*@`PW8=ib)j$)UVpYahf~igibpq4n*D6cv zSu`6yb=PP9j3FZ)Gq8gOc-Tuyn9-z?Othzy&a_KsYU?T^YfR0^5a80Bb1`X52KW`QLJh(YC*eHH%PNV0e;%=-c}@eClc2)m1m957s$y9_|I|j7 z+%I^}Bz0nooA%LfkRH2d`j+}+kGR&k3>nFfg3%XngID2ic|lN=#UJ)cY#j?S_aprC9W_!e!}MKKX2ga}gQz7PNy?wAjHas#B0@Dz8h- zXMRR6E(nuUKQe*QM2WFr0Xhp73&yF7uLG{=X;u0oI*CZ~g61UpgfAj6p2HqQxa9|i zC4z0fU^h4(9D$aG69RRZFyJ&Jqlr>QQ3KfZptJg=Ty%u}6)0h!Q=e1RUQOe5y|3Bi zK@*sO*-8QMz)FQUDS^h%4xANLLou%QU0uuxLGdGk`o(o2c-!7Y=&DE%8g(1B+noAQ zKQb(pZv`rhGk8I;M_*Dl1eywri)zQHuS)P#;NOU0i87aUCM|7!5i`zz%BzoTuJ$45 zIAwWvN>ByRyeevgxMh2;l%arBYHWKZ@Vk{}LQUhJKHT@VzBk*;h;XoD@=CPGk=eIx%z;-vbNXZGz4oAx47M!{7wt(=QV2%}Is&Ta z;Ec<_d5MGn^N*8%`NJQ8dP^nA5ez7KK0!qX|Ji(C68s774%1l>!}>g;ol&ABgom?g zaXt|ZDMVz#vp-$_0a@&im;X=jmuI?bMh$OZ_?@e}R_b~ey=XwkppU;J2o2Lu!7(MN zG*rSodnVmG)Tj0^K#2VKcCRcbS)_?DoT6PfW9VJ7^U;7LIZ^VKF89@H(FZ#SM zB>|kbRJ3*oEHUO6`Th!Z0Sq4tWI1ZAEcvvjNf!RHp+<>Oy9$$x|22^-I0k6T>Y`rh zA5;|$&nRK}6CwEt4t3kk+P%hlfVMunLCx*I=B?KV-t|`h_#fApd|0mdS;HwC*DokD z>`hey8A(1229(FH5xdsw#lmgft&8I2pr;0kKL;uqRD*!xFXZZWsYZ}|)P@*!NpLm5x*Q$x3l)~fwgJ#Wa?z5D z;Fr3rjE6+JiyHi7V-vR>G438f_OH1!=@Pwd+FZ_OmEsZMpd=TW4vfHEKawXbrG_E7 z$T|(}WEg?d9sZZaNxuct?SJ`QzoXhZHgXbXI`E;3Ai|$&RMK~cdOUM6dlu~E5;V-e$*%kDQ+YeR%%SkU_pZ3AksOG94!x}hYg9s!I z_nPc<^N7z5GmnyU5hDXwx&m}jikW*MaDJ-}&uhS*Xjm4ewvLuVr8aJWDg%acm zJu7J?!eI^G!ubQf$qad$1u4vza;u*?iXbaqDh_ZMr=PnW!7gEgJ1oE8A;{qe_*58# z_=Gs?Z2IA*Jm(uP&D>?a?nLOANTQ@lpB+Y%d`%EDaUH?%XPk|BjuX%=$hG$@H_(fO z=HU=XSQ?(u2}U$3=N>fmTX?q~0dKJp0uTW!Ql0a!&vSjuqD@aC%zpPe8fQohLO^Q6^@$+4>wP%)hq zRE~*T8<_3mUKs@g@74_tV}lOR6r3JG)Y!4CQ1WT%Y>zmQ#U%{v(d0?hW0+MxB-qn< zuf(pgMB#s^X9}BDrk&@4Cuj3rN6QAL%Oj!vH3@&0=C%$hwa_;kh_{Pq)1ZVwh(Z_= z%Bgj7v{2Nb*40i;pJ6_L^PDv*E`qKR^1J5eyPx|8K5_RnPSr}iq{C?&gY@o=hH??IcbjO0UX=?mJxu z)JsWgv=hvK;ym$?PeY^YCRM4@**P`I);+QDj*MT4PK|f)8fk66(x(|y*fq3tfeFdl3^_Ss7p^)(x&Y&`^tIcaReQS z#?^D$-c+1>%bKuzoSW^%w57cTFSqgx=IkwNN-G=Q5RXKa`~ zhLqDRWjBs<#~bCUzNJT~D~?ik^iY@2!T;tE;Qz+P*v}VrX0=7RRPob~ctIiQRD6+S z7a&Y?Z)RDE;3|D+CAGB!6Y3gs%kH^7lTNoprqWu&Jn_ahs?|m5i0A;eI{9IJkpd5N z7KVL<_&5bD_eQ{AUQL)5DvY1G9>Pv<($tmM>5(R16lz1Hb%qfhEGo7NJlb@=2r2%p zK2#jVL3E}4Yq@i#QTr5b9YMW)$d-%g;IpzixRY$-2<*&!N$#Smz=HM0U}F%HE|26I zS%=p%N5JsFdMM=Ertl_G>{!U}VW_R7-b806TYm^CJhBFw^PgcTFLQ36uLI2=X&#(? zw&R}r`x>Jf4EinidQ?AOqFOr5K;Lr1!=j!}!w&f^bomwdWNQlLhVR_-?4kl{_0)*#whuYR$9_R^?ZCbmY%>YFg7$#< z0$E_@js9;m)hk7#*%36#Z{Og#kekMqjk< zZqdk4z3@o>cozbu=LCl-;_Hw)gGK>Slq-tXdRMzdKLqb)$C4p^PgM`gI-^1^ip^-G zD;0yjJ+i2aD(5{Aw*pf#WY{9W|`EqcKM z(B+YWJDWXN?n$Rd@1FXuHF~)-tD9Kgb$_G&sqI4=rq{i2M&8S5GL|y+5hQC)a7$Jv z^0fcGW3Oy5ZR~fG&Mii9aKS450{DrF^>sxTC(!o`U~eVP7M@V?^~=$9%oshub$A!| z!ASwx7VbFP46|PAdk0=WV#_eIIpL40aIlc9s1N{Y0ETpzBVF#}gzZ zm$At~C@38Wnm+?!FbQH%Z@-W*c*v{8M6gp=bg&qqOfc5S(W0X5;Yry0xSWOLdgSoM zi-5v*j%v=b_Sx3wAU%+t?f6Ud=-qVP$sfN3J)5oERbS<0loY^wfnagwLI7O81-*G; zA4-fY>9?Jewwl|VkId7aY?CI{7@PEQFaN|?2HTH1c1VPgDv6c{ay+{DKcNEK){c(v zgbyV-bk;*DZfr`c{Y_*{JIqXrw^{O)U#|7s=8;A(;5$+0NC?eh8Kynb;`ct%@P*ee zH~#SP7u$UYI4PuSpH{J!xh?_I~c%__z#i=7NCQu!68bpYjbk4{Py&KAaDX#>bNtlX9V zj;bE1W|QD5slZl;$8WChr#tDjx2C48g2DU_dO!!!vTaKoT8k^!Dz{_>#;V>Lx}0dL z$J@#dn^=K~*uVB>_;M(x5X#dRl<;?ndL3e)4D}K2pkBU1ja~(Hb8J;mjh*>Q(+Y)I zDR3et#eoKX*G};cd%0(Jz3?rSlx#5gwwFhB$jyV4NL`y|*6pb|zr2olrY7;-hHg+F zcl2}WuRYTVo1_5kXRWVkG}|}D(SFBv;jh?g{OY19!i6BlO4%whTC*|R9I;dN z+;lLQ-})IzNPsIH5A8#%@s0BJikHKrUrQ|bW1&{z&O8>+IhxL-v>Qj`BDqz4`vg)4 zq8d7rr}+L}B|_u)x)Q3o!#Q-NvETz|IyH%m>e&qBtv?q|pWJ=V{Dk|CJ4LNw{;?~U zr`*oL_CztHz&)X)ZPqs~@12`^kabx4-B})!d~R0o!-i`S@z*)=M9NF049dao9)qN; z|Bqwb_fePJrP1%{{)k;<9$Ir6L}3{yhEra16;~gzISz2s$0AvVjGWCJ0WJG~d02YhDcv8)cFkZ=0cE-yy`%oO*jQ+d%(M#_?G`zExk9HoCJJiU z+lkIVc64${3L9eq4z7^fBTm1j`x|zx%Wqg8*WRZ-(tKQD;wGvXpV%pP$5AImc$YuJV-pTMm(6UcN|h6nSqX(KqJ4Im)Boee`znxH|Ogu}S4Un+Qn( zZ%dZ-QWrGpphLr*x1Y$}N`THYho|s2nt@GQYjMHuq@cz)cf75do2*YxP}j(|s$rv= zINhi4Vc2E7GnrtCO%Ic0TVr4U*9GF{=TQ2NWP$<&gDKg{QDYq4-$J1=MTGQ=EJN#FkrN3jdw^7v(r#jyJ#)+L?73 zTxtEyX|4*hL`W7|Mm(Jye~={K)ALqDIBqAMLbY>Gzu>Iev0R^kTD?qfzH!(EVl!&p zab{0**$~D(@A~R|Q46SHOYe6RYD6bIypD_sa>KjK?0PKM{FFKytaJ=3|jpm={D>AoUP+ z(F|m6ND`}yWN3{p3&MvoPf_fB{&X9(A?&0qLRw%OCUkj}@LH0J6IMhfN^&ioEVD$n z!A9KT6pNq{H-ccuGNb!_%dCfpocJ_mU4&Vl5o(;R%@F3l`{Bdq_t$6&&eQyXKs;-P z;6om?zlNYf!nqGa2zoT88nB6K{{a)Nt3_TUNT5e$;S+UarSq%d`Wbw|Q!&p9UQO|1`TLchuHizKoB^4L`zk>69EfO+B zazNt|?e5+SB9k@t;?DKikNk%A21gRbK=FWn;o@2F%a&ml&Xp+!=$r$K4iY;FLrt1& zV~$X^1k|MqTg9gt8M!2^&>I2sTCN1i(i!GV`V`Md~M3zAOy?DEB+_}@OK;3(WV4G1fr8hmB*8VF6Z zGHhupq}RDXxQOnz5w^OdmgFHfaJP)Fog~1NHT(|TwG5*LsA7#C6d0ak_66yrI|c(A z2XmE)73ynUs-#MLc{WE{7$9j;O>BAy#(h9)6c@81QCW<=+;E1rMMu{Xd7@QEpSBHL z{1FufUg6PNwYlw0H=J^p!A*vITB4IK`l8u9O=jvMdX7@lJR)LU*cTyDauo@^qgRgK zI3+jJ;Cmidjs!fXvY5L_NI&^43L+m4s#%R9$=R$vB04X<_npEQ@IRMf@kQai=kB?J zUTSJ4-a^6yc~GP90C6+-CaUAGAFy;hQbRM~M;tl`&X7Xd>q3%X3Cj3(of+{S8|u0Y zMs{Vi!0?P6!4!z=Qtde?=zE&PshHT6YbXtSP~f`vg5Yu zKze*n>#oVxD<(P0ae%8u2dz*gFXmyE{GD$l%`Q$JlepHM7|lW-{Ai#IhOK4S9aRwY z2)C;mn2<8IX@mn}1pysP%*Dsk6?MW{!eC|W_R$S=@QMLOFdX5FjC*FSqA-o~r{NC< zY$8rkuyw_;BayXp^Gdf%bg03L5MUF|JTzT!`}?9NtTnm0zV5}DpYgQO&kal3d#h}a zsE*a6O7oQxG>#BXy{H!XYXoFjuQ0x-)t888=42J1lU}5K>HIDk1*}x30Uqr0yudVv zvpF8O#Uy|aodlPcm**&6toRUIejfu)gf z@-mdRe`WL0H^R09TTC`yslo#7gE<4=I@p5UdPn9NKJ}NnNXj_b6`A&2;xz?xc8)Q9 z)R44M3+py#8o9kEHfV1h3YRaEijH_d`F8Ua`8L!d>}j#hQHC0-Qb;P~SG+et2Ym@- z1GR>|F8E_9*L{&O%2&??x>pWJNQA_fS#ZBTnjhsik+90Q_?EKx9cnaEfv7NtEx1}A zjq5-~gnGvj0HUB&2xf;?IW*-&)T^c4qjIdwfYxG z3RR4tWjusNmH9@XdcrDy|zr;+(OIh(9d? zrEX%jKC&JPW8A}8Q?A16gHP7IgQnHzPwyR165DQ%6bxEa)#~5hy<4RJ_hnwp-=Q}A zu1l*V=KcrlNAfLiME*B_g-y}zKjJ$c2DX9C-g?io%`>n8!4S-!IIu5`jyVa!+hKr8 zw4H7r+rXqn4ZK-4rMMw4;4B^fYIG${1QLyu0D}IT=!C6&-SzeCMA)e zdN!}Zb>lvdE8$MCV>Mi1zm1-t{)di#56(h8aHKs%NhSK?C^DEXk5tIiEA8EQ1oT_F zKcUBpSfc(W)8D%n_q*;j(Laq-ZOmC|EtH!LT5K}3MF!{cZ6=^Kg1cVv`?ay|@``JyY5zC82gPYpmNouw8RaVsu`d%Iyku$0Y4XVu zBl97CDn3Q@A1zt!@W)^)DdKbtZdl_ZItKeXa&Gk7WsQ*&2HuC-U%xu4VhE2jzG@e%ICo*|Zu=-2N~*LfRoe z<%);&{2`i;2l@z|L*Zra5?~u>>hF29A!kmPosDC+WIN*!yfqqeZ!R* zaLD<;QktxD#47~}hMmK;pfhI8_YXSnddF{FW^CG~zQskChi!O)fk`#1uv!eX&2-M& zkeh7d+|`EFec9B7SAyGXKawo_Ae#kF)rHtF=D1MEUYxph2*PE1XBFIU| zZ5DEyh0DlduIY6!`q_`zf~cjl;kDN>$~1s6II}sW-^)d^DosxKpRAHyuBR1YmJ4F_ zQqoZj7T|t<9443JNa)@kZ}4k(&*Uxbp5G^C;SxCI6_{af4XhA9H#s0mp}hYDPq;3# zUJg&V#=|0Hn2ArggfCc*`^6Brgs&7XuLC5+m!FUbgTYvGEltg9`a-ZT5+N-A34(Cc z6?r)X;nH6}&o_cSNZfh?9o)ul`+*LMqIE(WjE@FwkhFof4l{@!al#jTA-wm|2tD8Wr0w-LYg%z+gJ z1EEr}0srf`X(cVReJuzV`1PWp5CdDaVe?uy2{3i6wu|7 zlFzqCq#Y5WU(h}KU3B&po9?FWcilwMKaEpS(K9AI*8V3&e?4xwc2JJNuwCT9Fi#(P zxa{{MhKBGz1Jg#M^EiImkVK~4g90VWS;|`3w&Om&IEmX&oiNKFm-r<8J%ZCn_GwGNFOtQkwTp% zN>XUFrW3}>UwR6WJ?$D=$5F`h(SCtZuuAQ7M>s- zp#zwlXULv5v5U~@@bn-*gt@9AbXP_Gn4mw-L<^0Hhz`xi{EI)=0&cKQ%2M5sf5P6I z;}<8u;TFBsb4i~ZgEj}$dYFgx;N3IdMYfaH<4^1P$tpXvUt4>A1m^7eeEhhB9W%tr z2PeTxjDOl}Bj_(UghjORNt0NuFg+yeW}s{Pt9R=BOgSy=8`2ISadC)` zSJxkFp7Kh!lOpl;P>RG|qh|D^O7vAFqK=eEU)sdIxFM;b5(f9pj@2y*Jo>`PC%(jD(k(K3EN^ko%8=KCf~bt-CFA`%_)hF^D)mwIM!drT@ll?F-w$p14e@q@E}41PfeCzT#k ztFp#-z}VF{P3Jxp-*`(CI;g|ME?Z??C+%(Ulowx!aFkyj3q)ztLwrHWg82L@PoI~0 zL8-Db&WOfJw8#^5VQ(SduoRox+AREHF3BmR$z!0tCR~2SH|auoX*Broi;8wjgRR8q z;vnJMzu_ythlA^1|M`2^c*V2koCo*XyZwffIyXI%{JGyCS0?FaEemMRfyl8YW8lB{ zd;FTJ$@uXY|God?RAlB1L4WT#4D7h2r9~`%?l;J-$*2UCVb4do@2EX(`Okd^;WN5* zWbZe~k2-f+Mlre6`wjCUF~rFCrpCv-BT8GlZj0Z7)?>EdRQ0)w9`Hy`EfJLB?d($ z)I+(K_8bgOjgaeUWGHiCzk%RLa|DF^wcij|jjH9QgpiB-4@h`{dEpMHFYPxN68Dq( zwN47Fduk<8~^^IiX8zkwUI#xiE(+J3`b=lHByaQBnnR(M1KU&0fnP-9QG{yUot+_xkB!N%I5u^jd^joHE`XPLr^} zES=;#d=W}}g!>R`aJMunFR=+EAqj|SCJZvh41}zsy;Hu3V%%GOs4~hf(vZ?_BeUSb zq-X-iDqR3dUi>&$0>OqMI&XABck5fAk#wbX)uPCs$aAcUB4_YD6~-8k;;>NV zF@qq@=Rd;i=>RzpC8t3bEg9B8Z$5PUE_t3P#L%@aZcQ7DgHPe4VeNEZh85#XIPOp4 z*8~eo>ui)AO_vW78GpGg_98CA_B7++L4T5SzsS_(v^pe}+3RNZ1?`W)a6A&H*({ zOJ)6n?%D66*zZEgT+a$wOSl_}4<+&A!fKTu(WJe@PLABll3g3~k;6KUnBMJtq-S0j zsitRNoD;&ceQD+ZJ>4sLg11=PXL`WBYxB8JdspAy(?G=k{*SF2c_a-5m0l!u$SO*1 zkP>pguC`9i|Nm$2TbCn8vh)7?DG&~Yhb@t8kJggqol)3avRgyDd%D$R&nRMJ!_ES- zs&X3z)Z#*RZRp27h8YL{H)=m8bNEJtgZ4bu=}^}?{b1)3Y{Q@7?BCF@YB;=}_yKHt zjq(9>`SJbyTFK#wT>SAVxm_bar%q&B#6rIY$D5mTNDTL1vwb!JvL80wEpeDTZMUH0?_sZOEAq$cEb;_>bho{T%jOZyO&! za_4^Bxs4(2l|dVi$ndY6ial(4m4{XEXcgcaXX+Sx8ntwy*=I?X!drzAhS;ZrVq=3rp|)} znK$Ll4Kr&TD#)CdIoZAgOI1V%4p{5&TwtuRIQm0^~ zATu6}Zv^5R@lf139+WfF<$-M5^#G$SAKCSR=wM75!9=fngc5a)`b`U5)p9cGi=jd!iSJ>QJfX=C;K6g`cOE5l2kY`7ixGp0sHSDbJ? zc6YOKoM3$V5^;I}-^f!KooFTt!KpxrJWa#lM^3hF& zP9NP>sMoB%5r*2r!1l>cZR^M3dFdFDhIZ484yqaJSb4;oNLY;`~WhCL>D>p&PRzaPHy^%;z^)eBa$t+g7 zA&R-5>PzT8<}(n389a$eR!R`bme^L)t6Pz0M$w{<4_s$Do&&}*(1yL#C(xvPX3 z)McOaE`Lwl@KcSkcd8oZ=gyr%1DobcJ~f}}nmAaYxb$fkAK_#U@@?6QH{P>jq(rrW ziE-jofUidqwnp`mAK@CS!{Vujh7E?*8B2ml=q}4xOhf-HU`Ev@WYnJ&Z5#nC!S7#0 z^#OPviUb=ufL~mKUIY#Ceezk%7ll zy0RRBo8v4BzoD{*8nlKO6cLFfb9^*I!KM|RHtBkPX@1*5pu@s=rDeoDHPQe|LuGA^ z$_PQD?13Uzpz0S(Q1lYUdLd1|l-^-uU`dxpN=4pn2-syY!?(}93+kgma5x6RAbR*P zRThv9^pQwDMQVaXdL5V_kY0n^0LyanTefh8a2ylfz2x=;-LeMzT?~&1jLPK4n$1L$ zHc(kD(g@~vMiaD1X~GU!#BeEAX)bzc#PRS&))1rxs>4J(1_s1P&kJ3qXV!Yg5|2zr zucvcW{0$677WBpse=N}=(Z;zQimUEg1HIA`-gbiW-igoobbHDT$<{=F)SN%_986_> zy-dN>);wq-R*^`76PdCusD8ZxTRbHuc=P_I!F7YxPMb@S^sE3jsz#P@{btw^Jfkvt z4NHIldwjbg3sJ%f6_wH4##X|{R2()>7B`$F!yhXa_$?s*hC1%~WX9VG{x;+p$o^+l zD4OqvIuyO2!Gg@2&7EPU{RYPhGV6Rg9ca!Urqdy3OqZW<^taV1`4sKEGEPX*+oJ9o z_c=M1A9cRBh2f2SxVMw>dg}2Q+_$XG=6U{QpndfT#*2P}@rK-8TW5bSCuJUg87%VQ zS2JFa*-KQm2AVl3Zb4>UE{2NU0~IsKjJ5qT(5!W*g&}7?p!8_5rJo=|jP1OT)jUtE zTStDCF*4AjT#4Lmz2(XVKOb4RY(|bWS`xYoPotNMXr3=}bnWLg#uAZ(EM@ncU~ibJ zBy~rjwx|zvXfYVQVjd3dbGqO5oald$1ACQ7+d;CwrSEl_Y>dB?p_`v~;r}^Pf5I-? zc6@gdYUpVUW4;ZUmQaQ*o-gdUW66D#ScZ!T)k&@-m!MO_v?+J6P;w60ttlO_hdcj- z+01h6P2Qh=*89_rgBI0GPDNRA(<3@IpcBv33RFi`QFN;V_({5QT!It@H9b0w%DkX1 z@aU3DRJlyWDq7YB@SNQt`aI!*kj|4{KK&r*o)A72H`+>M`AQ$>@Xqy)=7VlpFGirx z&K<{NxgM#|+^1(CNlNrd199PnQ|=;tT7q9q@J@v&4*=UFhJaI|KM*luVi=hya^ z9{YV*WbGu#Y3`LlHwP#nkiV!bY3yu10x<1s;%F)2F97Fj3RqIW4mOtPw-i_dJD(oPd z<1k0ey;R5VM^_o$z!8Z0JXRD-V51Q>xBE$5)*=NhNkplb$TSkusro8Wt`{<{($%Xm zXt)hQ`E{vwDoz%?1U~9kErvc|{nbV>`D$dAM`@nTB(*+K#9)EZo=0y{=hGL6&T)`kbYXa(@g%(P^kXNc9HMK)pnR47FYDyhy}3YYD2h;fx`ayqm@fX z4*5h%{DiX|oeZ6pVZs=E#Z+JvcMLyjzb*Y|41(3`5xS?-MYg7`*bp?qw3SH%)q{+e zoe;DUh8=p{RNG>~b@x)TBYv`_84Y3C3;XV=v~jxD;y4-pWbL^|2B>V%nP2PSg3MiJ z6cc);va>-09C%n{U4zFu(B;RU{p|1oK3XOY>+|b+-*a+(Kj!FXE3?_pvCq$SoJtj6 z#46b^`2F9WMnrlfpc0jEjJ*kmo}i1akP^L_mCEAEtbF-fq5}l_6i96*3i3pyDnmQ! zJh}#Iyi*B=s9HjNy~vfUR?+oU2}&E5`I0uOn+yOE&$S9tS`sTcKr+KA<`r0zk&!!g z43XZ8RT)XQ?{R8>_iumsKWD#z|IrqkdZ;?Mp_R1n>En8gupZqQJtsyq$Qhiu4N0$$ zsW7g8|JI8)AZcTWpqvY)0^12Z;!F}4f-Hxs$kP=5zRP1VsZ$Zo3uJ9{D5ea}G zK1_dFO$r4(mWJv>C!?GZk!23^!S$N^Giq6GRQq%a12*eK{+&>2v|e<$M>L{Y{$tn|PA z<^TTMU;dwanw)|H$Xa3#a!m9VrJf8(I=EZYww})?eme{iicIoU&GMh^COr(Md~*A{ z%V2J4O(MJ0((@a0Pw?|7%lnY2p#sl@NYqCyH>IV>RA#ek{zH@h(k6iK)W2HYh6dpM zu*g#xqX3KUC34Ll%&yTqjMNW4{)kcSvcsFP3b|xuYf-0_TBe-U|B}we+GxSNru`eb z*7c|w52BF{JR+q_T7KgRb<%&F^Vj58V7ni*z^NQz=b{6dN)M zGiY5(J;6R}If|}x!TGFho5zgF`R9N49qJsv`3_TGK~KO%#66pcA~A=IsjN^lridfl z-;vPifks;)oz;x*GX-PPu#S!1G}!DJ=(_HL<{PGLhDpkd#S$yc1q-NS((JQb<7_?T zbb5PiH336oVrFnrRluK#vGu^myL(uOf~sRvbtgX-c=+GrlgCv<*My+<%S5{B3<|r5 zYs?_0dxX0nx=~pyn>0sNCiI(7MkA-7w|!^!JaddwHwQc%YD`pd^rz+?!t4DInFD2e z9;)Aa9$gq4MY;(vDAwwR9RMn>$b#9hqz63sYx+Bmkt}3?g$Ru-<|*F{O`~I;r#oEW&oy2bn+hcpeirM z2~IlzBhpZ+c}@E_bU}ACA`i%j1h$^xbnkkGK#gL3Amoywu-JUVb=3two6M-nXR?~R zs(I^ti53=ZQS_n1a%BQ8lk+^s55cK73{w^>+N7WmBWG#Muvq~IR4zoeipo_6cY{G0 zk@wL*BG6|7MPAKS?pY~J-TAaawhFxUJkmQT@?obJqZ6L}xf7PlXGl@D0FF0DeWdmq zOK>p3zcUX+92GUUM-E1mkHL}J=;=TLj^E(sHlR79-xfj>xYpA`%xLP?2_Q0H7oniS z|Lp_PC|mBa0Kw*zsD=UKq0b<-i|u~=(z%(MIXZ!TbuiVF1M6(c)fJJyW{dH6Y^ndgfIhc;}* z;gkOP#b7X|O42RXeY!GlY5#<$;WLO}9mevblq`hg+mbV|>{MRpz0Q2zy2EZZZanU_ zVfNYvvfs##TXa*>3*2zkH^*XB$<1~_7whrelRQtQ$e!`{a@~VI-)dRG1wZmUzIo5= zQmcC32$MAVWuNz)Ts9IdweFugS%{f@Z=#{!=EVa3+vnq-y~)RB4lXvcgl|tFbqI7A zd!P4{1T+>F2fKjpP-b!;rW%F^Bxkgk8rJSof(BXT8HCvh9HvgVUac(iS>2Z*2dZr(tzK0*W_nA4c_Z zWYJ4!&U@ZaTUpjOvZcN~=UdTNG`%1N?=Uho$NJGI-3b#{G*_|kq09?u{SMgj?K#Gc^LZZxj(W3!kr2tjZYc$>=T_ zdR1Li-e&WHbW#J9*zQ_}t3%uxI(q&`kbcx3!9Utj< z&ve{PU*BA?qq*%soZx;;zM&4=Z|6>~k!PNzu2IZAc01ro->KvRAlNY+%d)g=eM+6X z6Dp;uYglv*Y~MRB!G>w)qM7YybHztcHrkfua7rHjytCY)Lw2*=A@6psbJ!?c=kr*M zOs`Z`Q~Rx|ZTZ_OBwq8@%*#^YikZ@btuNrFHpG?T-cSpesIpF{@cj?T(I3@1m{X2Z zoi68QRczvZb)N&@E)i}oQ9<*#TvtmF3s+t+=gaU|qNY?DvT}&GEf~Jt)?d)6BNifE z3R9;qY$pI$4sN|Jp||3;)FBE^^F~a1z>&68M--DuA@7v%XXjHk2=zR@GvDFnZL%QE zd(5{Bs)+==$)tHS2aGNa)|zYq@+HL|dqagH)N)HXK#l$$O99^MHVttqP>Z1&q@^hY zvetx+GV9FpcJdTTJ=Hf;?)DmMdpKviF{5Qu&}H~*EZu^(b*B;|qHsbOs#DHeG&Uw#1-iyI#3M)J*|iPe-E<0pXXBZcrM+w_354W3KGkc!d_dA&xZ{`x;0C)muCo zN=HNK@rKeM_Q^2rWGkQcr2yMnl~`&D8=yT(0Y28T>{wwwKZB zg--)yJpO!!jNtOj7D(G#^772oDRVqmSHJwcT*@M*9Nk87ZLA~k?#zEBl_dammhZ;j zul(PEZ51Lj1tT^&qb}1$6$X5zT~8Q45DSr)DN&1o=m&10Y%ALsV%HikKIzZVN(fX} z{2Fh%3EXA@Q1qz$i&u|^ZG2W|s=2Sp;L36S8d9_OQbX!o+F@3+%EdarDz?{y3q{gSvFsJpK9G*4f8e}~{!@c ziL`%ZC*}_h{@TFo$FDoRPr)bM{rop~77OpgzxKDYM~bFu`&R4*-fQQa{}bK06_U|T z9|D76ykxyGu@AgxXs8&IC-nNDbGi1+iw39?#jK@xO)D_B`ra1<$e1d?1BE5?Df6=S zFL@eFg9xf&obt%9jK}&zp=PR=Iy!Rf&`314?fbv2FV>1pAJ(D2-ENX+^3{jNOV)Pi zOJ^z7VFVzKL|koOEISpr@qHs9zxT*OLb8xGCHNcRA{_VBXG^^tKC*5ieM{e7){_pw z@q&xC9vNy#hN_L{Z-kLnU9N(crJHiK^=eUwBiVIlh0_Qf~p+DSukO> zG+*CLR7gZ6T(&|pLXgrGFe@U`oiN-WWu(fS1NkUWaia1xpV4Q)3=7!r7%tfLrJK2c zI#zH?TWEx&07_E2`w=C7IDl2T*09rd>7?{snV9b=qm$?yBPfe_{)#5&N{`{)aBopc z?`yeT=Lan%F}yL&vl&G-a>CjFfPW`|!$%;DO2}s5Rg}s*nQAP}qRMja7L%6NBw=Ax z3`W@&_;H2Me{)yspJXwC`>%?6M)UTrEeq~a4M^~GH?QkKEJgX?ZS;%MfjjOi{JZ&! zXjzklf*;{c{tbx#oF@Rg9ADVdlFGco_62t@`zP#@8hyX{QcQ;6;M8edP-utzx=e-6 zJ8I6Dyj;JHZv4f)(u-@*)02dqFTXy__S)cgKYrb5Hy+u}k?kDW&XMgrfbDeifp0Dh zu5578%fsl#h9|PH&e2@W=9);Ges`O&?0P`}I?MCA-7yPrk5fx!-BRZ5)7wq>DW2ATzr z6X4y3mGNWkIi~>8sW{2KEefnw!%LHV=Tb3b8=1?*QKWE9u2 zVo+n6YfEBXBR#_opjF^iC*3JU z@OpYTX=*JxcOc|qbk7#657W+o&d^8dD5H6DD0B)of{3gfTLlW9i{egF9UH*0vJauj z(|D|02)vycvo(tanMD#$Z zpYSOOB+df0(>3`%2U5Z+9CI;2#6tm|ph=Q1@d_xAJL}B_QXpv>TwP$wb~Pgg>Nued z|5aHj(SAphd7>MU1D_FV-jGJL{cTq2b2`u9?xpD=~I*v2W-?lx{V{Z++zVAi0vX#mV`Exlj^G#$` zVG&OLhhB9{2AGAb$gD;`nDHH1pCOKvPb*m7Ou`l{(NE_*hm#!GvPFn#$oYGWm&e_} zyxQ|K8fH!W|D&GUU>+>=1Ab8WmH)j~W`1wJ)(HSrzB-RCwtKhUXt&lW%r>5*Xwp^| z?E)-Bm{kP4E=q7Zl>h533l3kB!Cp&h+@g;J#yG=v&4k__tk94P|Jc3BKG^$qFw$Xn z)LOwq8BeuHPP!gHmldi&Kk*>g^lJN3i5K|W@vXKG#MD%yC@e)4WHKw zU|HHXLaVCpW=2HgXELh~AgBkSp{aBu^TS%u0Ion$zsNfnFN@?7gRbl`E01M9RbM1k>$N#8XdZ0; z5Gg;R3xxw-E@|J)DCFqWG8)_oKBoz_So(E`v0Rqx4zgh0XzNS0b7}UyBnpJCFv{uS zsLR*>w5-m27%Zm&SwHXI*ajSmY9Fw*o;JI2>lp)golR4aR(}@JFYFFx)nfaLwO;Hs zF2c&q;;Y#sn0*GE3HJk*4R^=mzy9%-kTwg^e zAC`e}GjG=+z+C`-Sie0qlK&ZyGV@i`u`U3%;X=;OHSZdI*jlf?ys3 z)?E%qeYXlbYbxt1sHWx?6`tFBQh))q_`p@zIY!eoo2sI$%oF$XJX+S1lsj;27|;|B zenP{pqC#X}Ug@{%=3I|`?@9UUl-_+OQk4L8b~R2x^3-Jq$HT>-I-A>8K_h)uf}E z^i0$w;|4I)*e~^OG#TjS`}qL@)b~bH@E~L<@sCiQG0c%uZ|%W`z&`y3fK4xP8!01Oa0BP z1%vWiBAmH#^4hNg~Fe+dnG`3XIVIY$iAR!I|8JbEr zGN)rea~S&Bde-DoKRfDYNB!)mpFI=(43o0mkujc%0%KaFKxd z0K*fScTVIgC!e=qhq)=4g<j2&$(L6=taGN9<5+&5R3*qtBrVR)lecXR_s zla-BtQDw6n3>^rK!F$?}sSd(d{*zal83g1E#O9yrzSD@Rd>L&URoFUCB`VOd3TCXr z+Ej;-^maq@AZ@}DjMgJ?KLd0EY>rwoXduIi{4ShHpBzX7&+GN-rxc7PDFTh^&T+{E zprt#B%2k1C~@>Q(Z#gvdEQmJ=EHjwe$D-rXDmjENcAXQ%)_lR<(9F zX^SEhQo!z)vgpL>qrELSTBa34=})NCwdQ<12n&qHX`SF!oagsChNh70U_(YunyKfH z3WRad(qFdEmK}>_8AN@r5ZfIbo#BHT3L~=*WCtPMxch=7+Bwgc4_PQ=Vo-cKK)Iw_ zg6~XxZQ0%hAvF196#cXlq>ccou|UGtmR@cH`?~kB#TI!gsSMSx5=^J6Q#!8Q9UM7y zhuAqs_cRUE!1cchxDmS)kc%U)q(aBMJ(^gv`pQw#JldIk$`Wd>mK_j(YTOze-wVv> z{}N4PbuYLtC1J5tt17NA#n+y~13qO@8GZtDiY zg@Qy=`~kvY_F^z3mt5}H4SLZaDWYM+3-w`a43JF2saSQK zx$yW8C*(FT4*j@71GB$Vi)^?4AI4FJlW?w!bxcA*fgO!x9?#`M!Pg!@(ZZYNgy@~2 zz{r=|@nd9ED z9{`O3u5z?^j}~uZDo2ZVHv+#~b%sWbi-b#M0tsWhWZa|M%NF|y6QRl#Z0!Z;3Fq=$ zR|UMJ{H!ecN&i=*%ek0vp-Qj@1ZX72@y0n_@Se#EwUj)Vq6->!l`p$O7Ou@*;m?L$ zyaMTFI;qZvPO)wr>$348DntT<)N!g3%$k8KS)QHg)WL|q7K{l>pbLfZSn&=+hZjB1 zIEP~dE}ub3od!drbkNwRwVQijH!M_6#CUP1%}*EuxPwrr4Kp<_YX6EZ1drGRY%CcUQVUhX?rU24*Dx))^r>4HA6~Rz=92LEx&^-u2 zy}m%^h3#L{2lS)BUdU>m_llVvpTqK8{P3WbZE-KK88T?|FynCPs0hIUfTTsx1}J5X zrzvG5zlyjzjl>-oJIFGqp#s+g7UenciNyp{@cCzKbit473!%VdFMJ<3M-oz>b@?Sz zM(R%+rx~RdUU#A(hY*0SzX4#po^tAMsuqf?HAgdeGUM_;@I*CVpjQcx__F9euhRqz zjnI!8X*-5h7Wj~;@=sF)59^m!(C@*D&t_O#?nmiReNcAOQ8vaw2!SQM9oww-+4AmrjNso+l`d-+=@d7es<4GaL)vr{DW z@$2o3+zH@*YLMUzYcYF}I2)H#-su_bgHSrF9|pz(w+)CHit9q>LawRE5{>*SP_2+c~A10XTBGS`q&}k@WszPKXSHL+` zc)Te(mLC$Md(5cUK~Vn$*-J)XeNZ1mb|OI-5zjzSBGjM?FEixzi!hjbeY2UH@;o_NymWT zAUIv4?%6ERNGCoDyN<@Y3c!9x!f5eU)V(nm)N_F=*CfZ5b ztz6B_(?@{RlqRhN>#BbOwAMOvmwi`B<{;EV4*qoew(h&$AhDAlUFpX$vtR zW;!RvV^`XF=9Mn+iAI}K7;HgfpGH{s9wS=h9hKHz=4GiSesFl>`4J8HJ#eNBe#25B zQ=rYfu(cfh59VU`D$`g1ZOgC7m~m3RC)6@5pQJzfKME6dkK!MFAANLLgpS#~%kSS|q-3AX&odsD z4sq+F^U`*I(`4DX{EyyhZBX1E#KF%6C}2x8*uCw@^E++$akWU{Xw@}2gPnlBDikH@ znw&}XzRkkQm!o6k-}yH`)m&(Ti=ULPu4IahJ-u!qjjQHW?cdRb+9%r)CeZ`pzm4)= z*5umW5z*X+-v(|!er-2|4(L0&-*D`;*K2!^mRIT(u{GS2mUZP;9Q3@bV&%nHuAp@2 z?^xk7iMRkI5LQb!4oy_a)h2Y!iV=0dCb+mx$%hA>a*f3}FR&_dc1pFQ@{-d|xs|#> za$2WTMGdR52G|^HE0f~L0c(>LYvzQ0DE#ovAEt)^ADAgKY}QQFM_k7iSoRDV{+Y}d z+{t-HcVp7O4A!clzn`0SID(251J+8&5$G=QoX8cC6R13xiH+qDlk(MB;Kdk_A?Xo`ngvz3HQ`@mn7Y zTX2uet%(Ik-9;_rN%_jTOz;RS$2S)}zWKqP5j`?&w<~jCvwncn?Wb^qd;|kNw_;WQ z^^gJJjh?eZ|MggBhbEO?HBZLk)$vIIFoL+leeqt!DYM($5VOhsp3r@8*Y#9YaU*%{@cqZ4yUO8)73`ow-T zheE~@+1VlJrPf|5*K~zwsp2nyTD|n}90MV`Jd&fN%bm~^bLK7WpU{QmQ+*5V(EZr{ z#pb7>4n>;;`X}oi@80qw6#%%kZcjR!;oVG1fffH|^Cp-wOUf8n^O>Rtbm{If=r!7p zX0WzVo8rM7q*(c2^d<+;h0Z=75WKwk+_Z2Oe}A zmH?q67bZONNCGMu+?>zwsBfK*Y}+wiG@L~r^@uwOVjD3qoNlIFU%+5i&Ct^)%>T-m zAwgy@CL&9Kmk%}j3o~Aj*-MOgk;xfQkt~Y580aJ3@UXz~qc=G0kl#X=y=~)mrQ3ma zQ+BBqO=s96b&6(^^qM34TREoKqRWU1)@f2#27a30F2^KuNObsf9d(SU@_73&yt*k^ zcp9HjER4}woyA#kkgpDG*Op|;CDuuOOaOd7z-tpvZ@{&y|$G$nPMId;!}jb~f5KIXg+)Mr@5 z#-N2n^f2KL$%|6ta1^Udj<#Eg8-q#l>DxZ{ve{#U1pyiJ(d-}*Tp z9(uT=K^o<3r&YY^og4~}!)b+fX&RwfgI6~|i!gq!z@r4L)LK?~{DqGTtWjqUu}h9_ ziSF~_OEgXMd!qe3Rz!xbdJ-cIB#8wvLLm*7NAJ86O?4m5Bf}!?j(_I)ee|bV#b44@ z8)qozriPzL5;R*3is-zRF-7_^qtH}4BAA;VhmB$k!=kit7NftBNo^vXPwVJh=osvY z&V{yNTYYp}%5#kGqiY`5QCx^j?8@rx#s|)w%k}6Tvc_&qH^5eTcK*@*+(T^{7C4jB z6S<~|Hd@JGM^HtP7ZuONOH1`<85-E&&dy&=AaJq4h;5Wn=jT3n+rQ`&P}XicqRGb2 z+1prGJfB;L9%)UFrrq_UHx^-gKL}<+6vOH8XdiFFmYJl~;o%z=8fK-qjB+LsuSg2u;WGb+WEb;Skmv_ZD9Wcyn`&kC3EA=h#V`b3^# zWw$=YYHV*F9-Ysh-f@XEGE?!;_^;pobp7eC&!~Wtq}Z7%kMs02n$-%OLm8HB!h^C> zQg$?-!RrQn_!4s-DUvw}Y)tUqk(7SBrzQAYs6OD=a9aS_v*@pJLlzkp`}^yN`pF1d zfS0ub2UJzzcRZDdiu{*M{Btz}$j-RVqAq^=XW>8v0X~~Crm%`}A^A70?GQJKqU(ys z#5rRz0j~F>bs+x3g=U!Mq$0YJ1$sub3UC^|x9&Q7Y5kToW0HdYLeVxZ$j*Ky^`(6aV3!}V^^2D-d~h3IuEOyh(7tR8kl2;piXD8>3h3DhC5C#_Ek!umG0b#H;9X0ikGo zR^;{ax{oCMesl?Vk@<3uXc*&zklbgI_=c^tg7B9VFWC&M;-~J>BX3qlJBdrNuw4&A zfqOV*MQ%x16V}*ocbtHr6}8C3Zbah$|Q%>wD`@R=)|$l z?i=|uAj1olr}{{AiYGEbt5X` zCFAVCCOwzuk@}lYzyB|PxYWhog)TaU;+XD%3-IpD{hit2wG0VLu!kSm%-|kWICB%i zLpd|W*`RqsXQwCdj|t%MDw-5ZPAT=y6gTI@ey6l%cX@gjuDg#{ue}d!qx9#y0M%B? zGio=rRzKcUJ$Qf{i(femI#X21``|K~TIPi~>Qi;%Pm&54RdX**h<{#)Of5lgtJsPO z?iYgf**9L2&a)}{aUwH0RaNOsM@>Q;ETY}|Y{!`{%FZI74M|<;IbvE{Ucs8s13nlN z-_Ph86HzaIhU=LzD$W=sL(98M_s%US+_GSjeHDSh%M8!fD?V7Y)Dp9s$;YJa2R-%R zkmdK;eo5xqRqxA-8K`X9Ee{lQ#Sw+t??mAS&GLT4jjR9*1(~Ugw@=i-*3FQL(r7tf zm3W32wG`}YIHH1E480ONf?1&rYf2lVI@gaiG2d^ONtx`;((0e+F*=!D_^HPF6$gqM zXmoWUcH2SI#c-#9SFZ>^(5Wn{!$j^6JMPKAf7mcUZ z%8CgUM&KGPnr6`mPSqEQPSbpydId(o6(GBEvb(hVPfL6=iG8S&Ug)Lo=|BJEU*Ex) zhg~uqZ$>j9M;v!EFzztRq{%F?{!@48uaDd5`%d^Y0IQhrZ`OOpgYhC6-`XqP{LP@_ zCA)}h1q-`SmF7^SdcX>ziffN=yhwu0ymRxCP>ZUvWnDFv^O~>p*p$OA=tYvyb^*Q3 z=UzUOM``LDa8}FR$7TRdx(?h2eJMC{{9+8a%#%7cuHEruAYC3Q-=;m=KqV@s)$9E9 zo!{O@HJn=vWcc_0>31pD(K z7v}Gt&wo(%gY*7h{L$0$S`@#Yqx5L`y!Bb1LaGN)$LEa8<$5!(y-R&Z!Q2-8H2kyo zQ>lBZH${G~-4fMx|24YcFgWY~(|`7T+ErAz=wDC4b24ta9s7<^RU)2~%r;}VD?P^; zwmt=!{!d;hE<|G~l zWIqeHUEPJABlxKvJcM_p=NRun6|`cQ|LFPTBTF69v-_)T!maG>llVv9C;QfO-Okr3 zF)0B-F3cGIrM{y8Q>G5)b_{!==LkJ&Om^+tE2^TiwsG~v2aH5%>3QLgV=na^1(Cz} zYE?*|HLhRoIog}Am?vF&yV|?ba|~Ni>&hlZ+D+2(Q_oR9NeUFbC}4X7)Q$$5mwS$O z%jtxhGu-^4=ZK$6F{32~d(#ZoxYl!=s~jJ)%gBHAeD=L`8;kzke2z{0koAhT_ztvh z+Kl@3=sehm^8n3ZoR6`Fc~zV_+-d$kW6OX2TaTvy(e&T?^wIS1^VHGwKbronPaRGF zjc1Rh|Izd}PajSHqvq5mO^euB}0%$%!A4K(q$G+2;%AM2pEe8ig(>!8!+$5#_~y44d8Rc_~x zEhE=8@^h-l{DvpFK{va~h_9=+BUcY^Rj;|s+3@Q1!58D-I&-d})-2%sgsFej!-bhU zymzR1!#{(}nW`fXH0cjGR-jp{8CIe>8+Q6UXcX={3ONNnG3Zx(q9Fr*1NFHr2K|P& zG-SYU;M2ZX4*C_>deDI1!P0A>`G26nhMYHDewxzMEDXBk>$<+|ii&>EYE{lIX9RA#1insPPyqgu6Ft@72Hk=3fEXZ4oqJLG9TTV0AJmvL3n9-nUe z`DxU25p5aQWs#f5k+7&0!4%6XxEoI<5*A(MMfr-`QDSEae^{yqZ_1MEbY_zZB)DV% z!N4VKvGh;L4@~QfYtB+ZTWW6ExVzwUheW3&(LeR0v?c9FKP*m*7ndL3wWE&-D*FjS*QBJc1mOfIrWOv*e(Zu$Asbz~Zj5UpUh5k3SrMSna3YtZ98wxn0p zS|EhixxQ28^4^ejp zf|!QbF%>L1!!0@zHKj2ty*q=|ztJUJ`jGP|1(ng`wB9IVD?zW_^r}X#gx6{;6o&J$tZaodw7_m%ra*YHYrRhjz(go7FD%L5Pc(V@w}XCaD>gIxLuaY!J6NI5%(vousTxwMftxr-&$ODM0gJ zoiFOlmZUYUk1SP$g7JaYu;_6InTQIbZYY&&EoF9YV%=jAh@*s;72i<>X=-T% ztN@tQYKeM!y3z$AjZtM8haGI?xr}tPA8Maz{6(NkvO^m!q0Bm<&j6=Y@}}g-PDNY9hMw!ox((7CczG3`BLg8RR|`Q z6D8SUnLBoO2aVULU@_0EQO_0QKuJ=S-tXG8$Ob|hp>i_I3-VCTP+cfaBlytM==}Wr z6|zNJ&RE;fwAXDTI0`Eh3wT(qms2jQI)P#JNH}@lsp%L7eIu|6uq`#SD5WUS*5SIR zVB{3}vQVT&ZPlH$$sUX!|FJGqnRFJJ4cx`x0!;K7Rq2NXHikN^-3)J~HpJY^X17bK zMR5!TtJZb*$eZ7I?IQE3S3P|PV7d$}(k|Yw(AQl8biifVtHfS>z8(t7IA5*`bRF#x zOe{{PXW2s-iQ=7=RRJk8QTr{_)J9Sc+8V|*3RGA)x< zJa7^nGB(t5Ve^qb29iB}THTn>{~!41unUJG>0Ycnv(+tMJBK*Me;d`o(-Dd~%MfPY zKVRn4>K>MiI$FBcy<9H1icS!VQ*Fa;KJ|*vxZ)H92wra(`{Gu2sBVyF1ugdd=9}k;eLFu=~d>xLKdw2N%9MaK^1^ z8%yUe%W1O?9R$v({ zx(NHyyR(OWJ1l4o;wGP1NmH?OtRZxC%+Q`k9EhL~x$uO`fBN&Mchnm*V{Vs75F&-< zRmLI$sZuYNtTmvNp@xxo7`6~!K(JBE68(0zPqJ0yu(AOmp%xS@Y?1jXyW7_Djk_@% zkMjB^xMnZE{{`LSH?n&DS2X+N4=3a>FJ*NGW-p&xo$*iS^JF#f@M{gZe4DoKk{OUp5 z2=5ub(AqQdUMye#mH7sMIp)g?vGjnp zzwZm=IL*ttTh+fbw*>>&*E;@6&(gs&`rfC@Bb62C9rWoeEc2@N@94tvTaW&1U;iK5 zm(Bb%PT9nb2%s%ql2J|&e-;Z43AE-6Y$Hl!10{5fUDPT;<^F^9Y<$^fypn}x-Lv3a z2qOS>80==v6>wxaFNEoAJ2Vc-d)k#H=l1a&GM>}6)44;gcskP?V_UX?**k82=#T|J zZXT(F;=B#p>Pwa8T@T0NFj$o5)H$-J7s8$}#iYlMaut4u-B*}{Vzv>ISH$!+#vW?> zdaDn0I1@V6rm^KFgyck(Z<``z>RIL%|L)+FrjvU_93froaVPhO_7n_ zWSU-Irnt>~5D(=s06PpWZixBF!e0mrkGou)W2%_BKKER^QbFtzi-37h?^=r7TDk1xIMf?_Tx#`tfL`4FY02f0gF>mwGt{9EYI= zY2q_4YX6EZ_>M|Y8*Yc81a0xJjj)NxZq*iq+1KGYO1|9j`5}182Xj8SX=yoJ#z5mR z7>bLKIr5Yj!c*p8jZiX4yiXOlB?J%y$_iQ4MHXdLuwCa212F^(IbVtyHNrR`SZpK{ z&ke(tY=B!RSX9Pjy3e4|i!{yeBi$WZmh_% z0EOse*hC7G>^iNYvD0p@8o%k|LQFPwnRWMh@nsZ7a~PuB(V=-)`-gNPc@*Vsxa~!h z_YM6SK`*w7VPxrwCN;+XY5VlX8~X5-QT)VL&BSy+27XjFdl($05siFgDldeoctO&= zYB7zY?O8?Gn++WWn?UkWTo8w`ul(5a5RIsu3>SDye!Z3DXmE@8T+($%ssWyNM|FRZ}@&X@++3^ zR6}*Fe4~e>2)r;Y*+e>2OC5}xA7}D(`SI-`wx)GHl9}!`jk9ZVj@7Dl?Wjl`Bs z9k`oBS$&d3N!F2SuRM*mE`SO!q-KSJPkGq_h*)Ck-@uC2X+>2T58T(SZx=>!WpCySp3X?i3K_=bgCQf$fR3TTrScM`gCA z8r}k<%xvY{ysk?44$WAp5*|_qDxza3D{U}txGzJx?sgWO#F=eWG3Si~fcD!pJd0tF zLKVJWWo*Y+ZmN+AW5-@6Oux58iMdnw=1{0qb{+a4^U0KJ92gSVgL!)G>Y&D+5AAQrzggm((xRqkj1 z@zf~N&0Q+9naZesBs+Icwf8SLT4h!RL)8^401;xeptPhlC>q@|2 zecEYWNQ-$H-D9GUq2N#;;;UOQyrs)~ph%DW(3~&=f-aYV1%fCjIH(OB^V4AvnMYNY z-az|{sTm4TotUCLLDgjowM8do9b;P~Y>7&){2X`vy&9=*D_tJRoxIEc(O|gd1?{u% zT9DCwA?>jK*p7zzX{4hM!17@_xQ8z#>*)s-aXc11BB%CkTM==~liAH?%9N4YW0@Mgl?2VhTrgP+DefgNs{USC5 z`o(4=iEZgW!%{52IJ)P)QUC;YFg3pG%&Qof)8A-87x?#!B4dsIk&gS)u_cQZQvTq6 z3#yztQiXpY`r*V5?~1lLWXQ5H$VS*(mOV zZ;_*sqyNay`tmdU-@VZ%axdrBGI21rvAF38|2oZQl)Z1`uduuqovYBX-6%#z`*wa% zx8G4PyTHOS#sdmhNDZ%`|W_Xy*>Fq?)$#`zC(ij?H(i;ogeF! zjD!|vDop|SbY(@FFUwvO5gXu-G~AblA1=Gry&S_5@WIHgF===sFVum~`3A$Mh4M72 zCd1jWR$c3KX~x?L{Wjbg#{MaxDm$YFzp^X`n*Vi%HGXNtAYX7VRJCRUK4JF1@@PS3 zURL>XIT+pC@MuA14nxaACTBoBvMBOmpig+i!$v?tZ*bTlzlAP)o89q|UU|jqeB_CO z5VeWe%Tub$hKHd$PpRt#&nf3|i=5q=8r;s;N6rl1Azl0fh~Z@nrojxf$3BWce7XFh zmIKZC28IeU=P4c~3^V7qFjSB^--{v}QvT6sL1sRv=r~w;(RMWX1gX((92t}JC^Gtp zj7?^}Kf$pU^*+4pK!xIrPbmi4YdHDn4UIVDj@$YLoG&)eHOf;vn!WAc5idGOqXwHhIl? z^p>QHKncO}NX_OI^PNg9U}*R$I+5qI^JoSCw^$^}E38i|>@*A#`i0^S%nmV0=^G>$ zkS4a6<$5w&5F@<$+0yWvWhyLIAhJ}P&kC`mCU23EKB35QBOrT=92)G^3R(EF=Ri7~?l zT4jS)FLitr4v&jOvQvYRZ!Lz&yU?!hzPLRF6UY5(>1p6D`H?G5h8z|@J+@73#PCqi zBFHm?auSugsUp`dQFfd!7Ij8K2)kCAv`|Y3p8!K^kR~>I}ifvn!N*M~= z%q5Ipd{<)f1X>!n$3wvgOH2-?uMZ-~&nQXEbHgHf>+}cW$BDd?X}&Zm$O0rxo^nTa9jaVyABb3#%oN6eZ%utjTrxASUX>@*m{tB6i zzTcXMv|ViVYznm%3wRjpK_CTm(Q;OC;)$Ks>Qr4oy54V13SDAxY^mHYhCD z3~vQS<~qEr>0vFZQz#_0wzo$n_C}}vYd&>9Z}C~7iYX-`ghYwADD;10(OOVY*b^eD zK^R{TgEJ1b<$G7?B?!{3u7uTeKaS2OcEPy{bG2jQlZnmvK; z%GB+hh}uE~K(VwkS`OoTH$_gUMFAE%Q;p0Ro!Rq2|ImY947@~i#DHe2aal9xRbJXm zN!*tl;p@boY1I;EZ%%|lftiq1D45Z1AbREh`aEIvJ}@-43s8C!QHd5{-`k%8C)Vm} z&9-CXiY-ad2uflc#1cSVs241*OwUE&Bt-$rBUWo>VEu#Di6mAe%Yc={r|K&n{I-Dm z*8CEjV)dI1?fK+*hTc@G+C?Xz;k-JJF48p8AHv+&u274|5Z6Oj&<+JM=z~+W)5E%< zgN@aT#*S-5o}SyfKkAN~D64Qz=sN)_^CWE}#b4}CJpddQFY{_TTa}eglhyGAju~oy zqM}fvB`{POP4a|mvS-m;iUiLEluQ$MXGN?;-_5rKQ^_{EFleMhcVW1AP}2ojYHe%Z z{T4J@w{oBT7L2$Kt2eeaSg>#eZ?NFg&tv*2_{8+`+bJ|Pd^2|Y#Csrrm{DWxEAeAH zfaa%h3J{Q!sIeQioEf5PV>WyQj8Xf^_dZj1lF-Hq8Ehq)w%(h1|9KQyr@g|cy8KQh zQa2aR>cs>*?^c4{d7_HGA7g{GLE5HDZ2|Q%UyqzNLp}jhLh43YSe z!MHCShr#SEErr@=qejAc%5^FVWBk+fU@@jrkT=<$a6AlUGm(cAeH&28!y;lviN?Cw zR$3X^@)$tuZTEYe`p5&^myW{&jL)qPBN!*T_t&{(w|qgNwpNRFdBBvHr6)?ivrQBG zLN&L2TD4h|7l{q(UppNQf0U%pWu5`dW~R>*=R;uBSDP$4-cbMAqJWsfO{ASh7^C3W zjtqri-anFLR5!-26j9dnc~%r;=&1@NW#p^?rcm&BmVX}-8xxzu0VVBgla8iRqRCKp zS*Oe%CbS84XkwvDQlk;3k9H~3yEH|9Ddc8>2bmnl9STyPXY`R8HW7JXVBf1uE$T&+ zhuM=lj1XojG-)=Q%s=uE=+5Dl*Y=?#mz-d;kf5GnL@|CTy^j~eCX0GOJ2r;z6mQD- zY>?Et=GiJ4)mTk&`NGU&6XRnBZ=ZSB!XCBbZtYmqRUQ{|J>hDvid#rkpFsY!>RaEx=@Yp}}lv~dfk$K_Zqmb4X4cjF^`}*bflD>r5G8ohi}zwYnw={7=EB@%adzcTNBOEcX@fFUdwBSURz;3 zmq)L#?En#Yk&QU-qpAf9-ZPVmPAFqdWsU5b%r$?pH|hid1lq;RysYpR8!dR0YVJ$N zk!twAeR$ru=JB0AW47GGgp!Uy)@?8(aw_Vy`h^Rsb@PW&;JAqNG)|*Au*fsaVCD)} zR7wGURG9t4;ir3qYX5$fhbDv?jC9s`X_Eg#VJ5#~0iwOv??E2*QB?ED&4+9=E_u-8 zXGVTw5cGNUD;ie@MGm^un}87^*gCA*jen}+qTf>FG|=o4-%lr=6KVM6I5t_^ zLU@ze+e-Y(rNwMg>Z}kGg8Zl3+mCIL=9I#WH|P*^c3`kNa7}e7mbuDw7F;kS;f;HP z@Jf}-RIF%A6BFRKaAGgc44_Jvk>4Fl>syp;PvssYdVDBu$*^69I~0Jp5%vb)zYrUM z?fBnsKKxAo?#BmS3?neRr_Tzgp+i7SdX$ejVoj^^+AfL5rF9aN520NpKk;u&WngdS z0H?H=q1)%(1^Lky=(YtmJ4WgSxY#T}5GGdY+irQ>ME^+t?n}p^|JH7kCgWxgosv;y zcO(wL>$bmRPL&-D?h(%Z&v6xvG}Gef@e;t28;E&Q)=q7qi#qCM;idKCc$R&h=9RmNTLQ5M|hv0QYzdf}J3;RNrSVIwAq&!(>Zy*icYsZszS9v-jr!|(k zm>~k~=~Uh)hd#arEd(g;6Mxy~e(4%}h*T)0jh^E*sEZUq z%!jC`#PAz~4{ad9o=mPVhy8*+e!fM0>f;BHuz_iZTbS($qie#afek7b@+*}sF~N|( zSIHkvBhA!L$(sKC9bBV(|LKi|EEpGBPnQlU^)AV|Sv4fs7~f5o_u;s1%0Bao_W5^# zb#z{LJFll3;ZPxiK&{cyh7_XIvGy|Ds)(s<+n?n}qv0mq|=-n`*>6!8@BR3W?CnONv9Ogzt(d5WC# zBS2}!_nq#zn@5c(YQqONz9{5ve)9m!WfT&-ww8-dJZdvwY?e|*#m7xFBaxtv)56GcJJ6)A$iY6ztNET*?BiiYRv#CvWx)>XsMTj<8nnjx7)v`ZtOv`w@b`KId)SFZ z4T;bfPrDXp4gWItT@tG_7r~MQ4k25p-8{zmu{rqNyPVC8??R!n;Hn&ZR80_*>nnDg zTAmrPwrGngd968g4Rf+T-zW^GLy$|a zy^JYGI;pcHMH}9H43#4cW?Aq-bKa{*t~D$^=!ZtKE6kiYCXq$1Rh+pSu}E!1gVDFy z=%9iQxC&NMSMx^51n^K1?l^s~>1v2&mtwV5*qA*IMlakhGkdCF`2!e#Qek-~%CuxJ zP$*mrKc=G(z+uBdT)+ZFgaG^`j9gR)wb^b}n}FF`Zv;|2yVP_+bQg&q80gQR-t|Fw zG%~hr>(R}iGHOkX$f*);Z_|-5K7n#2c}n5nItFq-1T5kteZaD^=aYU2WluRUeEAo# zivHEsa{5;gB-%`&e^>AGgw>sYjV1#B7lismB?*|F^!r;3DuI9Uw*NV=F48o=r~5&2 zO;Z))Kr;+d`d3WCvEuBlP$}J3XUihTQg36WqwZP9z2f4c|4N=`siEm~K$ zAj5{t7$a$Y5&NQu-vEE4efOo~(Eh_k>bkiGqsU?M=5>KVwtg~C$OZY^k`Gk`-QNp2(0Tfasu+o0)KEHm6r__txtP`WvG&z;tN zu&L8%L(ZHodrRbKz^(TV_P0Ca_mM2nKTK*3_3dSnb-m@9)ilFds9!dnH=jmIH+e;M z&02=hf1@?UbhhZfDK7MFH&r<<+uXt~MFqK;{}<$$_ARY%7V-n2Dlz-$~0DxSH^T*)yId??PunL>oW=?=Yvcn1PTUL^WbP&ibfCGL;eyN^2Dlt{ z`re2^z5oyX2b#Kx9~g4pba?=+wJn}>%cpjIrmwWRIMSoe`Z%3+Q%~HR%G!wg8eV6` z(651Z8V)t&F55b19j7+XwDo8~W^Uu^2AaDaFVO65B;a84w<8Amf{7Sv<}gx_8QVz9 zf#z+;3o`pm$A%6xc{5y)xvvMa4)hJ{!v^^l6NNj_H<;mq%pJpq2bwny6=cqvG0p>h z!REk0zNZnMJ?g#I`^^ zoqC7%%5L2m+T8WNmoB_J1?I>Sycx&y3r_7u+IUa<_tS{xA~FNnJk$t7o+$jul#VDV z5QoC6h-#vw5ce6Ulv6SHB3k5y))6@ix;&+(O_}bC;8KFIP`QfhR1{neG;HM`PB@?|PxBdk0wvA9 zdRWL7ucH?^)V`&UOEK(HbVK!=SkG0y5DEKs}@QdKP7_3-#|CBkh!&=I5P6 z34}5!^AvgH=TFyM`vCAZwenR0531=Z(mi@L^>EJ;MYk4mDhvF^gvqWELwO413dX`I zChe%1$s;jj{{slLGLK>qYm&R$$0EzI3rcF5SKvK2KYjT8?h2ESXK6lxM^;gt zDS(&g)BP%dvay|98i^#qlw7Qxb3a${-1`Siv<7ZpC`9GX+lVF~%Y7>RaRSr6=icY9 zqGde+@i3Q(o)=a*fN4`!G+NeFlbkF3xUaDwok!OdO!ng4JGh3F!vXS`3PzY#Hb+#y zyR32ye}$Dx)RYFPbkt<9#3i>BJBZXpQfp$pc25mVv)qZtCy~d|t!64rxZLxFd;FBA zT;J=_gdJ*SkP6fuQ}R$J?Y{~WB2YT8EK;+1#ayRS3s55Ar|5*ro2}sg7K4k18=dJ`X*;az6$fIjhQy0juhTpN!nuzC^ zOrYJylw9iSngiFaQOcC8r_E%ht+G&yQK>53=W0-*?y1Yvy@TBXOrbcN778>~*ibY> z3#?n0Kmfr;t?FjEx-Df8KBBniWxe5th6M{*D9>h7_c)KHPS=l4jE!=RDsWzczN58&JQ2uaMMZh6A+wN`NJJ%I zqFVDb(ZCxVVUZ$Kz^@Q4(ee5|NB_iN|#flO>VK z{+DGVL7ntZnbf*zf{r4?e=bDvMdH1$d?9AdK^9`RssQkz8kts&)k;Gx36MuGPK5W} z!qN@A7PujB=;#CiGR_=gKno!57M=SZ*|x4eMOxsULI7E)y_i=x);pYL{> zy*%MzU^P}qucDEoaCtMB2YLkMG@_(LrEZE4>arE_W`9tG(UL(Z7#~;&1R0c{1?>;~ z1oXfJEF@;weyuh&zn1zSZg-O>4J#LBC5&EtS7KdQv@%dhVfl%p*0inNIL<#u=K505 z&Ew zOT=MpS~MPJ*o8DY0W%h882wLR(q-i-Xiaqj!|IW6@_Hy^T5D!O-w131j6cmNN+}Ao zO=cX%;p!{+3Wv6ugL#ntSQn~HR9E|y77m<9RDdseZw*X37hp8cU>Qj@GNv;u)(mfj zEz7>b4Ub%ldKC&s1wx>rxThYxcO2#RS{o_on&d9l-(d&r@2JRghHi^Y-aAi4(?Dra znT#((t6b+%^mAUx-$oxQ^v4+kLpxePFHT|G&0#jd6j-f=_d$FlX$Qgr{t!l>-Oo4N znyC0oi8P5U0B=xefRzBO%(S~qQtKPO9tyiSU#<#VxjmwH5!qR=F-r3*WXh`4hBXWb zzDD&GPa~LW%k$`RbBK}zg%$;1P%tm?A_q!SUf#%Wt*lWs!!^Am+=48an471}=a=Ss zu%bd2_MEf8;~FJc_;Sg3pJ%jqGA1jnUJ;b&ku`ttp=k*pOrsmeGj0)P9jH>e$J(##1y`ZIDDFe+sPZM4y z=@e}>ZE!{14`LkQO_xWiKC(SR!ER$Sbo;!!7tuQSzU@eb_J#kkeZ9<2{^Z1KQ zt{&h_8+d17H{1FVpsTw?+g#csK#-aKX&#EZ(y=xpUH zZ-K2=aERz});O~pX9_|?o@y6J zV=#)rz?tqEj;19h94>^v*d{fqbmVAHgPWzo=-^-fq7U@&MDVXzav`IFb%i0oX2PjI~St0Ntr+ZGs!mQ2pg^qvqK0KTZ;hm(Xw{9QyUsqJPBrN$%DOZ#`^BB zzhh5#YOjsL#M&^`M?Njy6n#Coe7Nx#lAhhV`epqk<5<3JII+yxfa$Hi93Ca2ECUcV zRWJu{7^qkA`H6N+Y2K9>Zyp|~VAiLka~tD$Vt*pg-druqSElo!Q6-tkiH7scis06p zNPS(@X{FHXVWoB9`LIZ>UFj%dL1D?He1}@jJH3FtR!%Rv4TT#V^So#PXi zH#d#sthM|i^|iG_zN5bOV)Qlqjva=kRXpgvf%!v2ASN#4^l+qOqRM3|Ry(-q_?j6- z(xAJJ=VDI9C=$u`+A@$?-y2bTGi=rPg)%pm!t|~DP9<7$WzA6Oy4;A)rVW5s%n*)s zy5&pcn&1qzTy&aBLu_a?SN?B3gyXOad6C%0%r?12gS@eZ6!=I^+D$Qero93tq6%1_ zNEPq{3`}A5Tm4xkHa_*#g_wHG(ETc_c#Or_lbG_?_$aDgny4}~--jEIAg+)X+dxLz zmL9ud2R7Rbu&!h5sWwRJe!(w5EyK&E3#Mz*y)92G>5Zfw*CqegLunM1xD6L#n0?fm zaiuo%ZOh<2B*p+@PfS<43Xc5trSV(sP3hHC;-^pDF$21vX7Z~ZTl*wU#RL6}#@*5|&mcNcjrH*4sYmY~jXQos z!R&g}#yO%Mde_LCd&eKwCSW;If{*-;zVSQlFtsU0Y+*(x={VuC+>_1qS`J+HAYJ*gk+e)3ZPVPEvO0B#CBoEa0VF5N z#jk7DL=GV@qUO}+F0kUFPOHYxCJ~w1h^Ca&ukZE`5^d%IdL3*;8FJr3o*BOq)m!mC z7DQH>V*(9+9>$YRMGTU&T(8mMCgA|kN5A|t1_haLDlE~aFsB#&YI#)V-q`}&SYm(b z(ywzf0-I{1Gh3*lk8DiFOUuQ1xRN|gP%)DWXe6m)CmlHk%Po5_yz-ve|$Z@XE%$K$6J9jknKneIR|LL)h<{&MOM5<^i&oYyER{@1;ipz5>>?Ky_W4FZ$1( zPk#oZ+Ow~FK84Jv;iK;P+?BDd)@`lk^DGC5;`T3KtaLxhmzO4Ao<&rBqbvvgU{5}k z{(VEM)~@GHPWfnY(8Liw1k3gHvPj2C`a>T8CF8LH2qJP7sm&oIaR;W ziyqy}G(GcH)*m6sB|Chu@C#eE`gMiG+EzpyG8V?=t!tGzVamaPSVam^6LFE3rOTj+ ze#-CVoz$#V?|XND%6ffS)&;(ZtIlXgp?Q;bNX=?cRe=ViNr82voHpj|O1R6fT;eAf z%OEFeMUPIv0OCO#(;G_F=C3# zi&=8AXSeVGuyCcRZrytlYGn=~I2!vLE*7j*nuz4xs}&5LXB~@7(2WsntlhLI7YhLF z4%m-lPqFD)X$uRvO{Pm1Pt{jlHv$MMbxzjimxNm6L-e$AbvCW50Q*3;qh#s1TS?t$ zD4=URX$<(rG>V&x@{25NUxH7vtW%c-y7`NFW&8JZVSPn)NGGyne$2P%!)1I1HwszM zC*`YCQ~jHGx~L>?H{V(|hU&v?(hb0W{Mza#c=jLPJ*981I}x|E>BVLA9{6NZ6$fTJ z7tUKmDl+QLnl8dAiwwuRJnk=B$JS7$b5$%wH3tikXN9Hhyu!l=fEt=9eY80Ayp31# zmB1>P>{nbp*SV8)>n7U46X_s|PBP#jKSbmM6zLBb>8kQX#lfuE{_QICox`K%vEOEc z360u`sDR-DT0c185HfQZ^Mm2q7~y6wb)g%!OH0HF}V#iQ6ZT*u1ZY5E&NH z>ugGWGVvDyuV881Wcz4=sO&CJ@A$wmJd0z6R4@^4#qz?xYX_-@KoD+L)hgnND=tp2 z+?}C-(@T{XJ7D|8-E$!1r@=FHh6hmNLPwWJayItytgW#D^Q!jm=)yF(pRFD6AKP&< zKaCUkazD@eNU5=$TA%!KVw;R~4AN~wq1tWRg*Hf#@!4PN7Zm$*b2F<@cE^@Y+n_nJ7)?LiYy;9e_J~ z7;8qAB@J%=ZZ;CvecjsXE<{$NE_U(p8kVds z=fQwKdk{$<6EmUitgD1uvZ7-Q`5jT zyx==v3vhoP%v&? z^g`U-JS^xprVlKM5`<{Ai7W~ZZK#{B94n4!!&|uvDz>qv1vq%{`E3V{{w6rO$e= zajJvns1}%YM(P}X&zy~NS7eWiGV?l1RoMu+FI+!z&0R2Q;|yI+iV|j0jGm+#vG@IeVr< z+Xni8Pi+yzLB0k4bA~4<1AWLZ>*38oK7vpi@-sfkvr)f^* zO;zO=g=2jz$O52O+GwWDU!F!HSiFRT0JKue8Lqm@ttkO8OHoZB^Wt^RRcJ(9MJa|e zl$>t5gvaHss*G*R$l!U(NBd@Y#$Vk%m*=#L21&{5RV#Yqg!9@x)fj)L5HLUQwq10o z=SEFsUF(g^K1>{7qF8z)IEL2U23P<5skbT<877^Z1>!IaJfavQtEwqZiuF=hq40?d zNosNrfRb^dGrZ#lzX&cKHt5NP!a}p7K3I(9GV#(}i;dtwS(G{T+qj>bj_;cri(Bl; z5lbL8L4z3rtJz(~HwbJVT4F8jwNb-f`qd+HoFPBQ1+z&;nR0NNAjmH`mm;m^QH-S} zbPf9=N^0u5;gY2qD}f}8F@xbK828by1s-Gea{P_Gqp3C&knLP7ODg*Ab^X{t>YnEv z3M&2n$r|AX-tPz%Q7CYTI#p>(C{3*E z77N#St2qikrpqI_8T;4~ElX@(*ZwVC%N5SD7(}5T=D3EtUG!2r#*LS`Zi-2KCBm(N z*QJmY=^Hd-SF6)DJhAK5g}<57!->8PLb>WyADb!o&1w{d9_B!w@05W}t`b@~E0cItsd8~RSc$KyS3LSS?Nu)NA*5dp@_kmwaC>W6z(jVaKOgz` z9zL3ZQA^YOzSJf5Er4h^-h*U>1q}s|NP5M92Hay*j$)d}TH}LFbuc@PnB@N8y7Y9Y zLu1$z(6TP;qF;&Uao|BkFM>DSQ7A(Lngyu-VA&!%p#Vvg=xUraG9C?bG1J*QEHxh? z_JQS}Ye9iF1LJZ#Z`O>3SF1(6i0&m;Dy7Hlx<3UUyHf#6qofS=HK zF)^BGd_YxH5)q=J-*;yhM1=y9Ehg$wf^-;mwy>SHmGkx8`kUsmn5)MqFyIoyT5{~H zXt6xfm*U6net_2VsNhphVBukSyR$%`|Gzaeu77V)nUzVD*(}qgexJ;l-YLOc4Xi3_ z;1HB*3}+j90mN3)Us_O+m&UA#a{P7F{$vrwgcyZrQUq|*Blfxdbln2_K;a##!kF$9 zg>XCp)CbHy*d7>lfZc2ii=SejU#MIJbZI&y+M5t1;W@zBx?D*wiB%XbJ_kWh(qT4& z!#elZTRNVHE|KbjAeV|SI5l%d_1&6D{Hh!q5j9U|5m(Y|<5(zbZb@55*Mkz`Azi8y z%z+l-Q8##_q-fb{2Y8yCH4*dS_V&JFW@_72da{MrD&5!ys?3Jhph8*a{#X1-mL9m$ z<@cJ>96S9iDHSDs{glcR-joD1V`Q&UD(Xk1$<1G(#EYK|Hok*2lB`3+F$*R z)Ss)*nffI-Ry#j5^y>ytO928D02BZK00;mxQ)E@yN@kyGV*~&mpeO(j0000000001 x03ZMW000000A^@uaxHUdZf9&|E_icfP)h{{000000RRC2JpcdzmtzC~0031N=5hc4 diff --git a/src/Hl7.Fhir.Validation.STU3/Hl7.Fhir.Validation.STU3.csproj b/src/Hl7.Fhir.Validation.STU3/Hl7.Fhir.Validation.STU3.csproj new file mode 100644 index 00000000..95971a4b --- /dev/null +++ b/src/Hl7.Fhir.Validation.STU3/Hl7.Fhir.Validation.STU3.csproj @@ -0,0 +1,23 @@ + + + + + + + + Firely validation library for FHIR STU3 + Next-gen Firely validator for STU3, which validates FHIR data against profiles. + HL7;FHIR;Validation;Utility; + Firely.Fhir.Validation.STU3 + + + + + + + + + + \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.STU3/Properties/AssemblyInfo.cs b/src/Hl7.Fhir.Validation.STU3/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..e3aead2d --- /dev/null +++ b/src/Hl7.Fhir.Validation.STU3/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +/* + * Copyright (C) 2022, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + +using System; +using System.Runtime.CompilerServices; + +// The following GUID is for the ID of the typelib if this project is exposed to COM + +[assembly: CLSCompliant(true)] diff --git a/src/Hl7.Fhir.Validation.Shared/ConstraintBestPracticesSeverity.cs b/src/Hl7.Fhir.Validation.Shared/ConstraintBestPracticesSeverity.cs deleted file mode 100644 index 94d87346..00000000 --- a/src/Hl7.Fhir.Validation.Shared/ConstraintBestPracticesSeverity.cs +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2016, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -namespace Hl7.Fhir.Validation -{ - ///

- /// Represents whether to treat violations of the invariants marked as "best practice" as errors or as warnings. - /// - public enum ConstraintBestPracticesSeverity - { - /// - /// A failed "Best Practice" invariant should result in a warning. - /// - Warning, - - /// - /// A failed "Best Practice" invariant should result in an error. - /// - Error - } -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems b/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems index c14f17e6..b00d7c1e 100644 --- a/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems +++ b/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems @@ -9,12 +9,7 @@ Hl7.Fhir.Validation.Shared - - - - - + - \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs b/src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs new file mode 100644 index 00000000..3c92b0d4 --- /dev/null +++ b/src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ +using Hl7.Fhir.Model; +using System.Threading.Tasks; + +namespace Firely.Fhir.Validation +{ + /// + /// A service that can resolve external references to other resources. + /// + public interface IExternalReferenceResolver + { + /// + /// Resolves the reference to a resource. + /// + /// The resource, or null if the reference could not be resolved. + Task ResolveAsync(string reference); + } + +} diff --git a/src/Hl7.Fhir.Validation.Shared/IsExternalInit.cs b/src/Hl7.Fhir.Validation.Shared/IsExternalInit.cs deleted file mode 100644 index 4d4c0816..00000000 --- a/src/Hl7.Fhir.Validation.Shared/IsExternalInit.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 - -// Needed for pre-net5 targets to support "init"-type setters (C# 9.0). -// See also https://github.com/dotnet/roslyn/issues/45510 - -using System.ComponentModel; - -namespace System.Runtime.CompilerServices -{ - /// - /// Reserved to be used by the compiler for tracking metadata. - /// This class should not be used by developers in source code. - /// - [EditorBrowsable(EditorBrowsableState.Never)] - internal static class IsExternalInit - { - } -} - -#endif \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/PocoValidationExtensions.cs b/src/Hl7.Fhir.Validation.Shared/PocoValidationExtensions.cs deleted file mode 100644 index d8a86af7..00000000 --- a/src/Hl7.Fhir.Validation.Shared/PocoValidationExtensions.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2016, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Model; -using System.Collections.Generic; - -namespace Hl7.Fhir.Validation -{ - /// - /// Add support for validating against Base subclasses (instead of ITypedElement) to the Validator - /// - public static class PocoValidationExtensions - { - /// - /// Validate an instance, use the instance's type to pick the relevant profile to validate against. - /// - public static OperationOutcome Validate(this Validator me, Base instance) - { - return me.Validate(instance.ToTypedElement()); - } - - /// - /// Validate an instance against a given set of profiles. - /// - public static OperationOutcome Validate(this Validator me, Base instance, params string[] definitionUri) - { - return me.Validate(instance.ToTypedElement(), definitionUri); - } - - /// - /// Validate an instance against a given set of profiles. - /// - public static OperationOutcome Validate(this Validator me, Base instance, StructureDefinition structureDefinition) - { - return me.Validate(instance.ToTypedElement(), structureDefinition); - } - - /// - /// Validate an instance against a given set of profiles. - /// - public static OperationOutcome Validate(this Validator me, Base instance, IEnumerable structureDefinitions) - { - return me.Validate(instance.ToTypedElement(), structureDefinitions); - } - } -} diff --git a/src/Hl7.Fhir.Validation.Shared/SchemaCollection.cs b/src/Hl7.Fhir.Validation.Shared/SchemaCollection.cs deleted file mode 100644 index a52fb39d..00000000 --- a/src/Hl7.Fhir.Validation.Shared/SchemaCollection.cs +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2016, 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-net-sdk/blob/master/LICENSE - */ - -using Hl7.Fhir.Utility; -using System; -using System.Xml.Schema; - -#nullable enable - -namespace Hl7.Fhir.Specification.Source -{ - /// - /// A class that retrieves, compiles and caches an necessary to validate FHIR XML. - /// - public class SchemaCollection - { - private const string FHIRSINGLE_XSD_RESOURCENAME = "fhir-single.zip"; - - /// - /// Constructs a SchemaCollection which retrieves XML schemas from the given . - /// - [Obsolete("The FHIR-supplied XSD schemas in SchemaCollection are now shipped as embedded resources, so you can no longer use a specific IArtifactSource to read from.")] - public SchemaCollection(IArtifactSource _) : this() - { - // Nothing - } - - /// - /// Constructs a SchemaCollection which retrieves XML schemas from the default source. - /// - /// The default source is the source returned by . - public SchemaCollection() - { - _validationSchemaSet = new(SerializationUtil.BASEFHIRSCHEMAS, typeof(SchemaCollection).Assembly, FHIRSINGLE_XSD_RESOURCENAME); - } - - private readonly IncludedXsdSchemaSet _validationSchemaSet; - - /// - /// Return an XmlSchemaSet that contains the minimal set necessary to validate FHIR XML. - /// - public XmlSchemaSet MinimalSchemas => _validationSchemaSet.CompiledSchemas; - - /// - /// Returns the schemas necessary to use XML validation on FHIR resources. - /// - public static XmlSchemaSet ValidationSchemaSet => Default.MinimalSchemas; - - /// - /// A singleton instance of the - /// -#pragma warning disable IDE1006 // Naming Styles - public static readonly SchemaCollection Default = new(); -#pragma warning restore IDE1006 // Naming Styles - } -} - -#nullable restore \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs b/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs deleted file mode 100644 index 7371f645..00000000 --- a/src/Hl7.Fhir.Validation.Shared/ValidationSettings.cs +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2016, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Specification; -using Hl7.Fhir.Specification.Snapshot; -using Hl7.Fhir.Specification.Source; -using Hl7.Fhir.Specification.Terminology; -using Hl7.Fhir.Utility; -using System; -using System.Xml.Schema; - -namespace Hl7.Fhir.Validation -{ - /// - /// Configuration settings for the class. - /// - public class ValidationSettings - { - /// - /// The used to map instance types encountered in - /// to canonicals when a profile for these types needs to be retrieved. - /// - public StructureDefinitionSummaryProvider.TypeNameMapper? ResourceMapping { get; set; } - - /// - /// The resolver to use when references to other resources are encountered in the instance. - /// - /// Most of the time this resolver is used when resolving canonicals to profiles or valuesets, - /// but it may also be used to resolve references encountered in instance data. - /// See for more information. - public IResourceResolver? ResourceResolver { get; set; } - - /// - /// The terminology service to use to validate coded instance data. - /// - public ITerminologyService? TerminologyService { get; set; } - - /// - /// An instance of the FhirPath compiler to use when evaluating constraints - /// (provide this if you have custom functions included in the symbol table) - /// - /// If this property is not set, the validator will - /// use a FhirPathCompiler with all FHIR extensions installed. - public Hl7.FhirPath.FhirPathCompiler? FhirPathCompiler { get; set; } - - /// - /// The validator needs StructureDefinitions to have a snapshot form to function. If a StructureDefinition - /// without a snapshot is encountered, should the validator generate the snapshot from the differential - /// present in the StructureDefinition? Default is 'false'. - /// - public bool GenerateSnapshot { get; set; } // = false - - /// - /// Configuration settings for the snapshot generator - /// (if the property is enabled). - /// Never returns null. Assigning null reverts back to default settings. - /// - public SnapshotGeneratorSettings GenerateSnapshotSettings - { - get => _generateSnapshotSettings; - set => _generateSnapshotSettings = value?.Clone() ?? SnapshotGeneratorSettings.CreateDefault(); - } - - private SnapshotGeneratorSettings _generateSnapshotSettings = SnapshotGeneratorSettings.CreateDefault(); - - /// - /// Include informational tracing information in the validation output. Useful for debugging purposes. Default is 'false'. - /// - public bool Trace { get; set; } // = false; - - /// - /// StructureDefinition may contain FhirPath constraints to enfore invariants in the data that cannot - /// be expresses using StructureDefinition alone. This validation can be turned off for performance or - /// debugging purposes. Default is 'false'. - /// - public bool SkipConstraintValidation { get; set; } // = false; - - /// - /// A list of constraints to be ignored by the validator. Default values are dom-6, rng-2, "bdl-8" and "cnl-0" - /// - public string[] ConstraintsToIgnore { get; set; } = new string[] { "dom-6", "rng-2", "bdl-8", "cnl-0" }; - - /// - /// If a reference is encountered that references to a resource outside of the current instance being validated, - /// this setting controls whether the validator will call out to the ResourceResolver to try to resolve the - /// external reference. - /// - /// References that refer to resources inside the current instance (i.e. - /// contained resources, Bundle entries) will always be followed and validated. See - /// for more information. - public bool ResolveExternalReferences { get; set; } // = false; - - /// - /// If set to true (and the XDocument specific overloads of validate() are used), the validator will run - /// .NET XSD validation prior to running profile validation - /// - public bool EnableXsdValidation { get; set; } // = false; - - /// - /// Choose whether the validator will treat the violations of the invariants marked as best practices as errors or as warnings. - /// - public ConstraintBestPracticesSeverity ConstraintBestPracticesSeverity { get; set; } - - /// - /// Determine where to retrieve the XSD schemas from when when Xsd validation is enabled and run. - /// - /// If this is not set, the default FHIR XSDs from the specification will be used. - public XmlSchemaSet? XsdSchemaCollection { get; set; } - - /// - /// Default constructor. Creates a new instance with default property values. - /// - public ValidationSettings() { } - - /// Clone constructor. Generates a new instance initialized from the state of the specified instance. - /// The specified argument is null. - public ValidationSettings(ValidationSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - other.CopyTo(this); - } - - /// Copy all configuration settings to another instance. - /// Another instance. - /// The specified argument is null. - public void CopyTo(ValidationSettings other) - { - if (other == null) throw Error.ArgumentNull(nameof(other)); - - other.ConstraintBestPracticesSeverity = ConstraintBestPracticesSeverity; - other.GenerateSnapshot = GenerateSnapshot; - other.GenerateSnapshotSettings = GenerateSnapshotSettings.Clone(); - other.EnableXsdValidation = EnableXsdValidation; - other.ResolveExternalReferences = ResolveExternalReferences; - other.ResourceResolver = ResourceResolver; - other.SkipConstraintValidation = SkipConstraintValidation; - other.TerminologyService = TerminologyService; - other.Trace = Trace; - other.FhirPathCompiler = FhirPathCompiler; - other.ResourceMapping = ResourceMapping; - other.XsdSchemaCollection = XsdSchemaCollection; - other.ConstraintsToIgnore = ConstraintsToIgnore; - } - - /// Creates a new object that is a copy of the current instance. - public ValidationSettings Clone() => new(this); - - /// Creates a new instance with default property values. - public static ValidationSettings CreateDefault() => new(); - - } -} \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/Validator.cs b/src/Hl7.Fhir.Validation.Shared/Validator.cs index 556e1ac9..92fd31c4 100644 --- a/src/Hl7.Fhir.Validation.Shared/Validator.cs +++ b/src/Hl7.Fhir.Validation.Shared/Validator.cs @@ -1,445 +1,86 @@ /* - * Copyright (c) 2016, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE + * Copyright (C) 2023, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. */ - +using Firely.Fhir.Validation.Compilation; using Hl7.Fhir.ElementModel; -using Hl7.Fhir.FhirPath; using Hl7.Fhir.Model; -using Hl7.Fhir.Serialization; -using Hl7.Fhir.Specification.Navigation; -using Hl7.Fhir.Specification.Snapshot; using Hl7.Fhir.Specification.Source; -using Hl7.Fhir.Support; +using Hl7.Fhir.Specification.Terminology; using Hl7.FhirPath; -using Hl7.FhirPath.Expressions; -using System; -using System.Collections.Generic; -using System.IO; +using System.Threading.Tasks; -namespace Hl7.Fhir.Validation +namespace Firely.Fhir.Validation { /// - /// This is a shim, simulating the interface of the original SDK validator (pre 5.0 SDK) on top of the - /// new Firely Validator SDK. + /// A FHIR profile validator, which is able to validate a FHIR resource against a given FHIR profile. /// public class Validator { /// - /// The current settings used by the validator when calling one of the Validate methods. - /// - public ValidationSettings Settings { get; private set; } - - /// - /// When the validator encounters a StructureDefinition without a snapshot, and is true, - /// this event will be invoked to obtain a snapshot for that StructureDefinition. - /// - /// If this event is not set, a snapshot will be created using a with - /// the settings in or the default settings if those - /// are not set. This snapshot generator will use the to fetch - /// additional StructureDefinitions encountered while snapshotting. - public event EventHandler? OnSnapshotNeeded; - - /// - /// When is true, this event will be called to - /// resolve references encountered in the instance data (including those encountered in calls to the - /// FhirPath resolve() function). - /// - /// If this event is not set, or resolution resulted in an Exception, resolution is tried using - /// the , by calling . - public event EventHandler? OnExternalResolutionNeeded; - - private SnapshotGenerator? _snapshotGenerator; - - internal SnapshotGenerator? SnapshotGenerator - { - get - { - if (_snapshotGenerator == null) - { - var resolver = Settings.ResourceResolver; - if (resolver != null) - { - SnapshotGeneratorSettings settings = Settings.GenerateSnapshotSettings ?? SnapshotGeneratorSettings.CreateDefault(); - _snapshotGenerator = new SnapshotGenerator(resolver, settings); - } - - } - return _snapshotGenerator; - } - } - - private FhirPathCompiler? _fpCompiler; - - /// - /// The FhirPath compiler to use for handling the FhirPath-based invariants in the - /// StructureDefinition. - /// - /// This property will return if set, - /// otherwise it will construct a singleton FhirPathCompiler with all FHIR extensions installed. - internal FhirPathCompiler FpCompiler - { - get - { - // Use a provided compiler - if (Settings.FhirPathCompiler != null) - return Settings.FhirPathCompiler; - - if (_fpCompiler == null) - { - var symbolTable = new SymbolTable(); - symbolTable.AddStandardFP(); - symbolTable.AddFhirExtensions(); - - _fpCompiler = new FhirPathCompiler(symbolTable); - - Settings.FhirPathCompiler = _fpCompiler; - } - - return _fpCompiler; - } - } - - /// - /// Construct a new validator with the given settings. - /// - /// - public Validator(ValidationSettings settings) - { - Settings = settings.Clone(); - } - - /// - /// Construct a new validator with default settings. - /// - public Validator() : this(ValidationSettings.CreateDefault()) - { - } - - /// - /// Validate an instance, use the instance's to pick the relevant profile to validate against. - /// - public OperationOutcome Validate(ITypedElement instance) - { - throw new NotImplementedException(); - //var state = new ValidationState(); - //var result = ValidateInternal(instance, declaredTypeProfile: null, statedCanonicals: null, statedProfiles: null, state: state) - // .RemoveDuplicateMessages(); - //result.SetAnnotation(state); - //return result; - } - - /// - /// Validate an instance against a given set of profiles. - /// - public OperationOutcome Validate(ITypedElement instance, params string[] definitionUris) => - Validate(instance, (IEnumerable)definitionUris); - - /// - /// Validate an instance against a given set of profiles. - /// - public OperationOutcome Validate(ITypedElement instance, IEnumerable definitionUris) - { - throw new NotImplementedException(); - //var state = new ValidationState(); - //var result = ValidateInternal(instance, declaredTypeProfile: null, statedCanonicals: definitionUris, statedProfiles: null, state: state) - // .RemoveDuplicateMessages(); - //result.SetAnnotation(state); - //return result; - } - - /// - /// Validate an instance against a given set of profiles. - /// - public OperationOutcome Validate(ITypedElement instance, params StructureDefinition[] structureDefinitions) => - Validate(instance, (IEnumerable)structureDefinitions); - - /// - /// Validate an instance against a given set of profiles. - /// - public OperationOutcome Validate(ITypedElement instance, IEnumerable structureDefinitions) - { - throw new NotImplementedException(); - //var state = new ValidationState(); - //var result = ValidateInternal( - // instance, - // declaredTypeProfile: null, - // statedCanonicals: null, - // statedProfiles: structureDefinitions, - // state: state).RemoveDuplicateMessages(); - //result.SetAnnotation(state); - //return result; - } - - // This is the one and only main entry point for all external validation calls (i.e. invoked by the user of the API) - internal OperationOutcome ValidateInternal( - ITypedElement instance, - string declaredTypeProfile, - IEnumerable statedCanonicals, - IEnumerable statedProfiles, - object state) // used to be ValidationState - { - throw new NotImplementedException(); - //var resolutionContext = instance switch - //{ - // { InstanceType: "Extension" } when instance.Name == "modifierExtension" => ProfileAssertion.ResolutionContext.InModifierExtension, - // { InstanceType: "Extension" } => ProfileAssertion.ResolutionContext.InExtension, - // _ => ProfileAssertion.ResolutionContext.Elsewhere - //}; - - //var processor = new ProfilePreprocessor(profileResolutionNeeded, snapshotGenerationNeeded, instance, declaredTypeProfile, statedProfiles, statedCanonicals, Settings.ResourceMapping, resolutionContext); - //var outcome = processor.Process(); - - //// Note: only start validating if the profiles are complete and consistent - //if (outcome.Success) - // outcome.Add(ValidateInternal(instance, processor.Result, state)); - - //return outcome; - } - - private StructureDefinition? profileResolutionNeeded(string canonical) => - //TODO: Need to make everything async in 2.x validator -#pragma warning disable CS0618 // Type or member is obsolete - Settings.ResourceResolver?.FindStructureDefinition(canonical); -#pragma warning restore CS0618 // Type or member is obsolete - - - internal OperationOutcome ValidateInternal(ITypedElement instance, ElementDefinitionNavigator definition, object state) - => ValidateInternal(instance, new[] { definition }, state).RemoveDuplicateMessages(); - - - // This is the one and only main internal entry point for all validations, which in its term - // will call step 1 in the validator, the function validateElement - internal OperationOutcome ValidateInternal(ITypedElement elementNav, IEnumerable definitions, object state) - { - throw new NotImplementedException(); - //var outcome = new OperationOutcome(); - //var instance = elementNav.ToScopedNode(); - - //try - //{ - // var allDefinitions = definitions.ToList(); - - // if (allDefinitions.Count() == 1) - // outcome.Add(startValidation(allDefinitions.Single(), instance, state)); - // else - // { - // var validators = allDefinitions.Select(nav => createValidator(nav)); - // outcome.Add(this.Combine("the list of profiles", BatchValidationMode.All, instance, validators)); - // } - //} - //catch (Exception e) - //{ - // outcome.AddIssue($"Internal logic failure: {e.Message}", Issue.PROCESSING_CATASTROPHIC_FAILURE, instance); - //} - - //return outcome; - - //Func createValidator(ElementDefinitionNavigator nav) => - // () => startValidation(nav, instance, state); - - } - - private OperationOutcome startValidation(ElementDefinitionNavigator definition, ScopedNode instance, object state) - { - throw new NotImplementedException(); - //// If we are starting a validation of a referenceable element (resource, contained resource, nested resource), - //// make sure we keep track of it, so we can detect loops and avoid validating the same resource multiple times. - //if (instance.AtResource && definition.AtRoot) - //{ - // state.Global.ResourcesValidated.Increase(); - // var location = state.Instance.ExternalUrl is string extu - // ? extu + "#" + instance.Location - // : instance.Location; - - // return state.Instance.InternalValidations.Start(location, definition.StructureDefinition.Url, - // () => validateElement(definition, instance, state)); - //} - //else - //{ - // return validateElement(definition, instance, state); - //} - } - - private OperationOutcome validateElement(ElementDefinitionNavigator definition, ScopedNode instance, object state) - { - var outcome = new OperationOutcome(); - - try - { - throw new NotImplementedException(); - } - catch (StructuralTypeException te) - { - outcome.AddIssue(te.Message, Issue.CONTENT_ELEMENT_HAS_INCORRECT_TYPE, instance.Location); - return outcome; - } - } - - - internal OperationOutcome.IssueComponent? Trace(OperationOutcome outcome, string message, Issue issue, string location) => - Settings.Trace || issue.Severity != OperationOutcome.IssueSeverity.Information - ? outcome.AddIssue(message, issue, location) - : null; - - internal OperationOutcome.IssueComponent? Trace(OperationOutcome outcome, string message, Issue issue, ITypedElement location) => - Settings.Trace || issue.Severity != OperationOutcome.IssueSeverity.Information - ? Trace(outcome, message, issue, location.Location) - : null; - - internal ITypedElement? ExternalReferenceResolutionNeeded(string reference, OperationOutcome outcome, string path) - { - if (!Settings.ResolveExternalReferences) return null; - - try - { - // Default implementation: call event - if (OnExternalResolutionNeeded != null) - { - var args = new OnResolveResourceReferenceEventArgs(reference); - OnExternalResolutionNeeded(this, args); - return args.Result; - } - } - catch (Exception e) - { - Trace(outcome, "External resolution of '{reference}' caused an error: " + e.Message, Issue.UNAVAILABLE_REFERENCED_RESOURCE, path); - } - - // Else, try to resolve using the given ResourceResolver - // (note: this also happens when the external resolution above threw an exception) - if (Settings.ResourceResolver != null) - { - try - { - var poco = Settings.ResourceResolver.ResolveByUri(reference); - if (poco != null) - return poco.ToTypedElement(); - } - catch (Exception e) - { - Trace(outcome, $"Resolution of reference '{reference}' using the Resolver SDK failed: " + e.Message, Issue.UNAVAILABLE_REFERENCED_RESOURCE, path); - } - } - - return null; // Sorry, nothing worked - } - - - // Note: this modifies an SD that is passed to us and will alter a possibly cached - // object shared amongst other threads. This is generally useful and saves considerable - // time when the same snapshot is needed again, but may result in side-effects - private OperationOutcome snapshotGenerationNeeded(StructureDefinition definition) + /// Constructs a validator, passing in the services the validator depends on. + ///
+ /// An that is used when the validator must validate a code against a + /// terminology service. + /// An that is used to resolve the StructureDefinitions to validate against. + /// A that resolves an url to an external instance, represented as a Model POCO. + /// Optionally, a FhirPath compiler to use when evaluating constraints + /// (provide this if you have custom functions included in the symbol table). + public Validator( + ICodeValidationTerminologyService terminologyService, + IAsyncResourceResolver resourceResolver, + IExternalReferenceResolver referenceResolver, + FhirPathCompiler? fhirPathCompiler = null) { - if (!Settings.GenerateSnapshot) return new OperationOutcome(); + var elementSchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(resourceResolver); - // Default implementation: call event - if (OnSnapshotNeeded is not null && Settings.ResourceResolver is not null) + _validationContext = new ValidationContext(elementSchemaResolver, terminologyService) { - var eventData = new OnSnapshotNeededEventArgs(definition, Settings.ResourceResolver); - OnSnapshotNeeded(this, eventData); - return eventData.Result ?? new OperationOutcome(); - } - - // Else, expand, depending on our configuration - var generator = SnapshotGenerator; - if (generator != null) + FhirPathCompiler = fhirPathCompiler, + ExternalReferenceResolver = resolve, + ConstraintBestPractices = ConstraintBestPractices, + MetaProfileSelector = MetaProfileSelector, + ExtensionUrlFollower = ExtensionUrlFollower + }; + + async Task resolve(string reference) { - //TODO: make everything async in 2.x validator -#pragma warning disable CS0618 // Type or member is obsolete - generator.Update(definition); -#pragma warning restore CS0618 // Type or member is obsolete - -#if DEBUG - // TODO: Validation Async Support - string xml = (new FhirXmlSerializer()).SerializeToString(definition); - string name = definition.Id ?? definition.Name.Replace(" ", "").Replace("/", ""); - var dir = Path.Combine(Path.GetTempPath(), "validation"); - - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); - - File.WriteAllText(Path.Combine(dir, name) + ".StructureDefinition.xml", xml); -#endif - - - return generator.Outcome ?? new OperationOutcome(); + var r = await referenceResolver.ResolveAsync(reference); + return r?.ToTypedElement(ModelInfo.ModelInspector); } - - return new OperationOutcome(); } - } - - /// - /// Arguments supplied to the event when invoked. - /// - public class OnSnapshotNeededEventArgs : EventArgs - { - /// - /// Construct new events args. - /// - public OnSnapshotNeededEventArgs(StructureDefinition definition, IResourceResolver resolver) - { - Definition = definition; - Resolver = resolver; - } + private readonly ValidationContext _validationContext; /// - /// The which needs to be snapshotted. The event should - /// generate the snapshot in the property of this - /// instance. + /// Determines how to deal with failures of FhirPath constraints marked as "best practice". Default is . /// - public StructureDefinition Definition { get; } + /// See , and + /// https://www.hl7.org/fhir/best-practices.html for more information. + public ValidateBestPracticesSeverity ConstraintBestPractices = ValidateBestPracticesSeverity.Warning; /// - /// The resolver to use when generating the snapshot. + /// The to invoke when a is encountered. If not set, the list of profiles + /// is used as encountered in the instance. /// - public IResourceResolver Resolver { get; } + public MetaProfileSelector? MetaProfileSelector = null; /// - /// An that represents success or issues encountered while - /// creating the snapshot. + /// The to invoke when an is encountered in an instance. + /// If not set, then a validation of an Extension will warn if the extension cannot be resolved, or will return an error when + /// the extension cannot be resolved and is a modififier extension. /// - public OperationOutcome? Result { get; set; } - } + public ExtensionUrlFollower? ExtensionUrlFollower = null; - /// - /// Arguments supplied to the event when invoked. - /// - public class OnResolveResourceReferenceEventArgs : EventArgs - { /// - /// Construct new events args. + /// Validates an instance against a profile. /// - public OnResolveResourceReferenceEventArgs(string reference) + /// A report containing the issues found during validation. + public ResultReport Validate(Resource instance, string profile) { - Reference = reference; + var validator = new SchemaReferenceValidator(profile); + return validator.Validate(instance.ToTypedElement(ModelInfo.ModelInspector).AsScopedNode(), _validationContext); } - - /// - /// The reference that should be resolved to an instance. - /// - public string Reference { get; } - - /// - /// The resolved instance. - /// - public ITypedElement? Result { get; set; } - } - - - // Marked obsolete at 2022-11-22, EK - [Obsolete("This enumeration is not used (publicly) by the SDK and will be removed from the public surface in the future.")] -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member - public enum BatchValidationMode - { - All, - Any, - Once } -#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } diff --git a/src/Hl7.Fhir.Validation.Shared/XmlValidationExtensions.cs b/src/Hl7.Fhir.Validation.Shared/XmlValidationExtensions.cs deleted file mode 100644 index 63032872..00000000 --- a/src/Hl7.Fhir.Validation.Shared/XmlValidationExtensions.cs +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2016, 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://raw.githubusercontent.com/FirelyTeam/firely-net-sdk/master/LICENSE - */ - -#nullable enable - -using Hl7.Fhir.Model; -using Hl7.Fhir.Serialization; -using Hl7.Fhir.Specification.Source; -using Hl7.Fhir.Support; -using System; -using System.Collections.Generic; -using System.Xml; -using System.Xml.Linq; -using System.Xml.Schema; - -namespace Hl7.Fhir.Validation -{ - /// - /// Add support for validating data straight from an . - /// - public static class XmlValidationExtensions - { - private static XmlSchemaSet getSchemaSetFromSettings(Validator v) => v.Settings.XsdSchemaCollection ?? - SchemaCollection.ValidationSchemaSet; - - /// - /// Validate an instance serialized in xml, use the instance's type to pick the relevant profile and - /// FHIR XSD schema to validate against. - /// - public static OperationOutcome Validate(this Validator me, XmlReader instance) - { - var result = me.ValidatedParseXml(instance, getSchemaSetFromSettings(me), out var poco); - - if (poco != null) - result.Add(me.Validate(poco)); - - return result; - } - - /// - /// Validate an instance serialized in xml against a given set of profiles and the - /// relevant XSD profile from the FHIR specification. - /// - public static OperationOutcome Validate(this Validator me, XmlReader instance, params string[] definitionUris) - { - var result = me.ValidatedParseXml(instance, getSchemaSetFromSettings(me), out var poco); - - if (poco != null) - result.Add(me.Validate(poco, definitionUris)); - - return result; - } - - /// - /// Validate an instance serialized in xml against a given set of profiles and the - /// relevant XSD profile from the FHIR specification. - /// - public static OperationOutcome Validate(this Validator me, XmlReader instance, StructureDefinition structureDefinition) - { - var result = me.ValidatedParseXml(instance, getSchemaSetFromSettings(me), out var poco); - - if (poco != null) - result.Add(me.Validate(poco, structureDefinition)); - - return result; - } - - /// - /// Validate an instance serialized in xml against a given set of profiles and the - /// relevant XSD profile from the FHIR specification. - /// - public static OperationOutcome Validate(this Validator me, XmlReader instance, IEnumerable structureDefinitions) - { - var result = me.ValidatedParseXml(instance, getSchemaSetFromSettings(me), out var poco); - - if (poco != null) - result.Add(me.Validate(poco, structureDefinitions)); - - return result; - } - - - internal static OperationOutcome ValidatedParseXml(this Validator me, XmlReader instance, XmlSchemaSet xsdSchemas, out Resource? poco) - { - var result = new OperationOutcome(); - - try - { - - if (me.Settings.EnableXsdValidation) - { - var doc = XDocument.Load(instance, LoadOptions.SetLineInfo); - result.Add(validateXml(doc, xsdSchemas)); - instance = doc.CreateReader(); - } - - poco = (Resource)(new FhirXmlParser()).Parse(instance, typeof(Resource)); - } - catch (Exception e) - { - result.AddIssue($"Parsing of Xml into a FHIR Poco failed: {e.Message}", Issue.XSD_CONTENT_POCO_PARSING_FAILED, default(string)); - poco = null; - } - - return result; - - static OperationOutcome validateXml(XDocument instance, XmlSchemaSet xsdSchemas) - { - var result = new OperationOutcome(); - - instance.Validate(xsdSchemas, (o, args) => { result.AddIssue(toIssueComponent(args)); }); - - return result; - } - - } - - - private static OperationOutcome.IssueComponent toIssueComponent(ValidationEventArgs args) - { - string message = $".NET Xsd validation {args.Severity}: {args.Message}"; - string pos = $"line: {args.Exception.LineNumber}, pos: {args.Exception.LinePosition}"; - - return args.Severity == XmlSeverityType.Error - ? Issue.XSD_VALIDATION_ERROR.ToIssueComponent(message, pos) - : Issue.XSD_VALIDATION_WARNING.ToIssueComponent(message, pos); - } - } -} - -#nullable restore \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs index a11cbec7..b681738c 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs @@ -129,12 +129,12 @@ public void InjectMetaProfileTest() }; var context = ValidationContext.BuildMinimalContext(_fixture.ValidateCodeService, _fixture.SchemaResolver); - context.SelectMetaProfiles = metaCallback; + context.MetaProfileSelector = metaCallback; var result = schema!.Validate(bundle.ToTypedElement(), context); result.Result.Should().Be(ValidationResult.Failure); - context.SelectMetaProfiles = null; + context.MetaProfileSelector = null; result = schema!.Validate(bundle.ToTypedElement(), context); result.Result.Should().Be(ValidationResult.Success); @@ -156,26 +156,26 @@ public void ValidateExtensionTest() var context = ValidationContext.BuildMinimalContext(_fixture.ValidateCodeService, _fixture.SchemaResolver); // Do not resolve the extension - context.FollowExtensionUrl = buildCallback(ExtensionUrlHandling.DontResolve); + context.ExtensionUrlFollower = buildCallback(ExtensionUrlHandling.DontResolve); var result = schema!.Validate(patient.ToTypedElement(), context); result.Warnings.Should().BeEmpty(); result.Errors.Should().OnlyContain(e => e.IssueNumber == Issue.UNAVAILABLE_REFERENCED_PROFILE.Code); result.Result.Should().Be(ValidationResult.Failure, because: "extension2 could not be found."); // Warn if missing - context.FollowExtensionUrl = buildCallback(ExtensionUrlHandling.WarnIfMissing); + context.ExtensionUrlFollower = buildCallback(ExtensionUrlHandling.WarnIfMissing); result = schema!.Validate(patient.ToTypedElement(), context); result.Warnings.Should().OnlyContain(w => w.IssueNumber == Issue.UNAVAILABLE_REFERENCED_PROFILE_WARNING.Code); result.Errors.Should().OnlyContain(e => e.IssueNumber == Issue.UNAVAILABLE_REFERENCED_PROFILE.Code); // Error if missing - context.FollowExtensionUrl = buildCallback(ExtensionUrlHandling.ErrorIfMissing); + context.ExtensionUrlFollower = buildCallback(ExtensionUrlHandling.ErrorIfMissing); result = schema!.Validate(patient.ToTypedElement(), context); result.Errors.Should().Contain(w => w.IssueNumber == Issue.UNAVAILABLE_REFERENCED_PROFILE.Code); result.Warnings.Should().BeEmpty(); // Default - context.FollowExtensionUrl = null; + context.ExtensionUrlFollower = null; result = schema!.Validate(patient.ToTypedElement(), context); result.Errors.Should().BeEmpty(); result.Warnings.Should().OnlyContain(e => e.IssueNumber == Issue.UNAVAILABLE_REFERENCED_PROFILE_WARNING.Code); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/BindingValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/BindingValidatorTests.cs index 7fc4438e..7f6440a8 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/BindingValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/BindingValidatorTests.cs @@ -240,7 +240,7 @@ public void ValidateCodeWithUnreachableTerminologyServerAndUserIntervention() { setup(new FhirOperationException("Dummy", System.Net.HttpStatusCode.NotFound)); var validationContext = ValidationContext.BuildMinimalContext(_validateCodeService.Object); - validationContext.OnValidateCodeServiceFailure = userIntervention; + validationContext.ValidateCodeServiceFailureHandler = userIntervention; var input = createCoding("http://terminology.hl7.org/CodeSystem/data-absent-reason", "UNKNOWN"); var result = _bindingAssertion.Validate(input, validationContext); @@ -263,7 +263,7 @@ public void ValidateConceptWithUnreachableTerminologyServerAndUserIntervention() { setup(new FhirOperationException("Dummy message", System.Net.HttpStatusCode.NotFound)); var validationContext = ValidationContext.BuildMinimalContext(_validateCodeService.Object); - validationContext.OnValidateCodeServiceFailure = userIntervention; + validationContext.ValidateCodeServiceFailureHandler = userIntervention; var codings = new[] { createCoding("http://terminology.hl7.org/CodeSystem/data-absent-reason", "masked") , diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs index bfe87193..fdc9e0f0 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ResourceSchemaTests.cs @@ -13,7 +13,7 @@ public class ResourceSchemaTests public void FollowMetaProfileTest() { var context = ValidationContext.BuildMinimalContext(); - context.SelectMetaProfiles = callback; + context.MetaProfileSelector = callback; var instance = new { @@ -28,12 +28,12 @@ public void FollowMetaProfileTest() var result = ResourceSchema.GetMetaProfileSchemas(instance, context, new ValidationState()); result.Should().BeEquivalentTo(new Canonical[] { "userprofile2", "profile3", "profile4", "userprofile5" }); - context.SelectMetaProfiles = declineAll; + context.MetaProfileSelector = declineAll; result = ResourceSchema.GetMetaProfileSchemas(instance, context, new ValidationState()); result.Should().BeEmpty(); // remove the callback: - context.SelectMetaProfiles = null; + context.MetaProfileSelector = null; result = ResourceSchema.GetMetaProfileSchemas(instance, context, new ValidationState()); result.Should().BeEquivalentTo(new Canonical[] { "profile1", "profile2", "profile3", "profile4" }); From dddc99d9e7c3f8cb597d09d450fde795f80d00aa Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 22 Nov 2023 21:53:06 +0100 Subject: [PATCH 09/27] Update SDK to 5.4.1-20231122.7 --- firely-validator-api-tests.props | 2 +- firely-validator-api.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/firely-validator-api-tests.props b/firely-validator-api-tests.props index a84cbccc..b5fa3ee1 100644 --- a/firely-validator-api-tests.props +++ b/firely-validator-api-tests.props @@ -10,7 +10,7 @@ - 5.4.1-20231114.4 + 5.4.1-20231122.7 5.1.0 diff --git a/firely-validator-api.props b/firely-validator-api.props index 85439109..c3566b31 100644 --- a/firely-validator-api.props +++ b/firely-validator-api.props @@ -19,7 +19,7 @@ - 5.4.1-20231114.4 + 5.4.1-20231122.7 From d6c3cc85f87645071aad32ab9d67e102a2d7c756 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 22 Nov 2023 22:32:43 +0100 Subject: [PATCH 10/27] Solve nullable warnings --- .../SchemaBuilders/TypeReferenceBuilder.cs | 19 +++++++++++++------ .../Impl/ChildrenValidator.cs | 11 ++++++----- .../Impl/DefinitionsAssertion.cs | 2 +- .../Impl/ElementSchema.cs | 2 +- .../Impl/FhirTxt1Validator.cs | 2 +- .../Impl/FixedValidator.cs | 2 +- .../Impl/ReferencedInstanceValidator.cs | 2 +- .../Impl/RegExValidator.cs | 2 +- .../Impl/SliceValidator.cs | 2 +- .../TypeReferenceBuilderTests.cs | 8 ++++---- 10 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs index 7bece9c5..074be4d9 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs @@ -68,11 +68,15 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv #else var hasProfileDetails = def.Type.Any(tr => tr.Profile.Any() || tr.TargetProfile.Any()); #endif - if ((!nav.HasChildren || hasProfileDetails) && def.Type.Any()) - yield return ConvertTypeReferences(def.Type); + if ((!nav.HasChildren || hasProfileDetails) && def.Type.Count > 0) + { + var typeAssertions = ConvertTypeReferences(def.Type); + if (typeAssertions is not null) + yield return typeAssertions; + } } - public IAssertion ConvertTypeReferences(IEnumerable typeRefs) + public IAssertion? ConvertTypeReferences(IEnumerable typeRefs) { if (!CommonTypeRefComponent.CanConvert(typeRefs)) throw new IncorrectElementDefinitionException("Encountered an element with typerefs that cannot be converted to a common structure."); @@ -101,12 +105,13 @@ public IAssertion ConvertTypeReferences(IEnumerable null, // If there are no explicit profiles, use the schema associated with the declared type code in the typeref. { Count: 1 } single => new SchemaReferenceValidator(single.Single()), @@ -124,7 +129,9 @@ internal IAssertion ConvertTypeReference(CommonTypeRefComponent typeRef) var targetProfileAssertions = ConvertTargetProfilesToSchemaReferences(targetProfiles); var validateReferenceAssertion = buildvalidateInstance(typeRef.AggregationElement, typeRef.Versioning, targetProfileAssertions); - return new AllValidator(profileAssertions, validateReferenceAssertion); + return profileAssertions is not null + ? new AllValidator(profileAssertions, validateReferenceAssertion) + : validateReferenceAssertion; } else if (!(code is "Reference" or "canonical" or "CodeableReference") && typeRef.TargetProfile.Any()) { diff --git a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs index ad823960..3eabcbf4 100644 --- a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs @@ -9,6 +9,7 @@ using Hl7.Fhir.Utility; using Newtonsoft.Json.Linq; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.Serialization; @@ -89,7 +90,7 @@ public ResultReport Validate(IScopedNode input, ValidationContext vc, Validation elementsToMatch.Insert(0, new ValueElementNode(input)); var matchResult = ChildNameMatcher.Match(ChildList, elementsToMatch); - if (matchResult.UnmatchedInstanceElements.Any() && !AllowAdditionalChildren) + if (matchResult.UnmatchedInstanceElements?.Count > 0 && !AllowAdditionalChildren) { var elementList = string.Join(",", matchResult.UnmatchedInstanceElements.Select(e => $"'{e.Name}'")); evidence.Add(new IssueAssertion(Issue.CONTENT_ELEMENT_HAS_UNKNOWN_CHILDREN, $"Encountered unknown child elements {elementList}") @@ -97,18 +98,18 @@ public ResultReport Validate(IScopedNode input, ValidationContext vc, Validation } evidence.AddRange( - matchResult.Matches.Select(m => + matchResult.Matches?.Select(m => m.Assertion.ValidateMany( m.InstanceElements ?? NOELEMENTS, vc, state .UpdateLocation(vs => vs.ToChild(m.ChildName)) .UpdateInstanceLocation(ip => ip.ToChild(m.ChildName, choiceElement(m))) - ))); + )) ?? Enumerable.Empty()); return ResultReport.FromEvidence(evidence); - static string? choiceElement(Match m) => m.ChildName.EndsWith("[x]") ? m.InstanceElements?.FirstOrDefault().InstanceType : null; + static string? choiceElement(Match m) => m.ChildName.EndsWith("[x]") ? m.InstanceElements?.FirstOrDefault()?.InstanceType : null; } private static readonly List NOELEMENTS = new(); @@ -118,7 +119,7 @@ public ResultReport Validate(IScopedNode input, ValidationContext vc, Validation public bool ContainsKey(string key) => _childList.ContainsKey(key); /// - public bool TryGetValue(string key, out IAssertion value) => _childList.TryGetValue(key, out value); + public bool TryGetValue(string key, [MaybeNullWhen(false)] out IAssertion value) => _childList.TryGetValue(key, out value); /// public IEnumerator> GetEnumerator() => ((IEnumerable>)_childList).GetEnumerator(); diff --git a/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs b/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs index 411121c9..9f583b5d 100644 --- a/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs @@ -49,7 +49,7 @@ public DefinitionsAssertion(IEnumerable schemas) /// Find the first subschema with the given anchor. ///
/// An if found, otherwise null. - public ElementSchema FindFirstByAnchor(string anchor) => + public ElementSchema? FindFirstByAnchor(string anchor) => Schemas.FirstOrDefault(s => s.Id == "#" + anchor); /// diff --git a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs index 0535bc3b..ef0250ae 100644 --- a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs @@ -155,7 +155,7 @@ static JToken nest(JToken mem) => /// Find the first subschema with the given anchor. ///
/// An if found, otherwise null. - public ElementSchema FindFirstByAnchor(string anchor) => + public ElementSchema? FindFirstByAnchor(string anchor) => Members.OfType().Select(da => da.FindFirstByAnchor(anchor)).FirstOrDefault(s => s is not null); /// diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs index e934006d..30bc42b9 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs @@ -42,7 +42,7 @@ protected override (bool, ResultReport?) RunInvariant(IScopedNode input, Validat { case string value: { - var result = XHtml.IsValidNarrativeXhtml(input.Value.ToString(), out var errors); + var result = XHtml.IsValidNarrativeXhtml(input.Value.ToString()!, out var errors); if (result) { diff --git a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs index 5b69d7bd..4bb85fdd 100644 --- a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs @@ -55,7 +55,7 @@ public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationS return ResultReport.SUCCESS; static string displayValue(ITypedElement te) => - te.Children().Any() ? te.ToJson() : te.Value.ToString(); + te.Children().Any() ? te.ToJson() : te.Value.ToString()!; } /// diff --git a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs index a4a7ba02..612f1a3b 100644 --- a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs @@ -123,7 +123,7 @@ private record ResolutionResult(ITypedElement? ReferencedResource, AggregationMo // 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)) + if (HasAggregation && AggregationRules?.Any(a => a == resolution.ReferenceKind) == false) { var allowed = string.Join(", ", AggregationRules); evidence.Add(new IssueAssertion(Issue.CONTENT_REFERENCE_OF_INVALID_KIND, diff --git a/src/Firely.Fhir.Validation/Impl/RegExValidator.cs b/src/Firely.Fhir.Validation/Impl/RegExValidator.cs index 70eb4c48..562d07a5 100644 --- a/src/Firely.Fhir.Validation/Impl/RegExValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/RegExValidator.cs @@ -47,7 +47,7 @@ public RegExValidator(string pattern) public override ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { var value = toStringRepresentation(input); - var success = _regex.Match(value).Success; + var success = value is not null && _regex.Match(value).Success; return !success ? new IssueAssertion(Issue.CONTENT_ELEMENT_INVALID_PRIMITIVE_VALUE, $"Value '{value}' does not match regex '{Pattern}'") diff --git a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs index d4990162..9891203d 100644 --- a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs @@ -56,7 +56,7 @@ public class SliceCase /// /// /// - public SliceCase(string name, IAssertion condition, IAssertion assertion) + public SliceCase(string name, IAssertion condition, IAssertion? assertion) { Name = name ?? throw new ArgumentNullException(nameof(name)); Condition = condition ?? throw new ArgumentNullException(nameof(condition)); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs index 9dc4edc4..0bf04477 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs @@ -186,7 +186,7 @@ private static ElementDefinition.TypeRefComponent build(string code, string[]? p #endif #if STU3 - private IAssertion convert(string code, string[]? profiles = null, string[]? targets = null) + private IAssertion? convert(string code, string[]? profiles = null, string[]? targets = null) { IEnumerable typeRefs = profiles switch { @@ -202,14 +202,14 @@ private IAssertion convert(string code, string[]? profiles = null, string[]? tar left.Join(right, x => true, y => true, (l, r) => (l, r)); } #else - private IAssertion convert(string code, string[]? profiles = null, string[]? targets = null) + private IAssertion? convert(string code, string[]? profiles = null, string[]? targets = null) => convertTypeReference(build(code, profiles, targets)); #endif - private IAssertion convert(IEnumerable trs) => + private IAssertion? convert(IEnumerable trs) => new TypeReferenceBuilder(_fixture.ResourceResolver).ConvertTypeReferences(trs); - private IAssertion convertTypeReference(ElementDefinition.TypeRefComponent typeRef) + private IAssertion? convertTypeReference(ElementDefinition.TypeRefComponent typeRef) => new TypeReferenceBuilder(_fixture.ResourceResolver).ConvertTypeReference(CommonTypeRefComponent.Convert(typeRef)); } } From 716c4b6c5c41082eba46c35a0c02a31493ae3563 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Thu, 23 Nov 2023 13:47:19 +0100 Subject: [PATCH 11/27] Move to net8.0 --- build/variables.yml | 2 +- firely-validator-api-tests.props | 2 +- firely-validator-api.props | 2 +- test/Benchmarks/Benchmarks.csproj | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/build/variables.yml b/build/variables.yml index 85d0781f..a801d89c 100644 --- a/build/variables.yml +++ b/build/variables.yml @@ -5,6 +5,6 @@ variables: buildConfiguration: 'Release' vmImage: 'windows-latest' - DOTNET_CORE_SDK: '6.0.x' + DOTNET_CORE_SDK: '8.0.x' GITHUB_PACKAGES_APIKEY: $(GitHubPushPackagesAPIKey) # key is set in variable group APIKeys NUGET_APIKEY: $(NuGetAPIKey) # key is set in variable group APIKeys \ No newline at end of file diff --git a/firely-validator-api-tests.props b/firely-validator-api-tests.props index b5fa3ee1..3981111e 100644 --- a/firely-validator-api-tests.props +++ b/firely-validator-api-tests.props @@ -2,7 +2,7 @@ true - net6.0 + net8.0 false 1591 diff --git a/firely-validator-api.props b/firely-validator-api.props index c3566b31..a43a5311 100644 --- a/firely-validator-api.props +++ b/firely-validator-api.props @@ -6,7 +6,7 @@ Firely Firely (https://fire.ly) Copyright 2015-2022 Firely - netstandard2.1 + net8.0 https://github.com/FirelyTeam/firely-serverless-validator diff --git a/test/Benchmarks/Benchmarks.csproj b/test/Benchmarks/Benchmarks.csproj index 8a1076dc..436dce24 100644 --- a/test/Benchmarks/Benchmarks.csproj +++ b/test/Benchmarks/Benchmarks.csproj @@ -3,7 +3,6 @@ Exe - net6.0 Benchmarks Firely.Fhir.Validation.Benchmarks enable From cfdcee1884bc5087f7710a34ea543e954a3ded6e Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Mon, 27 Nov 2023 18:54:22 +0100 Subject: [PATCH 12/27] Made most of the interface internal. --- Firely.Validator.API.sln | 6 +-- firely-validator-api.props | 3 ++ ...ly.Fhir.Validation.Compilation.STU3.csproj | 2 + .../ElementConversionMode.cs | 2 +- .../ElementDefinitionBuilder.cs | 2 +- .../StructureDefinitionBuilder.cs | 2 +- .../ISchemaBuilder.cs | 2 +- .../SchemaBuilder.cs | 2 +- .../SchemaBuilders/BaseSchemaBuilder.cs | 2 +- ...uctureDefinitionToElementSchemaResolver.cs | 2 +- .../Firely.Fhir.Validation.Compilation.csproj | 2 + .../ElementDefinitionValidator.cs | 2 +- .../StructureDefinitionValidator.cs | 2 +- .../Firely.Fhir.Validation.csproj | 36 ++++++++------ .../Impl/AllValidator.cs | 2 +- .../Impl/AnyValidator.cs | 2 +- .../Impl/BasicValidator.cs | 2 +- .../Impl/BindingValidator.cs | 2 +- .../Impl/CanonicalValidator.cs | 2 +- .../Impl/CardinalityValidator.cs | 2 +- .../Impl/ChildrenValidator.cs | 2 +- .../Impl/DatatypeSchema.cs | 2 +- .../Impl/DefinitionsAssertion.cs | 2 +- .../Impl/ElementSchema.cs | 2 +- .../Impl/ExtensionSchema.cs | 3 +- .../Impl/FhirEle1Validator.cs | 2 +- .../Impl/FhirExt1Validator.cs | 2 +- .../Impl/FhirPathValidator.cs | 2 +- src/Firely.Fhir.Validation/Impl/FhirSchema.cs | 2 +- .../Impl/FhirTxt1Validator.cs | 2 +- .../Impl/FhirTxt2Validator.cs | 2 +- .../Impl/FhirTypeLabelValidator.cs | 2 +- .../Impl/FixedValidator.cs | 2 +- .../Impl/InvariantValidator.cs | 2 +- .../Impl/IssueAssertion.cs | 6 +-- .../Impl/MaxLengthValidator.cs | 2 +- .../Impl/MinMaxValueValidator.cs | 2 +- .../Impl/PathSelectorValidator.cs | 2 +- .../Impl/PatternValidator.cs | 2 +- .../Impl/ReferencedInstanceValidator.cs | 2 +- .../Impl/RegExValidator.cs | 2 +- .../Impl/ResourceSchema.cs | 2 +- .../Impl/ResultAssertion.cs | 2 +- .../Impl/SchemaReferenceValidator.cs | 2 +- .../Impl/SliceValidator.cs | 2 +- .../Impl/TraceAssertion.cs | 2 +- .../AssertionToOperationOutcomeExtensions.cs | 2 +- .../Schema/AssertionValidators.cs | 2 +- .../Schema/DefinitionPath.cs | 2 +- .../Schema/IElementSchemaResolver.cs | 2 +- .../Schema/IFixedResult.cs | 2 +- .../Schema/IGroupValidatable.cs | 2 +- .../Schema/IValidatable.cs | 2 +- .../Schema/InstancePath.cs | 2 +- .../Schema/PathStack.cs | 2 +- .../Schema/StructureDefinitionInformation.cs | 2 +- .../Schema/ValidationContext.cs | 16 ++++--- .../Schema/ValidationLogger.cs | 2 +- .../Schema/ValidationState.cs | 2 +- .../Support/CachedElementSchemaResolver.cs | 2 +- .../Support/FhirSchemaGroupAnalyzer.cs | 2 +- .../Support/IAssertionExtensions.cs | 2 +- .../IncorrectElementDefinitionException.cs | 2 +- .../Support/MultiElementSchemaResolver.cs | 2 +- .../SystemNamespaceElementSchemaResolver.cs | 2 +- ...sproj => Firely.Fhir.Validation.R4.csproj} | 9 ++-- ...roj => Firely.Fhir.Validation.STU3.csproj} | 2 +- ...j => Firely.Fhir.Validation.Shared.shproj} | 0 .../IExternalReferenceResolver.cs | 7 +-- src/Hl7.Fhir.Validation.Shared/Validator.cs | 37 +++++++++++++-- test/Benchmarks/Benchmarks.csproj | 7 ++- test/Benchmarks/ValidatorBenchmarks.cs | 47 +++++++++---------- ...r.Validation.Compilation.STU3.Tests.csproj | 2 +- .../BasicSchemaBuilderTests.cs | 3 +- .../FhirTests/WipValidator.cs | 2 +- .../SchemaBuilderFixture.cs | 6 +-- .../TypeReferenceBuilderTests.cs | 2 +- .../Impl/ReferencedInstanceValidatorTests.cs | 3 +- 78 files changed, 179 insertions(+), 136 deletions(-) rename src/Hl7.Fhir.Validation.R4/{Hl7.Fhir.Validation.R4.csproj => Firely.Fhir.Validation.R4.csproj} (69%) rename src/Hl7.Fhir.Validation.STU3/{Hl7.Fhir.Validation.STU3.csproj => Firely.Fhir.Validation.STU3.csproj} (93%) rename src/Hl7.Fhir.Validation.Shared/{Hl7.Fhir.Validation.Shared.shproj => Firely.Fhir.Validation.Shared.shproj} (100%) diff --git a/Firely.Validator.API.sln b/Firely.Validator.API.sln index 0e475cf7..99c1cb5b 100644 --- a/Firely.Validator.API.sln +++ b/Firely.Validator.API.sln @@ -29,7 +29,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmar EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hl7.Fhir.Validation.R5.Tests", "test\Hl7.Fhir.Validation.Tests\Hl7.Fhir.Validation.R5.Tests.csproj", "{533FADED-DC64-408D-AF93-E7AE8D9DAF56}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hl7.Fhir.Validation.R4", "src\Hl7.Fhir.Validation.R4\Hl7.Fhir.Validation.R4.csproj", "{22520A59-7EBF-4740-A66C-3651742FAC0B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.R4", "src\Hl7.Fhir.Validation.R4\Firely.Fhir.Validation.R4.csproj", "{22520A59-7EBF-4740-A66C-3651742FAC0B}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Firely.Fhir.Validation.Compilation.Shared", "src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.shproj", "{7794C7D2-EA50-405D-A61A-23F8ECF4228B}" EndProject @@ -43,9 +43,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Comp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Compilation.R4.Tests", "test\Firely.Fhir.Validation.Compilation.Tests.R4\Firely.Fhir.Validation.Compilation.R4.Tests.csproj", "{B5968107-6E13-46C1-AB04-7FCEFFF179C7}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Hl7.Fhir.Validation.Shared", "src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.shproj", "{21BF806C-BBE6-4A33-91F9-BF3AB5F1E70B}" +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Firely.Fhir.Validation.Shared", "src\Hl7.Fhir.Validation.Shared\Firely.Fhir.Validation.Shared.shproj", "{21BF806C-BBE6-4A33-91F9-BF3AB5F1E70B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hl7.Fhir.Validation.STU3", "src\Hl7.Fhir.Validation.STU3\Hl7.Fhir.Validation.STU3.csproj", "{4C9C5275-0C05-4CDE-A74C-1B43245252FB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.STU3", "src\Hl7.Fhir.Validation.STU3\Firely.Fhir.Validation.STU3.csproj", "{4C9C5275-0C05-4CDE-A74C-1B43245252FB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/firely-validator-api.props b/firely-validator-api.props index 85439109..92e436f4 100644 --- a/firely-validator-api.props +++ b/firely-validator-api.props @@ -28,6 +28,7 @@ true false $(DefineConstants);DEBUG;TRACE + @@ -38,11 +39,13 @@ True snupkg true + 0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1 CS4014 + NU5104 diff --git a/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj b/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj index d453302b..7ce0de0b 100644 --- a/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj +++ b/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj @@ -19,5 +19,7 @@ + + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/ElementConversionMode.cs b/src/Firely.Fhir.Validation.Compilation.Shared/ElementConversionMode.cs index e65dbc01..dbd29c9b 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/ElementConversionMode.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/ElementConversionMode.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation.Compilation /// Determines which kind of schema we want to generate from the /// element. /// - public enum ElementConversionMode + internal enum ElementConversionMode { /// /// Generate a schema which includes all constraints represented diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/ElementDefinitionBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/ElementDefinitionBuilder.cs index 9155977e..4bfd11a8 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/ElementDefinitionBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/ElementDefinitionBuilder.cs @@ -6,7 +6,7 @@ namespace Firely.Fhir.Validation.Compilation /// /// The schema builder for the . /// - public class ElementDefinitionBuilder : ISchemaBuilder + internal class ElementDefinitionBuilder : ISchemaBuilder { /// public IEnumerable Build(ElementDefinitionNavigator nav, ElementConversionMode? conversionMode = ElementConversionMode.Full) diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/StructureDefinitionBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/StructureDefinitionBuilder.cs index 69db32c2..83a64ced 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/StructureDefinitionBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/EnterpriseSchemaBuilders/StructureDefinitionBuilder.cs @@ -6,7 +6,7 @@ namespace Firely.Fhir.Validation.Compilation /// /// The schema builder for the . /// - public class StructureDefinitionBuilder : ISchemaBuilder + internal class StructureDefinitionBuilder : ISchemaBuilder { /// public IEnumerable Build(ElementDefinitionNavigator nav, ElementConversionMode? conversionMode = ElementConversionMode.Full) diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/ISchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/ISchemaBuilder.cs index d65114ac..e3223fb3 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/ISchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/ISchemaBuilder.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation.Compilation /// /// An interface for implementing a schema builder. Utilize this to extend the schema. /// - public interface ISchemaBuilder + internal interface ISchemaBuilder { /// /// Constucts a schema block. diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs index bd3dbc6d..f3866b15 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs @@ -21,7 +21,7 @@ namespace Firely.Fhir.Validation.Compilation /// Converts the constraints in a to an /// , which can then be used for validation. /// - public class SchemaBuilder : ISchemaBuilder + internal class SchemaBuilder : ISchemaBuilder { /// /// The resolver to use when the under conversion diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BaseSchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BaseSchemaBuilder.cs index 7822426e..09870377 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BaseSchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BaseSchemaBuilder.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation.Compilation /// you can directly implement the /// method using the parameter instead of the . /// - public abstract class BaseSchemaBuilder : ISchemaBuilder + internal abstract class BaseSchemaBuilder : ISchemaBuilder { /// public IEnumerable Build(ElementDefinitionNavigator nav, diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs b/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs index d6fa8681..7f482e54 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs @@ -27,7 +27,7 @@ namespace Firely.Fhir.Validation.Compilation /// Also, since schema generation is expensive, this resolver will cache the results /// and return the already-converted schema for the same uri the next time " is called. /// - public class StructureDefinitionToElementSchemaResolver : IElementSchemaResolver // internal? + internal class StructureDefinitionToElementSchemaResolver : IElementSchemaResolver // internal? { private readonly SchemaBuilder _schemaBuilder; diff --git a/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj b/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj index 67a27455..2377634b 100644 --- a/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj +++ b/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj @@ -20,5 +20,7 @@ + + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs index 1c7f3d09..046891b2 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// An that represents a FHIR ElementDefinition /// [DataContract] - public class ElementDefinitionValidator : IValidatable + internal class ElementDefinitionValidator : IValidatable { /// public JToken ToJson() => new JProperty("elementDefinition", new JObject()); diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs index 10e70594..a6817899 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// An that represents a FHIR StructureDefinition ///
[DataContract] - public class StructureDefinitionValidator : IValidatable + internal class StructureDefinitionValidator : IValidatable { /// public JToken ToJson() => new JProperty("elementDefinition", new JObject()); diff --git a/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj b/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj index ac86719e..ec2817db 100644 --- a/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj +++ b/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj @@ -1,19 +1,27 @@ - + - + - - Firely validation library - Next-gen Firely validation library for FHIR data. - HL7;FHIR;Validation;Utility; - Firely.Fhir.Validation - + + Firely validation library + Next-gen Firely validation library for FHIR data. + HL7;FHIR;Validation;Utility; + Firely.Fhir.Validation + - - - - - - + + + + + + + + + + + + + + diff --git a/src/Firely.Fhir.Validation/Impl/AllValidator.cs b/src/Firely.Fhir.Validation/Impl/AllValidator.cs index 3f36e527..a9076c18 100644 --- a/src/Firely.Fhir.Validation/Impl/AllValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/AllValidator.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// An assertion that expresses that all member assertions should hold. ///
[DataContract] - public class AllValidator : IGroupValidatable + internal class AllValidator : IGroupValidatable { /// /// The member assertions the instance should be validated against. diff --git a/src/Firely.Fhir.Validation/Impl/AnyValidator.cs b/src/Firely.Fhir.Validation/Impl/AnyValidator.cs index 9a7aa9a4..9faf4db0 100644 --- a/src/Firely.Fhir.Validation/Impl/AnyValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/AnyValidator.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// An assertion that expresses that any of its member assertions should hold. /// [DataContract] - public class AnyValidator : IGroupValidatable + internal class AnyValidator : IGroupValidatable { /// /// The member assertions of which at least one should hold. diff --git a/src/Firely.Fhir.Validation/Impl/BasicValidator.cs b/src/Firely.Fhir.Validation/Impl/BasicValidator.cs index 1f66fc1e..33e4f04e 100644 --- a/src/Firely.Fhir.Validation/Impl/BasicValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/BasicValidator.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// /// Base class for simple validators that have only a single property to configure. /// - public abstract class BasicValidator : IValidatable + internal abstract class BasicValidator : IValidatable { /// public virtual JToken ToJson() => new JProperty(Key, Value); diff --git a/src/Firely.Fhir.Validation/Impl/BindingValidator.cs b/src/Firely.Fhir.Validation/Impl/BindingValidator.cs index 0585db39..f5a93499 100644 --- a/src/Firely.Fhir.Validation/Impl/BindingValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/BindingValidator.cs @@ -24,7 +24,7 @@ namespace Firely.Fhir.Validation /// An assertion that expresses terminology binding requirements for a coded element. /// [DataContract] - public class BindingValidator : IValidatable + internal class BindingValidator : IValidatable { /// /// How strongly use of the valueset specified in the binding is encouraged or enforced. diff --git a/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs b/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs index 39e120dd..f7d6abae 100644 --- a/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/CanonicalValidator.cs @@ -11,7 +11,7 @@ namespace Firely.Fhir.Validation /// The purpose of this validator is to enforce this rule and perform the necessary checks. /// [DataContract] - public class CanonicalValidator : IValidatable + internal class CanonicalValidator : IValidatable { /// public JToken ToJson() => new JProperty("canonical", new JObject()); diff --git a/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs b/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs index 976c00a8..1c911f4c 100644 --- a/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs @@ -22,7 +22,7 @@ namespace Firely.Fhir.Validation /// of elements in a slice for example. /// [DataContract] - public class CardinalityValidator : IGroupValidatable + internal class CardinalityValidator : IGroupValidatable { /// /// Lower bound for the cardinality. If not set, there is no lower bound. diff --git a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs index ad823960..67ab9e23 100644 --- a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs @@ -19,7 +19,7 @@ namespace Firely.Fhir.Validation /// can be applied to a child, depending on its name. /// [DataContract] - public class ChildrenValidator : IValidatable, IReadOnlyDictionary + internal class ChildrenValidator : IValidatable, IReadOnlyDictionary { private readonly Dictionary _childList = new(); diff --git a/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs b/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs index c7241e4d..c8682be7 100644 --- a/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// An that represents a FHIR datatype (except Extension). /// [DataContract] - public class DatatypeSchema : FhirSchema + internal class DatatypeSchema : FhirSchema { /// /// Constructs a new diff --git a/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs b/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs index 411121c9..6ebadc51 100644 --- a/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/DefinitionsAssertion.cs @@ -23,7 +23,7 @@ namespace Firely.Fhir.Validation /// string of the anchor. /// [DataContract] - public class DefinitionsAssertion : IAssertion + internal class DefinitionsAssertion : IAssertion { /// /// The list of subschemas. diff --git a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs index 0535bc3b..4dea7a46 100644 --- a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs @@ -19,7 +19,7 @@ namespace Firely.Fhir.Validation /// schema to be succesful. /// [DataContract] - public class ElementSchema : IGroupValidatable + internal class ElementSchema : IGroupValidatable { /// /// The unique id for this schema. diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs index e4baec72..fcbd95f7 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs @@ -10,7 +10,6 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.Serialization; -using static Firely.Fhir.Validation.ValidationContext; namespace Firely.Fhir.Validation { @@ -18,7 +17,7 @@ namespace Firely.Fhir.Validation /// A schema representing a FHIR Extension datatype. /// [DataContract] - public class ExtensionSchema : FhirSchema + internal class ExtensionSchema : FhirSchema { /// /// Constructs a new diff --git a/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs index de4b7e8b..fe720381 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirEle1Validator.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// Represents the hand-coded version of the equivalent running invariant "ele-1". /// [DataContract] - public class FhirEle1Validator : InvariantValidator + internal class FhirEle1Validator : InvariantValidator { /// public override string Key => "ele-1"; diff --git a/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs index 87155013..e9e01a7e 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirExt1Validator.cs @@ -15,7 +15,7 @@ namespace Firely.Fhir.Validation /// Represents the hand-coded version of the equivalent running invariant "ext-1". /// [DataContract] - public class FhirExt1Validator : InvariantValidator + internal class FhirExt1Validator : InvariantValidator { /// public override string Key => "ext-1"; diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index 98e6b9d1..0fb20708 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -25,7 +25,7 @@ namespace Firely.Fhir.Validation /// An assertion expressed using FhirPath. /// [DataContract] - public class FhirPathValidator : InvariantValidator + internal class FhirPathValidator : InvariantValidator { /// [DataMember] diff --git a/src/Firely.Fhir.Validation/Impl/FhirSchema.cs b/src/Firely.Fhir.Validation/Impl/FhirSchema.cs index 38ec5692..18dbffa8 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirSchema.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// /// An that represents a FHIR datatype or resource. /// - public abstract class FhirSchema : ElementSchema + internal abstract class FhirSchema : ElementSchema { /// /// A collection of information from the StructureDefintion from which diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs index e934006d..e310e5e4 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt1Validator.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// Represents the hand-coded version of the equivalent running invariant "ext-1". /// [DataContract] - public class FhirTxt1Validator : InvariantValidator + internal class FhirTxt1Validator : InvariantValidator { /// public override string Key => "txt-1"; diff --git a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs index 4f638a8b..60bb9cbf 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTxt2Validator.cs @@ -15,7 +15,7 @@ namespace Firely.Fhir.Validation /// Represents the hand-coded version of the equivalent running invariant "ext-1". /// [DataContract] - public class FhirTxt2Validator : InvariantValidator + internal class FhirTxt2Validator : InvariantValidator { /// public override string Key => "txt-2"; diff --git a/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs index 86ddcda7..ca15527f 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirTypeLabelValidator.cs @@ -15,7 +15,7 @@ namespace Firely.Fhir.Validation /// /// The instance type is taken from [DataContract] - public class FhirTypeLabelValidator : BasicValidator + internal class FhirTypeLabelValidator : BasicValidator { /// /// The stated instance type. diff --git a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs index 5b69d7bd..323136e5 100644 --- a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs @@ -18,7 +18,7 @@ namespace Firely.Fhir.Validation /// Asserts that the value of an element is exactly the same as a given fixed value. /// [DataContract] - public class FixedValidator : IValidatable + internal class FixedValidator : IValidatable { /// /// The fixed value to compare an instance against. diff --git a/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs b/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs index 8c268cd7..6f99d3ee 100644 --- a/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/InvariantValidator.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// An assertion expressed using FhirPath. /// [DataContract] - public abstract class InvariantValidator : IValidatable + internal abstract class InvariantValidator : IValidatable { /// /// The shorthand code identifying the invariant, as defined in the StructureDefinition. diff --git a/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs b/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs index f900d00c..27bf3aea 100644 --- a/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/IssueAssertion.cs @@ -69,7 +69,7 @@ public class IssueAssertion : IFixedResult, IValidatable, IEquatable /// A reference to the definition (=an element in a StructureDefinition) that raised the issue. /// - public DefinitionPath? DefinitionPath { get; private set; } + internal DefinitionPath? DefinitionPath { get; private set; } /// /// Interprets the of the assertion as a @@ -154,7 +154,7 @@ public static class Pattern } /// - public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState state) + ResultReport IValidatable.Validate(IScopedNode input, ValidationContext _, ValidationState state) { // Validation does not mean anything more than using this instance as a prototype and // turning the issue assertion into a result by cloning the prototype and setting the @@ -172,7 +172,7 @@ public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationS /// /// /// - public ResultReport AsResult(ValidationState state) => asResult(state.Location.InstanceLocation.ToString(), state.Location.DefinitionPath); + internal ResultReport AsResult(ValidationState state) => asResult(state.Location.InstanceLocation.ToString(), state.Location.DefinitionPath); /// /// Package this as a diff --git a/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs b/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs index 5a17a660..a922fdfb 100644 --- a/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// Asserts a maximum length on an element that contains a string value. /// [DataContract] - public class MaxLengthValidator : BasicValidator + internal class MaxLengthValidator : BasicValidator { /// /// The maximum length the string in the instance should be. diff --git a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs index 1f949a8d..e2869cc6 100644 --- a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs @@ -19,7 +19,7 @@ namespace Firely.Fhir.Validation /// Asserts the maximum (or minimum) value for an element. /// [DataContract] - public class MinMaxValueValidator : IValidatable + internal class MinMaxValueValidator : IValidatable { /// /// Represents a mode op operation for the . diff --git a/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs b/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs index a44da508..fa283b1f 100644 --- a/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs @@ -21,7 +21,7 @@ namespace Firely.Fhir.Validation /// Used internally only for discriminating the cases of a . /// [DataContract] - public class PathSelectorValidator : IValidatable + internal class PathSelectorValidator : IValidatable { /// /// The FhirPath statement used to select a value to validate. diff --git a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs index 0721af01..733e3bd4 100644 --- a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs @@ -21,7 +21,7 @@ namespace Firely.Fhir.Validation /// pattern element /// in the FHIR specification. [DataContract] - public class PatternValidator : IValidatable + internal class PatternValidator : IValidatable { /// /// The pattern the instance will be validated against. diff --git a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs index 1fd2fbe2..2b71b933 100644 --- a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs @@ -21,7 +21,7 @@ namespace Firely.Fhir.Validation /// to be found at runtime in the "reference" child of the input. /// [DataContract] - public class ReferencedInstanceValidator : IValidatable + internal class ReferencedInstanceValidator : IValidatable { /// /// When the referenced resource was found, it will be validated against diff --git a/src/Firely.Fhir.Validation/Impl/RegExValidator.cs b/src/Firely.Fhir.Validation/Impl/RegExValidator.cs index 70eb4c48..1170334a 100644 --- a/src/Firely.Fhir.Validation/Impl/RegExValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/RegExValidator.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// Asserts that the value of an element (converted to a string) matches a given regular expression. /// [DataContract] - public class RegExValidator : BasicValidator + internal class RegExValidator : BasicValidator { /// /// The regex pattern used to validate the instance against. diff --git a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs index e0475d2e..34b12cce 100644 --- a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs @@ -19,7 +19,7 @@ namespace Firely.Fhir.Validation /// It will perform additional resource-specific validation logic associated with resources, /// like selecting Meta.profile as additional profiles to be validated. [DataContract] - public class ResourceSchema : FhirSchema + internal class ResourceSchema : FhirSchema { /// /// Constructs a new diff --git a/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs b/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs index d0b85337..966399db 100644 --- a/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/ResultAssertion.cs @@ -15,7 +15,7 @@ namespace Firely.Fhir.Validation /// Asserts a validation result. /// [DataContract] - public class ResultAssertion : BasicValidator, IFixedResult + internal class ResultAssertion : BasicValidator, IFixedResult { /// /// Will validate to a success assertion without evidence. diff --git a/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs b/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs index d1274c13..e3120728 100644 --- a/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SchemaReferenceValidator.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// Asserts the validity of an element against a fixed schema. /// [DataContract] - public class SchemaReferenceValidator : IGroupValidatable + internal class SchemaReferenceValidator : IGroupValidatable { /// /// A singleton representing a schema reference to . diff --git a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs index d4990162..cfa5e8f2 100644 --- a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs @@ -22,7 +22,7 @@ namespace Firely.Fhir.Validation /// be validated against the assertions defined for each case. /// [DataContract] - public class SliceValidator : IGroupValidatable + internal class SliceValidator : IGroupValidatable { /// /// Represents a named, conditional assertion on a set of elements. diff --git a/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs b/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs index 36aa1701..b635f8b7 100644 --- a/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs +++ b/src/Firely.Fhir.Validation/Impl/TraceAssertion.cs @@ -43,7 +43,7 @@ public TraceAssertion(string location, string message) } /// - public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState state) + ResultReport IValidatable.Validate(IScopedNode input, ValidationContext _, ValidationState state) { // Validation does not mean anything more than using this instance as a prototype and // turning the trace assertion into a result by cloning the prototype and setting the diff --git a/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs b/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs index 516030f1..8e13356b 100644 --- a/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs +++ b/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs @@ -14,7 +14,7 @@ namespace Firely.Fhir.Validation /// /// Extension methods to interoperate with FHIR OperationOutcome /// - public static class AssertionToOperationOutcomeExtensions + internal static class AssertionToOperationOutcomeExtensions { /// /// Build an OperationOutcome from Assertion diff --git a/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs b/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs index 3f5bdc0c..9ccbe9fc 100644 --- a/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs +++ b/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs @@ -19,7 +19,7 @@ namespace Firely.Fhir.Validation /// of that do not implement these two interfaces. These extension /// methods allow the caller to invoke each of them, using a uniform Validate method call. /// - public static class AssertionValidators + internal static class AssertionValidators { /// /// Validates a set of instance elements against an assertion. diff --git a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs index 51ebc2a0..7fa674b1 100644 --- a/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs +++ b/src/Firely.Fhir.Validation/Schema/DefinitionPath.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// This class is also used to track the location of an instance. /// /// An example skeleton path is: "Resource(canonical).childA.childB[sliceB1].childC -> Datatype(canonical).childA" - public class DefinitionPath : PathStack + internal class DefinitionPath : PathStack { private DefinitionPath(PathStackEvent? current) : base(current) { diff --git a/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs b/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs index 6574634f..cb03c099 100644 --- a/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs @@ -9,7 +9,7 @@ namespace Firely.Fhir.Validation /// /// An interface for objects that let you obtain an by its schema uri. /// - public interface IElementSchemaResolver + internal interface IElementSchemaResolver { /// /// Retrieve a schema by its schema uri. diff --git a/src/Firely.Fhir.Validation/Schema/IFixedResult.cs b/src/Firely.Fhir.Validation/Schema/IFixedResult.cs index 1f797768..d3d5b707 100644 --- a/src/Firely.Fhir.Validation/Schema/IFixedResult.cs +++ b/src/Firely.Fhir.Validation/Schema/IFixedResult.cs @@ -11,7 +11,7 @@ namespace Firely.Fhir.Validation /// /// These are validators like , and that have /// the result of running them in a validation configured at compilation time. It is used to simplify schema's at compilation time. - public interface IFixedResult + internal interface IFixedResult { /// /// The represented by this instance. diff --git a/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs b/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs index 6632d55d..a6f48e7c 100644 --- a/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs +++ b/src/Firely.Fhir.Validation/Schema/IGroupValidatable.cs @@ -13,7 +13,7 @@ namespace Firely.Fhir.Validation /// The interface for a validation assertion that validates a rule about a set of elements. /// /// A rule that validates cardinality is a great example of this kind of assertion. - public interface IGroupValidatable : IAssertion, IValidatable + internal interface IGroupValidatable : IAssertion, IValidatable { /// /// Validates a set of instances, given a location representative for the group. diff --git a/src/Firely.Fhir.Validation/Schema/IValidatable.cs b/src/Firely.Fhir.Validation/Schema/IValidatable.cs index c1742e81..0897043c 100644 --- a/src/Firely.Fhir.Validation/Schema/IValidatable.cs +++ b/src/Firely.Fhir.Validation/Schema/IValidatable.cs @@ -11,7 +11,7 @@ namespace Firely.Fhir.Validation /// /// Implemented by assertions that work on a single . /// - public interface IValidatable : IAssertion + internal interface IValidatable : IAssertion { /// /// Validates a single instance. diff --git a/src/Firely.Fhir.Validation/Schema/InstancePath.cs b/src/Firely.Fhir.Validation/Schema/InstancePath.cs index 04c8bec3..c4b7df63 100644 --- a/src/Firely.Fhir.Validation/Schema/InstancePath.cs +++ b/src/Firely.Fhir.Validation/Schema/InstancePath.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// /// A class representing the location of an instance. /// - public class InstancePath : PathStack + internal class InstancePath : PathStack { private InstancePath(PathStackEvent? current) : base(current) { diff --git a/src/Firely.Fhir.Validation/Schema/PathStack.cs b/src/Firely.Fhir.Validation/Schema/PathStack.cs index 0f351711..dac5665b 100644 --- a/src/Firely.Fhir.Validation/Schema/PathStack.cs +++ b/src/Firely.Fhir.Validation/Schema/PathStack.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// /// An example skeleton path is: "Resource(canonical).childA.childB[sliceB1].childC -> Datatype(canonical).childA" /// - public abstract class PathStack + internal abstract class PathStack { /// /// Construct a new PathStack and place the current event on top of the stack. diff --git a/src/Firely.Fhir.Validation/Schema/StructureDefinitionInformation.cs b/src/Firely.Fhir.Validation/Schema/StructureDefinitionInformation.cs index 5a8dfb7c..57e35b01 100644 --- a/src/Firely.Fhir.Validation/Schema/StructureDefinitionInformation.cs +++ b/src/Firely.Fhir.Validation/Schema/StructureDefinitionInformation.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// Represents an informational assertion that has details about the StructureDefinition from which this schema is generated. /// [DataContract] - public record StructureDefinitionInformation : IJsonSerializable + internal record StructureDefinitionInformation : IJsonSerializable { /// /// How a type relates to its baseDefinition. (url: http://hl7.org/fhir/ValueSet/type-derivation-rule) diff --git a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs index 10f67199..cecce6ca 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs @@ -10,6 +10,8 @@ using Hl7.Fhir.Specification.Terminology; using Hl7.FhirPath; using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; namespace Firely.Fhir.Validation @@ -19,7 +21,7 @@ namespace Firely.Fhir.Validation /// /// Generally, you will configure one such within a /// subsystem doing validation. - public class ValidationContext + internal class ValidationContext { /// /// Initializes a new ValidationContext with the minimal dependencies. @@ -98,26 +100,26 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// A function to include the assertion in the validation or not. If the function is left empty (null) then all the /// assertions are processed in the validation. /// - public Predicate? IncludeFilter = null; + public ICollection> IncludeFilters = new List>(); /// /// A function to exclude the assertion in the validation or not. If the function is left empty (null) then all the /// assertions are processed in the validation. /// - public Predicate? ExcludeFilter = DEFAULT_EXCLUDE_FILTER; + public ICollection> ExcludeFilters = new[] { DEFAULT_EXCLUDE_FILTER }; /// - /// The default for , which will exclude FhirPath invariant dom-6 from triggering. + /// The default for , which will exclude FhirPath invariant dom-6 from triggering. /// public static readonly Predicate DEFAULT_EXCLUDE_FILTER = a => a is FhirPathValidator fhirPathAssertion && fhirPathAssertion.Key == "dom-6"; /// /// Determines whether a given assertion is included in the validation. The outcome is determined by - /// and . + /// and . /// public bool Filter(IAssertion a) => - (IncludeFilter is null || IncludeFilter(a)) && - (ExcludeFilter is null || !ExcludeFilter(a)); + (!IncludeFilters.Any() || IncludeFilters.Any(inc => inc(a))) && + !ExcludeFilters.Any(exc => exc(a)); /// /// Whether to add trace messages to the validation result. diff --git a/src/Firely.Fhir.Validation/Schema/ValidationLogger.cs b/src/Firely.Fhir.Validation/Schema/ValidationLogger.cs index 3a7d19dc..bb5bd885 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationLogger.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationLogger.cs @@ -21,7 +21,7 @@ namespace Firely.Fhir.Validation /// validated, and what the previous result was. Since it keeps track of running validations, it /// can also be used to detect loops. /// - public class ValidationLogger + internal class ValidationLogger { //TODO: I think this should have a parent logger /// diff --git a/src/Firely.Fhir.Validation/Schema/ValidationState.cs b/src/Firely.Fhir.Validation/Schema/ValidationState.cs index ba5a5452..ce14631f 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationState.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationState.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// /// Represents thread-safe, shareable state for a single run of the validator. /// - public record ValidationState + internal record ValidationState { /// /// A container for state properties that are shared across a full run of the validator. diff --git a/src/Firely.Fhir.Validation/Support/CachedElementSchemaResolver.cs b/src/Firely.Fhir.Validation/Support/CachedElementSchemaResolver.cs index 8afd3206..8257b82b 100644 --- a/src/Firely.Fhir.Validation/Support/CachedElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation/Support/CachedElementSchemaResolver.cs @@ -14,7 +14,7 @@ namespace Firely.Fhir.Validation /// , unless this has been resolved before, in which /// case resolution is done immediately from a cache. /// - public class CachedElementSchemaResolver : IElementSchemaResolver + internal class CachedElementSchemaResolver : IElementSchemaResolver { private readonly ConcurrentDictionary _cache = new(); diff --git a/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs b/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs index a11eec1a..d7fd1774 100644 --- a/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs +++ b/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs @@ -17,7 +17,7 @@ namespace Firely.Fhir.Validation /// /// This class is highly dependent on the presence of the FHIR-specific /// in ElementSchema, so it will only work for types that are derived from FHIR StructureDefinitions. - public static class FhirSchemaGroupAnalyzer + internal static class FhirSchemaGroupAnalyzer { /// /// The result of resolving a schema: either a schema, or a detailing the failure. diff --git a/src/Firely.Fhir.Validation/Support/IAssertionExtensions.cs b/src/Firely.Fhir.Validation/Support/IAssertionExtensions.cs index b176b354..4f8d5107 100644 --- a/src/Firely.Fhir.Validation/Support/IAssertionExtensions.cs +++ b/src/Firely.Fhir.Validation/Support/IAssertionExtensions.cs @@ -12,7 +12,7 @@ namespace Firely.Fhir.Validation /// /// /// - public static class IAssertionExtensions + internal static class IAssertionExtensions { /// /// diff --git a/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs b/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs index 1b979a3a..085b3473 100644 --- a/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs +++ b/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs @@ -13,7 +13,7 @@ namespace Hl7.Fhir.Validation /// /// Exception about an error encountered in an ElementDefinitions. /// - public class IncorrectElementDefinitionException : Exception + internal class IncorrectElementDefinitionException : Exception { /// /// Exception about an error encountered in an ElementDefinitions. diff --git a/src/Firely.Fhir.Validation/Support/MultiElementSchemaResolver.cs b/src/Firely.Fhir.Validation/Support/MultiElementSchemaResolver.cs index 6f724dfc..90815f09 100644 --- a/src/Firely.Fhir.Validation/Support/MultiElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation/Support/MultiElementSchemaResolver.cs @@ -13,7 +13,7 @@ namespace Firely.Fhir.Validation /// This implementation of will resolve a single uri against multiple /// child resolvers: the result of the first child resolver to return a non-null resolution will be used. /// - public class MultiElementSchemaResolver : IElementSchemaResolver + internal class MultiElementSchemaResolver : IElementSchemaResolver { /// /// The set of child used as the source for resolving schemas, diff --git a/src/Firely.Fhir.Validation/Support/SystemNamespaceElementSchemaResolver.cs b/src/Firely.Fhir.Validation/Support/SystemNamespaceElementSchemaResolver.cs index 5878484f..5474d91c 100644 --- a/src/Firely.Fhir.Validation/Support/SystemNamespaceElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation/Support/SystemNamespaceElementSchemaResolver.cs @@ -18,7 +18,7 @@ namespace Firely.Fhir.Validation /// for the full list). These types are the "atomic" types StructureDefinitions in FHIR are based /// on, so themselves have no source definition to convert from. /// - public class SystemNamespaceElementSchemaResolver : IElementSchemaResolver + internal class SystemNamespaceElementSchemaResolver : IElementSchemaResolver { // Note: we could move these to the SDK, but once our revision of the // type system is done, we can retrieve this using reflection too... diff --git a/src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj b/src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj similarity index 69% rename from src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj rename to src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj index 17042ff4..93948ea5 100644 --- a/src/Hl7.Fhir.Validation.R4/Hl7.Fhir.Validation.R4.csproj +++ b/src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj @@ -14,10 +14,9 @@ - - - + + + - - + \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.STU3/Hl7.Fhir.Validation.STU3.csproj b/src/Hl7.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj similarity index 93% rename from src/Hl7.Fhir.Validation.STU3/Hl7.Fhir.Validation.STU3.csproj rename to src/Hl7.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj index 95971a4b..d8b55f30 100644 --- a/src/Hl7.Fhir.Validation.STU3/Hl7.Fhir.Validation.STU3.csproj +++ b/src/Hl7.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.shproj b/src/Hl7.Fhir.Validation.Shared/Firely.Fhir.Validation.Shared.shproj similarity index 100% rename from src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.shproj rename to src/Hl7.Fhir.Validation.Shared/Firely.Fhir.Validation.Shared.shproj diff --git a/src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs b/src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs index 3c92b0d4..e60ac2b2 100644 --- a/src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs +++ b/src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs @@ -3,6 +3,7 @@ * Proprietary and confidential. Unauthorized copying of this file, * via any medium is strictly prohibited. */ +using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using System.Threading.Tasks; @@ -14,10 +15,10 @@ namespace Firely.Fhir.Validation public interface IExternalReferenceResolver { /// - /// Resolves the reference to a resource. + /// Resolves the reference to a resource. The returned object must be either a or /// - /// The resource, or null if the reference could not be resolved. - Task ResolveAsync(string reference); + /// The resource or element node, or null if the reference could not be resolved. + Task ResolveAsync(string reference); } } diff --git a/src/Hl7.Fhir.Validation.Shared/Validator.cs b/src/Hl7.Fhir.Validation.Shared/Validator.cs index 4c7959d8..6a77dbd3 100644 --- a/src/Hl7.Fhir.Validation.Shared/Validator.cs +++ b/src/Hl7.Fhir.Validation.Shared/Validator.cs @@ -10,6 +10,7 @@ using Hl7.Fhir.Specification.Terminology; using Hl7.Fhir.Utility; using Hl7.FhirPath; +using System; namespace Firely.Fhir.Validation { @@ -30,7 +31,7 @@ public class Validator public Validator( ICodeValidationTerminologyService terminologyService, IAsyncResourceResolver resourceResolver, - IExternalReferenceResolver referenceResolver, + IExternalReferenceResolver? referenceResolver, FhirPathCompiler? fhirPathCompiler = null) { var elementSchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(resourceResolver); @@ -38,20 +39,32 @@ public Validator( _validationContext = new ValidationContext(elementSchemaResolver, terminologyService) { FhirPathCompiler = fhirPathCompiler, - ResolveExternalReference = resolve, + ResolveExternalReference = referenceResolver is not null ? resolve : null, ConstraintBestPractices = ConstraintBestPractices, SelectMetaProfiles = MetaProfileSelector, FollowExtensionUrl = ExtensionUrlFollower, TypeNameMapper = TypeNameMapper }; + if (SkipConstraintValidation) + _validationContext.ExcludeFilters.Add(ass => ass is FhirPathValidator); + ITypedElement? resolve(string reference, string location) { var r = TaskHelper.Await(() => referenceResolver.ResolveAsync(reference)); - return r?.ToTypedElement(ModelInfo.ModelInspector); + return toTypedElement(r); } } + private static ITypedElement? toTypedElement(object? o) => + o switch + { + null => null, + ElementNode en => en, + Resource r => r.ToTypedElement(ModelInfo.ModelInspector), + _ => throw new ArgumentException("Reference resolver must return either a Resource or ElementNode.") + }; + private readonly ValidationContext _validationContext; /// @@ -80,6 +93,14 @@ public Validator( /// public TypeNameMapper? TypeNameMapper { get; set; } + /// + /// StructureDefinition may contain FhirPath constraints to enfore invariants in the data that cannot + /// be expresses using StructureDefinition alone. This validation can be turned off for performance or + /// debugging purposes. Default is 'false'. + /// + public bool SkipConstraintValidation { get; set; } // = false; + + /// /// Validates an instance against a profile. /// @@ -89,5 +110,15 @@ public ResultReport Validate(Resource instance, string profile) var validator = new SchemaReferenceValidator(profile); return validator.Validate(instance.ToTypedElement(ModelInfo.ModelInspector).AsScopedNode(), _validationContext); } + + /// + /// Validates an instance against a profile. + /// + /// A report containing the issues found during validation. + public ResultReport Validate(ElementNode instance, string profile) + { + var validator = new SchemaReferenceValidator(profile); + return validator.Validate(instance.AsScopedNode(), _validationContext); + } } } diff --git a/test/Benchmarks/Benchmarks.csproj b/test/Benchmarks/Benchmarks.csproj index 8a1076dc..29577bea 100644 --- a/test/Benchmarks/Benchmarks.csproj +++ b/test/Benchmarks/Benchmarks.csproj @@ -1,6 +1,6 @@ - + - + Exe net6.0 @@ -75,8 +75,7 @@ - - + diff --git a/test/Benchmarks/ValidatorBenchmarks.cs b/test/Benchmarks/ValidatorBenchmarks.cs index a2fa473e..fde9232c 100644 --- a/test/Benchmarks/ValidatorBenchmarks.cs +++ b/test/Benchmarks/ValidatorBenchmarks.cs @@ -6,11 +6,10 @@ using Hl7.Fhir.Specification; using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Specification.Terminology; -using Hl7.Fhir.Validation; using System; using System.Diagnostics; using System.IO; -using System.Linq; +using System.Threading.Tasks; //using Validator = Hl7.Fhir.Validation.Validator; namespace Firely.Sdk.Benchmarks @@ -22,11 +21,9 @@ public class ValidatorBenchmarks private static readonly IStructureDefinitionSummaryProvider PROVIDER = new StructureDefinitionSummaryProvider(ZIPSOURCE); private static readonly string TEST_DIRECTORY = Path.GetFullPath(@"TestData\DocumentComposition"); - public ITypedElement? TestResource = null; + public ElementNode? TestResource = null; public string? InstanceTypeProfile = null; - public IElementSchemaResolver? SchemaResolver = null; public IResourceResolver? TestResolver = null; - public ElementSchema? TestSchema = null; [GlobalSetup] public void GlobalSetup() @@ -34,17 +31,15 @@ public void GlobalSetup() //var testResourceData = File.ReadAllText(Path.Combine(TEST_DIRECTORY, "Levin.patient.xml")); var testResourceData = File.ReadAllText(Path.Combine(TEST_DIRECTORY, "MainBundle.bundle.xml")); - TestResource = FhirXmlNode.Parse(testResourceData).ToTypedElement(PROVIDER)!; + TestResource = ElementNode.FromElement(FhirXmlNode.Parse(testResourceData).ToTypedElement(PROVIDER)!); //InstanceTypeProfile = Hl7.Fhir.Model.ModelInfo.CanonicalUriForFhirCoreType(TestResource.InstanceType).Value!; InstanceTypeProfile = "http://example.org/StructureDefinition/DocumentBundle"; var testFilesResolver = new DirectorySource(TEST_DIRECTORY); TestResolver = new CachedResolver(new SnapshotSource(new CachedResolver(new MultiResolver(testFilesResolver, ZIPSOURCE))))!; - SchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(TestResolver.AsAsync()!); - TestSchema = SchemaResolver.GetSchema(InstanceTypeProfile); // To avoid warnings about bi-model distributions, run the (slow) first-time run here in setup - var cold = validateWip(TestResource!, TestSchema!, TestResolver!, SchemaResolver!); + var cold = validateWip(TestResource!, InstanceTypeProfile!, TestResolver!); Debug.Assert(cold.IsSuccessful); var oldCold = validateCurrent(TestResource!, InstanceTypeProfile!, TestResolver!); @@ -60,25 +55,16 @@ public void CurrentValidator() [Benchmark] public void WipValidator() { - _ = validateWip(TestResource!, TestSchema!, TestResolver!, SchemaResolver!); + _ = validateWip(TestResource!, InstanceTypeProfile!, TestResolver!); } - private static ResultReport validateWip(ITypedElement typedElement, ElementSchema schema, IResourceResolver arr, IElementSchemaResolver schemaResolver) + private static ResultReport validateWip(ElementNode typedElement, string schema, IResourceResolver rr) { - var constraintsToBeIgnored = new string[] { "rng-2", "dom-6" }; - var validationContext = new ValidationContext(schemaResolver, new LocalTerminologyService(arr.AsAsync())) - { - ResolveExternalReference = (u, _) => arr.ResolveByUri(u)?.ToTypedElement(), - // IncludeFilter = Settings.SkipConstraintValidation ? (Func)(a => !(a is FhirPathAssertion)) : (Func)null, - // 20190703 Issue 447 - rng-2 is incorrect in DSTU2 and STU3. EK - // should be removed from STU3/R4 once we get the new normative version - // of FP up, which could do comparisons between quantities. - // 2022-01-19 MS: added best practice constraint "dom-6" to be ignored, which checks if a resource has a narrative. - ExcludeFilter = a => a is FhirPathValidator fhirPathAssertion && constraintsToBeIgnored.Contains(fhirPathAssertion.Key) - }; - - typedElement = ElementNode.FromElement(typedElement); - var result = schema!.Validate(typedElement, validationContext); + var arr = rr.AsAsync(); + var ts = new LocalTerminologyService(arr); + + var validator = new Validator(ts, arr, new TestExternalReferenceResolver(rr)); + var result = validator.Validate(typedElement, schema); return result; } @@ -101,5 +87,16 @@ private static Hl7.Fhir.Model.OperationOutcome validateCurrent(ITypedElement typ //return outcome; } + private class TestExternalReferenceResolver : IExternalReferenceResolver + { + public TestExternalReferenceResolver(IResourceResolver resolver) + { + Resolver = resolver; + } + + public IResourceResolver Resolver { get; } + + public Task ResolveAsync(string reference) => Task.FromResult((object?)Resolver.ResolveByUri(reference)); + } } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj index 747d8f0c..33fa3d48 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj @@ -7,7 +7,7 @@ - Firely.Fhir.Validation.Compilation.Tests.STU3 + Firely.Fhir.Validation.Compilation.STU3.Tests diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs index cdf522a9..2b15f959 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/BasicSchemaBuilderTests.cs @@ -18,7 +18,6 @@ using System.Linq; using Xunit; using Xunit.Abstractions; -using static Firely.Fhir.Validation.ValidationContext; namespace Firely.Fhir.Validation.Compilation.Tests { @@ -128,7 +127,7 @@ public void InjectMetaProfileTest() } }; - var context = BuildMinimalContext(_fixture.ValidateCodeService, _fixture.SchemaResolver); + var context = ValidationContext.BuildMinimalContext(_fixture.ValidateCodeService, _fixture.SchemaResolver); context.SelectMetaProfiles = metaCallback; var result = schema!.Validate(bundle.ToTypedElement(), context); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs index af4e2ba6..b79558b0 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs @@ -76,7 +76,7 @@ ResultReport validate(ITypedElement typedElement, string canonicalProfile) // should be removed from STU3/R4 once we get the new normative version // of FP up, which could do comparisons between quantities. // 2022-01-19 MS: added best practice constraint "dom-6" to be ignored, which checks if a resource has a narrative. - ExcludeFilter = a => a is FhirPathValidator fhirPathAssertion && constraintsToBeIgnored.Contains(fhirPathAssertion.Key) + ExcludeFilters = new Predicate[] { a => a is FhirPathValidator fhirPathAssertion && constraintsToBeIgnored.Contains(fhirPathAssertion.Key) } }; _stopWatch.Start(); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilderFixture.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilderFixture.cs index 10863361..6701ca14 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilderFixture.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilderFixture.cs @@ -14,11 +14,11 @@ namespace Firely.Fhir.Validation.Compilation.Tests { public class SchemaBuilderFixture { - public readonly IElementSchemaResolver SchemaResolver; + internal readonly IElementSchemaResolver SchemaResolver; public readonly FhirPathCompiler FpCompiler; public readonly ICodeValidationTerminologyService ValidateCodeService; public readonly IAsyncResourceResolver ResourceResolver; - public readonly SchemaBuilder Builder; + internal readonly SchemaBuilder Builder; public SchemaBuilderFixture() { @@ -40,7 +40,7 @@ public SchemaBuilderFixture() Builder = new SchemaBuilder(ResourceResolver, new[] { new StandardBuilders(ResourceResolver) }); } - public ValidationContext NewValidationContext() => + internal ValidationContext NewValidationContext() => new(SchemaResolver, ValidateCodeService) { FhirPathCompiler = FpCompiler }; } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs index 9dc4edc4..85f38468 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SchemaBuilders/TypeReferenceBuilderTests.cs @@ -13,7 +13,7 @@ namespace Firely.Fhir.Validation.Compilation.Tests { - public static class SchemaFluentAssertionsExtensions + internal static class SchemaFluentAssertionsExtensions { public static AndConstraint BeASchemaAssertionFor(this ObjectAssertions me, string uri) => me.BeOfType().Which diff --git a/test/Firely.Fhir.Validation.Tests/Impl/ReferencedInstanceValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/ReferencedInstanceValidatorTests.cs index de4b1d70..1a3a1df8 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/ReferencedInstanceValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/ReferencedInstanceValidatorTests.cs @@ -81,8 +81,9 @@ public static object createInstance(string reference) => [ReferencedInstanceValidatorTests] [DataTestMethod] - public void ValidateInstance(object instance, ReferencedInstanceValidator testee, bool success, string fragment) + public void ValidateInstance(object instance, object testeeo, bool success, string fragment) { + ReferencedInstanceValidator testee = (ReferencedInstanceValidator)testeeo; static ITypedElement? resolve(string url, string _) => url.StartsWith("http://example.com/hit") ? From 792b29ce5ce6b4229efe3ee5deb930322a12521a Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Tue, 28 Nov 2023 14:49:12 +0100 Subject: [PATCH 13/27] Adding the PublicApiAnalyzers --- ...ly.Fhir.Validation.Compilation.STU3.csproj | 3 +- .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 8 ++ .../Firely.Fhir.Validation.Compilation.csproj | 1 + .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 8 ++ .../Firely.Fhir.Validation.csproj | 1 + .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 102 ++++++++++++++++++ .../Schema/PathStack.cs | 2 +- .../Support/AggregationMode.cs | 2 +- .../Support}/IExternalReferenceResolver.cs | 0 .../Support/ReferenceVersionRules.cs | 2 +- .../Support/TypeNameMapper.cs | 2 +- .../Firely.Fhir.Validation.R4.csproj | 1 + .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 12 +++ .../Firely.Fhir.Validation.STU3.csproj | 10 +- .../PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 12 +++ .../Hl7.Fhir.Validation.Shared.projitems | 1 - 21 files changed, 161 insertions(+), 11 deletions(-) create mode 100644 src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Shipped.txt create mode 100644 src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Unshipped.txt create mode 100644 src/Firely.Fhir.Validation.Compilation/PublicAPI.Shipped.txt create mode 100644 src/Firely.Fhir.Validation.Compilation/PublicAPI.Unshipped.txt create mode 100644 src/Firely.Fhir.Validation/PublicAPI.Shipped.txt create mode 100644 src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt rename src/{Hl7.Fhir.Validation.Shared => Firely.Fhir.Validation/Support}/IExternalReferenceResolver.cs (100%) create mode 100644 src/Hl7.Fhir.Validation.R4/PublicAPI.Shipped.txt create mode 100644 src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt create mode 100644 src/Hl7.Fhir.Validation.STU3/PublicAPI.Shipped.txt create mode 100644 src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt diff --git a/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj b/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj index 7ce0de0b..745898f1 100644 --- a/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj +++ b/src/Firely.Fhir.Validation.Compilation.STU3/Firely.Fhir.Validation.Compilation.STU3.csproj @@ -15,10 +15,11 @@ + - + diff --git a/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Shipped.txt b/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..382aad42 --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation.STU3/PublicAPI.Unshipped.txt @@ -0,0 +1,8 @@ +#nullable enable +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.Nested.get -> Hl7.Fhir.Specification.Source.IAsyncResourceResolver! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByCanonicalUri(string! uri) -> Hl7.Fhir.Model.Resource? +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByCanonicalUriAsync(string! uri) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByUri(string! uri) -> Hl7.Fhir.Model.Resource? +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByUriAsync(string! uri) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.StructureDefinitionCorrectionsResolver(Hl7.Fhir.Specification.Source.ISyncOrAsyncResourceResolver! nested) -> void diff --git a/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj b/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj index 2377634b..0284df9c 100644 --- a/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj +++ b/src/Firely.Fhir.Validation.Compilation/Firely.Fhir.Validation.Compilation.csproj @@ -16,6 +16,7 @@ + diff --git a/src/Firely.Fhir.Validation.Compilation/PublicAPI.Shipped.txt b/src/Firely.Fhir.Validation.Compilation/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation.Compilation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation.Compilation/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..382aad42 --- /dev/null +++ b/src/Firely.Fhir.Validation.Compilation/PublicAPI.Unshipped.txt @@ -0,0 +1,8 @@ +#nullable enable +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.Nested.get -> Hl7.Fhir.Specification.Source.IAsyncResourceResolver! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByCanonicalUri(string! uri) -> Hl7.Fhir.Model.Resource? +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByCanonicalUriAsync(string! uri) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByUri(string! uri) -> Hl7.Fhir.Model.Resource? +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.ResolveByUriAsync(string! uri) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.Compilation.StructureDefinitionCorrectionsResolver.StructureDefinitionCorrectionsResolver(Hl7.Fhir.Specification.Source.ISyncOrAsyncResourceResolver! nested) -> void diff --git a/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj b/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj index ec2817db..5d81131c 100644 --- a/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj +++ b/src/Firely.Fhir.Validation/Firely.Fhir.Validation.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Firely.Fhir.Validation/PublicAPI.Shipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Firely.Fhir.Validation/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..ecd945a4 --- /dev/null +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -0,0 +1,102 @@ +#nullable enable +const Firely.Fhir.Validation.IssueAssertion.Pattern.INSTANCETYPE = "%INSTANCETYPE%" -> string! +const Firely.Fhir.Validation.IssueAssertion.Pattern.RESOURCEURL = "%RESOURCEURL%" -> string! +Firely.Fhir.Validation.Canonical +Firely.Fhir.Validation.Canonical.Anchor.get -> string? +Firely.Fhir.Validation.Canonical.Canonical(Firely.Fhir.Validation.Canonical! original) -> void +Firely.Fhir.Validation.Canonical.Canonical(string! original) -> void +Firely.Fhir.Validation.Canonical.Canonical(string? uri, string? version, string? anchor) -> void +Firely.Fhir.Validation.Canonical.Deconstruct(out string? uri, out string? version, out string? anchor) -> void +Firely.Fhir.Validation.Canonical.HasAnchor.get -> bool +Firely.Fhir.Validation.Canonical.HasVersion.get -> bool +Firely.Fhir.Validation.Canonical.IsAbsolute.get -> bool +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.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.ExternalReferenceResolver +Firely.Fhir.Validation.IAssertion +Firely.Fhir.Validation.IExternalReferenceResolver +Firely.Fhir.Validation.IExternalReferenceResolver.ResolveAsync(string! reference) -> System.Threading.Tasks.Task! +Firely.Fhir.Validation.IJsonSerializable +Firely.Fhir.Validation.IJsonSerializable.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.IssueAssertion +Firely.Fhir.Validation.IssueAssertion.AsResult(string! location) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.IssueAssertion.Equals(Firely.Fhir.Validation.IssueAssertion? other) -> bool +Firely.Fhir.Validation.IssueAssertion.IssueAssertion(Hl7.Fhir.Support.Issue! issue, string! message) -> void +Firely.Fhir.Validation.IssueAssertion.IssueAssertion(int issueNumber, string! message, Hl7.Fhir.Model.OperationOutcome.IssueSeverity severity, Hl7.Fhir.Model.OperationOutcome.IssueType? type = null) -> void +Firely.Fhir.Validation.IssueAssertion.IssueNumber.get -> int +Firely.Fhir.Validation.IssueAssertion.Location.get -> string? +Firely.Fhir.Validation.IssueAssertion.Message.get -> string! +Firely.Fhir.Validation.IssueAssertion.Message.set -> void +Firely.Fhir.Validation.IssueAssertion.Pattern +Firely.Fhir.Validation.IssueAssertion.Result.get -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.IssueAssertion.Severity.get -> Hl7.Fhir.Model.OperationOutcome.IssueSeverity +Firely.Fhir.Validation.IssueAssertion.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.IssueAssertion.Type.get -> Hl7.Fhir.Model.OperationOutcome.IssueType? +Firely.Fhir.Validation.MetaProfileSelector +Firely.Fhir.Validation.ResultReport +Firely.Fhir.Validation.ResultReport.Errors.get -> System.Collections.Generic.IReadOnlyCollection! +Firely.Fhir.Validation.ResultReport.Evidence.get -> System.Collections.Generic.IReadOnlyList! +Firely.Fhir.Validation.ResultReport.GetIssues(Hl7.Fhir.Model.OperationOutcome.IssueSeverity? severity = null) -> System.Collections.Generic.IReadOnlyCollection! +Firely.Fhir.Validation.ResultReport.IsSuccessful.get -> bool +Firely.Fhir.Validation.ResultReport.Result.get -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ResultReport.ResultReport(Firely.Fhir.Validation.ValidationResult result, params Firely.Fhir.Validation.IAssertion![]! evidence) -> void +Firely.Fhir.Validation.ResultReport.ResultReport(Firely.Fhir.Validation.ValidationResult result, System.Collections.Generic.IEnumerable! evidence) -> void +Firely.Fhir.Validation.ResultReport.Warnings.get -> System.Collections.Generic.IReadOnlyCollection! +Firely.Fhir.Validation.TerminologyServiceExceptionResult +Firely.Fhir.Validation.TerminologyServiceExceptionResult.Error = 1 -> Firely.Fhir.Validation.TerminologyServiceExceptionResult +Firely.Fhir.Validation.TerminologyServiceExceptionResult.Warning = 0 -> Firely.Fhir.Validation.TerminologyServiceExceptionResult +Firely.Fhir.Validation.TraceAssertion +Firely.Fhir.Validation.TraceAssertion.AsResult() -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.TraceAssertion.Equals(Firely.Fhir.Validation.TraceAssertion? other) -> bool +Firely.Fhir.Validation.TraceAssertion.FixedResult.get -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.TraceAssertion.Location.get -> string! +Firely.Fhir.Validation.TraceAssertion.Message.get -> string! +Firely.Fhir.Validation.TraceAssertion.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.TraceAssertion.TraceAssertion(string! location, string! message) -> void +Firely.Fhir.Validation.TypeNameMapper +Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.ValidateBestPracticesSeverity.Error = 1 -> Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.ValidateBestPracticesSeverity.Warning = 0 -> Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.ValidateCodeServiceFailureHandler +Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ValidationResult.Failure = 1 -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ValidationResult.Success = 0 -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ValidationResult.Undecided = 2 -> Firely.Fhir.Validation.ValidationResult +Firely.Fhir.Validation.ValidationResultExtensions +override Firely.Fhir.Validation.Canonical.Equals(object? obj) -> bool +override Firely.Fhir.Validation.Canonical.GetHashCode() -> int +override Firely.Fhir.Validation.Canonical.ToString() -> string! +override Firely.Fhir.Validation.IssueAssertion.Equals(object? obj) -> bool +override Firely.Fhir.Validation.IssueAssertion.GetHashCode() -> int +override Firely.Fhir.Validation.TraceAssertion.Equals(object? obj) -> bool +override Firely.Fhir.Validation.TraceAssertion.GetHashCode() -> int +static Firely.Fhir.Validation.Canonical.explicit operator string!(Firely.Fhir.Validation.Canonical! c) -> string! +static Firely.Fhir.Validation.Canonical.ForCoreType(string! type) -> Firely.Fhir.Validation.Canonical! +static Firely.Fhir.Validation.Canonical.implicit operator Firely.Fhir.Validation.Canonical!(string! s) -> Firely.Fhir.Validation.Canonical! +static Firely.Fhir.Validation.Canonical.operator !=(Firely.Fhir.Validation.Canonical? left, Firely.Fhir.Validation.Canonical? right) -> bool +static Firely.Fhir.Validation.Canonical.operator ==(Firely.Fhir.Validation.Canonical? left, Firely.Fhir.Validation.Canonical? right) -> bool +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.ResultReport.FromEvidence(System.Collections.Generic.IReadOnlyCollection! evidence) -> Firely.Fhir.Validation.ResultReport! +static Firely.Fhir.Validation.TraceAssertion.operator !=(Firely.Fhir.Validation.TraceAssertion? left, Firely.Fhir.Validation.TraceAssertion? right) -> bool +static Firely.Fhir.Validation.TraceAssertion.operator ==(Firely.Fhir.Validation.TraceAssertion? left, Firely.Fhir.Validation.TraceAssertion? right) -> bool +static Firely.Fhir.Validation.ValidationResultExtensions.Combine(this Firely.Fhir.Validation.ValidationResult a, Firely.Fhir.Validation.ValidationResult b) -> Firely.Fhir.Validation.ValidationResult +static readonly Firely.Fhir.Validation.ResultReport.FAILURE -> Firely.Fhir.Validation.ResultReport! +static readonly Firely.Fhir.Validation.ResultReport.SUCCESS -> Firely.Fhir.Validation.ResultReport! +static readonly Firely.Fhir.Validation.ResultReport.UNDECIDED -> Firely.Fhir.Validation.ResultReport! +virtual Firely.Fhir.Validation.Canonical.$() -> Firely.Fhir.Validation.Canonical! +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.ExtensionUrlFollower.Invoke(string! location, Firely.Fhir.Validation.Canonical? url) -> Firely.Fhir.Validation.ExtensionUrlHandling +virtual Firely.Fhir.Validation.ExternalReferenceResolver.Invoke(string! reference, string! location) -> Hl7.Fhir.ElementModel.ITypedElement? +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? +virtual Firely.Fhir.Validation.ValidateCodeServiceFailureHandler.Invoke(Hl7.Fhir.Specification.Terminology.ValidateCodeParameters! p, Hl7.Fhir.Rest.FhirOperationException! e) -> Firely.Fhir.Validation.TerminologyServiceExceptionResult \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Schema/PathStack.cs b/src/Firely.Fhir.Validation/Schema/PathStack.cs index dac5665b..cc628d9c 100644 --- a/src/Firely.Fhir.Validation/Schema/PathStack.cs +++ b/src/Firely.Fhir.Validation/Schema/PathStack.cs @@ -37,7 +37,7 @@ protected PathStack(PathStackEvent? current) /// /// A linked list of events that represent a path. /// - public abstract class PathStackEvent + internal abstract class PathStackEvent { /// /// Add a new event to the path. diff --git a/src/Firely.Fhir.Validation/Support/AggregationMode.cs b/src/Firely.Fhir.Validation/Support/AggregationMode.cs index bc471ecb..38e65cda 100644 --- a/src/Firely.Fhir.Validation/Support/AggregationMode.cs +++ b/src/Firely.Fhir.Validation/Support/AggregationMode.cs @@ -14,7 +14,7 @@ namespace Firely.Fhir.Validation /// (system: http://hl7.org/fhir/resource-aggregation-mode) /// [FhirEnumeration("AggregationMode")] - public enum AggregationMode + internal enum AggregationMode { /// /// The reference is a local reference to a contained resource. diff --git a/src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs b/src/Firely.Fhir.Validation/Support/IExternalReferenceResolver.cs similarity index 100% rename from src/Hl7.Fhir.Validation.Shared/IExternalReferenceResolver.cs rename to src/Firely.Fhir.Validation/Support/IExternalReferenceResolver.cs diff --git a/src/Firely.Fhir.Validation/Support/ReferenceVersionRules.cs b/src/Firely.Fhir.Validation/Support/ReferenceVersionRules.cs index 664c9925..0dc4f42d 100644 --- a/src/Firely.Fhir.Validation/Support/ReferenceVersionRules.cs +++ b/src/Firely.Fhir.Validation/Support/ReferenceVersionRules.cs @@ -14,7 +14,7 @@ namespace Firely.Fhir.Validation /// (system: http://hl7.org/fhir/reference-version-rules) /// [FhirEnumeration("ReferenceVersionRules")] - public enum ReferenceVersionRules + internal enum ReferenceVersionRules { /// /// The reference may be either version independent or version specific diff --git a/src/Firely.Fhir.Validation/Support/TypeNameMapper.cs b/src/Firely.Fhir.Validation/Support/TypeNameMapper.cs index 3572396a..262e22d7 100644 --- a/src/Firely.Fhir.Validation/Support/TypeNameMapper.cs +++ b/src/Firely.Fhir.Validation/Support/TypeNameMapper.cs @@ -16,7 +16,7 @@ namespace Firely.Fhir.Validation /// /// Extension methods for use on top of . /// - public static class TypeNameMapperExtensions + internal static class TypeNameMapperExtensions { /// /// Will invoke the , if set. If there is no mapper, or the mapper returns diff --git a/src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj b/src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj index 93948ea5..9fcb8d84 100644 --- a/src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj +++ b/src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj @@ -17,6 +17,7 @@ + \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.R4/PublicAPI.Shipped.txt b/src/Hl7.Fhir.Validation.R4/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Hl7.Fhir.Validation.R4/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt b/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..ec99d819 --- /dev/null +++ b/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt @@ -0,0 +1,12 @@ +#nullable enable +Firely.Fhir.Validation.Validator +Firely.Fhir.Validation.Validator.ConstraintBestPractices -> Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.Validator.ExtensionUrlFollower -> Firely.Fhir.Validation.ExtensionUrlFollower? +Firely.Fhir.Validation.Validator.MetaProfileSelector -> Firely.Fhir.Validation.MetaProfileSelector? +Firely.Fhir.Validation.Validator.SkipConstraintValidation.get -> bool +Firely.Fhir.Validation.Validator.SkipConstraintValidation.set -> void +Firely.Fhir.Validation.Validator.TypeNameMapper.get -> Firely.Fhir.Validation.TypeNameMapper? +Firely.Fhir.Validation.Validator.TypeNameMapper.set -> void +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string! profile) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string! profile) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver, Hl7.FhirPath.FhirPathCompiler? fhirPathCompiler = null) -> void \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj b/src/Hl7.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj index d8b55f30..801c47c9 100644 --- a/src/Hl7.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj +++ b/src/Hl7.Fhir.Validation.STU3/Firely.Fhir.Validation.STU3.csproj @@ -14,10 +14,10 @@ - - - + + + + - - + \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.STU3/PublicAPI.Shipped.txt b/src/Hl7.Fhir.Validation.STU3/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5f282702 --- /dev/null +++ b/src/Hl7.Fhir.Validation.STU3/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt b/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..ec99d819 --- /dev/null +++ b/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt @@ -0,0 +1,12 @@ +#nullable enable +Firely.Fhir.Validation.Validator +Firely.Fhir.Validation.Validator.ConstraintBestPractices -> Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.Validator.ExtensionUrlFollower -> Firely.Fhir.Validation.ExtensionUrlFollower? +Firely.Fhir.Validation.Validator.MetaProfileSelector -> Firely.Fhir.Validation.MetaProfileSelector? +Firely.Fhir.Validation.Validator.SkipConstraintValidation.get -> bool +Firely.Fhir.Validation.Validator.SkipConstraintValidation.set -> void +Firely.Fhir.Validation.Validator.TypeNameMapper.get -> Firely.Fhir.Validation.TypeNameMapper? +Firely.Fhir.Validation.Validator.TypeNameMapper.set -> void +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string! profile) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string! profile) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver, Hl7.FhirPath.FhirPathCompiler? fhirPathCompiler = null) -> void \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems b/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems index b00d7c1e..8879d918 100644 --- a/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems +++ b/src/Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems @@ -9,7 +9,6 @@ Hl7.Fhir.Validation.Shared - \ No newline at end of file From 5b6bc6116415d8d24f9dea115368a48b10742693 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Tue, 28 Nov 2023 16:26:26 +0100 Subject: [PATCH 14/27] Adding tests for the validator. --- Firely.Validator.API.sln | 9 --- .../Schema/ValidationContext.cs | 4 +- src/Hl7.Fhir.Validation.Shared/Validator.cs | 38 ++++++---- ...hir.Validation.Compilation.R4.Tests.csproj | 1 + ...r.Validation.Compilation.STU3.Tests.csproj | 1 + .../FakeExternalReferenceResolver.cs | 19 +++++ ...idation.Compilation.Tests.Shared.projitems | 2 + .../ValidatorHighLevelApiTests.cs | 73 +++++++++++++++++++ .../Hl7.Fhir.Validation.R5.Tests.csproj | 4 - 9 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 test/Firely.Fhir.Validation.Compilation.Tests.Shared/FakeExternalReferenceResolver.cs create mode 100644 test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs diff --git a/Firely.Validator.API.sln b/Firely.Validator.API.sln index 99c1cb5b..d26b2270 100644 --- a/Firely.Validator.API.sln +++ b/Firely.Validator.API.sln @@ -27,8 +27,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Comp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{1B02120E-C942-448D-A444-6F7EB1816C95}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hl7.Fhir.Validation.R5.Tests", "test\Hl7.Fhir.Validation.Tests\Hl7.Fhir.Validation.R5.Tests.csproj", "{533FADED-DC64-408D-AF93-E7AE8D9DAF56}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.R4", "src\Hl7.Fhir.Validation.R4\Firely.Fhir.Validation.R4.csproj", "{22520A59-7EBF-4740-A66C-3651742FAC0B}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Firely.Fhir.Validation.Compilation.Shared", "src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.shproj", "{7794C7D2-EA50-405D-A61A-23F8ECF4228B}" @@ -78,12 +76,6 @@ Global {1B02120E-C942-448D-A444-6F7EB1816C95}.FullDebug|Any CPU.Build.0 = Debug|Any CPU {1B02120E-C942-448D-A444-6F7EB1816C95}.Release|Any CPU.ActiveCfg = Release|Any CPU {1B02120E-C942-448D-A444-6F7EB1816C95}.Release|Any CPU.Build.0 = Release|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.Debug|Any CPU.Build.0 = Debug|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.FullDebug|Any CPU.Build.0 = Debug|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.Release|Any CPU.ActiveCfg = Release|Any CPU - {533FADED-DC64-408D-AF93-E7AE8D9DAF56}.Release|Any CPU.Build.0 = Release|Any CPU {22520A59-7EBF-4740-A66C-3651742FAC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {22520A59-7EBF-4740-A66C-3651742FAC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU {22520A59-7EBF-4740-A66C-3651742FAC0B}.FullDebug|Any CPU.ActiveCfg = Debug|Any CPU @@ -127,7 +119,6 @@ Global GlobalSection(NestedProjects) = preSolution {AC070065-936C-4266-9652-5B967C1C4E1F} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} {1B02120E-C942-448D-A444-6F7EB1816C95} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} - {533FADED-DC64-408D-AF93-E7AE8D9DAF56} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} {DFEFC934-BF53-4CD2-9C05-992062378F2A} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} {994A42C0-396B-48B6-81A0-4E622460ECAB} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} {F0695E43-0242-4D14-BFF7-4D0F36B0DAD8} = {7D9F5C18-2827-4975-8FBF-DD45758075AE} diff --git a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs index cecce6ca..be43b8ce 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs @@ -106,7 +106,7 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// A function to exclude the assertion in the validation or not. If the function is left empty (null) then all the /// assertions are processed in the validation. /// - public ICollection> ExcludeFilters = new[] { DEFAULT_EXCLUDE_FILTER }; + public ICollection> ExcludeFilters = new List> { DEFAULT_EXCLUDE_FILTER }; /// /// The default for , which will exclude FhirPath invariant dom-6 from triggering. @@ -139,7 +139,7 @@ public ResultReport TraceResult(Func p) => /// reference other schemas. When any of these required dependencies are accessed, a will /// be thrown. /// - public static ValidationContext BuildMinimalContext(ICodeValidationTerminologyService? validateCodeService = null, + internal static ValidationContext BuildMinimalContext(ICodeValidationTerminologyService? validateCodeService = null, IElementSchemaResolver? schemaResolver = null, FhirPathCompiler? fpCompiler = null) => new(schemaResolver ?? new NoopSchemaResolver(), validateCodeService ?? new NoopTerminologyService()) { diff --git a/src/Hl7.Fhir.Validation.Shared/Validator.cs b/src/Hl7.Fhir.Validation.Shared/Validator.cs index 6a77dbd3..291f61b2 100644 --- a/src/Hl7.Fhir.Validation.Shared/Validator.cs +++ b/src/Hl7.Fhir.Validation.Shared/Validator.cs @@ -34,12 +34,18 @@ public Validator( IExternalReferenceResolver? referenceResolver, FhirPathCompiler? fhirPathCompiler = null) { - var elementSchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(resourceResolver); + _elementSchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(resourceResolver); + _terminologyService = terminologyService; + _referenceResolver = referenceResolver; + _fhirPathCompiler = fhirPathCompiler; + } - _validationContext = new ValidationContext(elementSchemaResolver, terminologyService) + private ValidationContext buildContext() + { + var validationContext = new ValidationContext(_elementSchemaResolver, _terminologyService) { - FhirPathCompiler = fhirPathCompiler, - ResolveExternalReference = referenceResolver is not null ? resolve : null, + FhirPathCompiler = _fhirPathCompiler, + ResolveExternalReference = _referenceResolver is not null ? resolve : null, ConstraintBestPractices = ConstraintBestPractices, SelectMetaProfiles = MetaProfileSelector, FollowExtensionUrl = ExtensionUrlFollower, @@ -47,11 +53,13 @@ public Validator( }; if (SkipConstraintValidation) - _validationContext.ExcludeFilters.Add(ass => ass is FhirPathValidator); + validationContext.ExcludeFilters.Add(ass => ass is FhirPathValidator); + + return validationContext; ITypedElement? resolve(string reference, string location) { - var r = TaskHelper.Await(() => referenceResolver.ResolveAsync(reference)); + var r = TaskHelper.Await(() => _referenceResolver.ResolveAsync(reference)); return toTypedElement(r); } } @@ -65,7 +73,10 @@ public Validator( _ => throw new ArgumentException("Reference resolver must return either a Resource or ElementNode.") }; - private readonly ValidationContext _validationContext; + private readonly IElementSchemaResolver _elementSchemaResolver; + private readonly ICodeValidationTerminologyService _terminologyService; + private readonly IExternalReferenceResolver? _referenceResolver; + private readonly FhirPathCompiler? _fhirPathCompiler; /// /// Determines how to deal with failures of FhirPath constraints marked as "best practice". Default is . @@ -105,20 +116,19 @@ public Validator( /// Validates an instance against a profile. /// /// A report containing the issues found during validation. - public ResultReport Validate(Resource instance, string profile) - { - var validator = new SchemaReferenceValidator(profile); - return validator.Validate(instance.ToTypedElement(ModelInfo.ModelInspector).AsScopedNode(), _validationContext); - } + public ResultReport Validate(Resource instance, string profile) => Validate(instance.ToTypedElement(ModelInfo.ModelInspector).AsScopedNode(), profile); /// /// Validates an instance against a profile. /// /// A report containing the issues found during validation. - public ResultReport Validate(ElementNode instance, string profile) + public ResultReport Validate(ElementNode instance, string profile) => Validate(instance.AsScopedNode(), profile); + + internal ResultReport Validate(IScopedNode sn, string profile) { var validator = new SchemaReferenceValidator(profile); - return validator.Validate(instance.AsScopedNode(), _validationContext); + var ctx = buildContext(); + return validator.Validate(sn, ctx); } } } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.R4/Firely.Fhir.Validation.Compilation.R4.Tests.csproj b/test/Firely.Fhir.Validation.Compilation.Tests.R4/Firely.Fhir.Validation.Compilation.R4.Tests.csproj index 0de44997..0beaab06 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.R4/Firely.Fhir.Validation.Compilation.R4.Tests.csproj +++ b/test/Firely.Fhir.Validation.Compilation.Tests.R4/Firely.Fhir.Validation.Compilation.R4.Tests.csproj @@ -23,5 +23,6 @@ + diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj index 33fa3d48..2b7e1bd9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj @@ -22,5 +22,6 @@ + diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FakeExternalReferenceResolver.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FakeExternalReferenceResolver.cs new file mode 100644 index 00000000..5edcef48 --- /dev/null +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FakeExternalReferenceResolver.cs @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + + +using Hl7.Fhir.Model; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Firely.Fhir.Validation.Tests +{ + internal class FakeExternalReferenceResolver : Dictionary, IExternalReferenceResolver + { + public Task ResolveAsync(string reference) => + System.Threading.Tasks.Task.FromResult(this.TryGetValue(reference, out var resource) ? (object)resource : null); + } +} \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems index e472bdf7..53f70726 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems @@ -1787,6 +1787,7 @@ + @@ -1805,6 +1806,7 @@ + diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs new file mode 100644 index 00000000..c7d51e75 --- /dev/null +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2022, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + +using Firely.Fhir.Validation.Compilation.Tests; +using FluentAssertions; +using Hl7.Fhir.Model; +using Hl7.Fhir.Support; +using Xunit; + +// Until we have Marco's IScopedNodeOnPoco adapter, I cannot write R5 tests using just the "shared" R4+ validator. +#if !R5 + +namespace Firely.Fhir.Validation.Tests +{ + [Trait("Category", "Validation")] + public class ValidatorHighLevelApiTests : IClassFixture + { + internal SchemaBuilderFixture _fixture; + + public ValidatorHighLevelApiTests(SchemaBuilderFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ValidatesAndReportsError() + { + var p = new Patient() { Deceased = new FhirString("wrong") }; + var validator = new Validator(_fixture.ValidateCodeService, _fixture.ResourceResolver, null); + var result = validator.Validate(p, Canonical.ForCoreType("Patient").ToString()); + + result.Errors.Should().ContainSingle().Which.IssueNumber.Should().Be(Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code); + result.IsSuccessful.Should().BeFalse(); + } + + [Fact] + public void NoReferenceResolution() + { + // First, without a resolver, we do not follow references and thus do not validate the referenced resource. + var p = new Patient() { ManagingOrganization = new ResourceReference("http://example.com/orgA") }; + var validator = new Validator(_fixture.ValidateCodeService, _fixture.ResourceResolver, null); + validator.Validate(p, Canonical.ForCoreType("Patient").ToString()).IsSuccessful.Should().BeTrue(); + + // Now with reference resolution + var or = new FakeExternalReferenceResolver() { ["http://example.com/orgA"] = new Organization() }; + validator = new Validator(_fixture.ValidateCodeService, _fixture.ResourceResolver, or); + var result = validator.Validate(p, Canonical.ForCoreType("Patient").ToString()); + + // Organization should fail constraint org-1 + result.Errors.Should().ContainSingle().Which.IssueNumber.Should().Be(Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT.Code); + } + + [Fact] + public void SkipConstraintValidation() + { + var o = new Organization(); + var validator = new Validator(_fixture.ValidateCodeService, _fixture.ResourceResolver, null); + + // validate with constraint validation, it should fail on org-1 + var result = validator.Validate(o, Canonical.ForCoreType("Organization").ToString()); + result.Errors.Should().ContainSingle().Which.IssueNumber.Should().Be(Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT.Code); + + // now, skip constraint validation + validator.SkipConstraintValidation = true; + result = validator.Validate(o, Canonical.ForCoreType("Organization").ToString()); + result.IsSuccessful.Should().BeTrue(); + } + } +} +#endif \ No newline at end of file diff --git a/test/Hl7.Fhir.Validation.Tests/Hl7.Fhir.Validation.R5.Tests.csproj b/test/Hl7.Fhir.Validation.Tests/Hl7.Fhir.Validation.R5.Tests.csproj index 1704f62d..778077cb 100644 --- a/test/Hl7.Fhir.Validation.Tests/Hl7.Fhir.Validation.R5.Tests.csproj +++ b/test/Hl7.Fhir.Validation.Tests/Hl7.Fhir.Validation.R5.Tests.csproj @@ -12,10 +12,6 @@ - - - - PreserveNewest From 11d170c8e1f4c5d2604909f1cfd1589b6efd2a65 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Wed, 29 Nov 2023 14:58:33 +0100 Subject: [PATCH 15/27] Rename ResultReport.FromEvidence to Combine, since it has nothing to do with Evidence anymore. --- firely-validator-api.props | 2 +- .../ElementDefinitionValidator.cs | 2 +- .../StructureDefinitionValidator.cs | 2 +- src/Firely.Fhir.Validation/Impl/AllValidator.cs | 4 ++-- src/Firely.Fhir.Validation/Impl/AnyValidator.cs | 2 +- .../Impl/ChildrenValidator.cs | 2 +- src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs | 2 +- src/Firely.Fhir.Validation/Impl/ElementSchema.cs | 8 ++++---- .../Impl/ExtensionSchema.cs | 2 +- .../Impl/ReferencedInstanceValidator.cs | 2 +- src/Firely.Fhir.Validation/Impl/ResourceSchema.cs | 4 ++-- src/Firely.Fhir.Validation/Impl/ResultReport.cs | 15 ++++++++------- src/Firely.Fhir.Validation/Impl/SliceValidator.cs | 2 +- .../PublicAPI.Unshipped.txt | 4 +++- .../AssertionToOperationOutcomeExtensions.cs | 6 +++--- .../Schema/AssertionValidators.cs | 2 +- .../Support/FhirSchemaGroupAnalyzer.cs | 2 +- .../Support/InMemoryExternalReferenceResolver.cs | 2 +- .../FhirTests/WipValidator.cs | 2 +- ....Validation.Compilation.Tests.Shared.projitems | 1 - .../ValidatorHighLevelApiTests.cs | 2 +- 21 files changed, 36 insertions(+), 34 deletions(-) rename test/Firely.Fhir.Validation.Compilation.Tests.Shared/FakeExternalReferenceResolver.cs => src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs (82%) diff --git a/firely-validator-api.props b/firely-validator-api.props index 92e436f4..a7da46ee 100644 --- a/firely-validator-api.props +++ b/firely-validator-api.props @@ -2,7 +2,7 @@ - 1.1.1 + 1.2.0-alpha1 Firely Firely (https://fire.ly) Copyright 2015-2022 Firely diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs index 046891b2..69fcfadf 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs @@ -25,7 +25,7 @@ public ResultReport Validate(IScopedNode input, ValidationContext vc, Validation //this can be expanded with other validate functionality evidence.AddRange(validateTypeCompatibilityOfValues(input, state)); - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); } /// diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs index a6817899..2af61627 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs @@ -23,7 +23,7 @@ public ResultReport Validate(IScopedNode input, ValidationContext vc, Validation //this can be expanded with other validate functionality var evidence = validateInvariantUniqueness(input, state); - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); } /// diff --git a/src/Firely.Fhir.Validation/Impl/AllValidator.cs b/src/Firely.Fhir.Validation/Impl/AllValidator.cs index a9076c18..9c4eebee 100644 --- a/src/Firely.Fhir.Validation/Impl/AllValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/AllValidator.cs @@ -80,11 +80,11 @@ public ResultReport Validate( evidence.Add(result); if (!result.IsSuccessful) break; } - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); } else return - ResultReport.FromEvidence(Members + ResultReport.Combine(Members .Select(ma => ma.ValidateMany(input, vc, state)).ToList()); } diff --git a/src/Firely.Fhir.Validation/Impl/AnyValidator.cs b/src/Firely.Fhir.Validation/Impl/AnyValidator.cs index 9faf4db0..154de6f1 100644 --- a/src/Firely.Fhir.Validation/Impl/AnyValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/AnyValidator.cs @@ -78,7 +78,7 @@ public ResultReport Validate( if (SummaryError is not null) result.Insert(0, SummaryError.ValidateMany(input, vc, state)); - return ResultReport.FromEvidence(result); + return ResultReport.Combine(result); } /// diff --git a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs index 67ab9e23..496aa7b3 100644 --- a/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ChildrenValidator.cs @@ -106,7 +106,7 @@ public ResultReport Validate(IScopedNode input, ValidationContext vc, Validation .UpdateInstanceLocation(ip => ip.ToChild(m.ChildName, choiceElement(m))) ))); - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); static string? choiceElement(Match m) => m.ChildName.EndsWith("[x]") ? m.InstanceElements?.FirstOrDefault().InstanceType : null; } diff --git a/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs b/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs index c8682be7..208e167e 100644 --- a/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/DatatypeSchema.cs @@ -40,7 +40,7 @@ public override ResultReport Validate(IEnumerable input, Validation // 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)))); - return ResultReport.FromEvidence(results.ToList()); + return ResultReport.Combine(results.ToList()); } /// diff --git a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs index 4dea7a46..fea7567f 100644 --- a/src/Firely.Fhir.Validation/Impl/ElementSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ElementSchema.cs @@ -86,13 +86,13 @@ public virtual ResultReport Validate( else { var validationResults = CardinalityValidators.Select(cv => cv.Validate(nothing, vc, state)).ToList(); - return ResultReport.FromEvidence(validationResults); + return ResultReport.Combine(validationResults); } } var members = Members.Where(vc.Filter); var subresult = members.Select(ma => ma.ValidateMany(input, vc, state)); - return ResultReport.FromEvidence(subresult.ToList()); + return ResultReport.Combine(subresult.ToList()); } /// @@ -102,13 +102,13 @@ public virtual ResultReport Validate(IScopedNode input, ValidationContext vc, Va if (ShortcutMembers.Any()) { var subResult = ShortcutMembers.Where(vc.Filter).Select(ma => ma.ValidateOne(input, vc, state)); - var report = ResultReport.FromEvidence(subResult.ToList()); + var report = ResultReport.Combine(subResult.ToList()); if (!report.IsSuccessful) return report; } var members = Members.Where(vc.Filter); var subresult = members.Select(ma => ma.ValidateOne(input, vc, state)); - return ResultReport.FromEvidence(subresult.ToList()); + return ResultReport.Combine(subresult.ToList()); } /// diff --git a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs index fcbd95f7..fa3174cf 100644 --- a/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ExtensionSchema.cs @@ -116,7 +116,7 @@ public override ResultReport Validate(IEnumerable input, Validation } } - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); static ExtensionUrlFollower callback(ExtensionUrlFollower? follower) => follower ?? ((l, c) => ExtensionUrlHandling.WarnIfMissing); diff --git a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs index 2b71b933..3f379d3d 100644 --- a/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/ReferencedInstanceValidator.cs @@ -97,7 +97,7 @@ public ResultReport Validate(IScopedNode input, ValidationContext vc, Validation _ => validateReferencedResource(reference, vc, resolution, state) }; - return ResultReport.FromEvidence(evidence.Append(referenceResolutionReport).ToList()); + return ResultReport.Combine(evidence.Append(referenceResolutionReport).ToList()); } else return ResultReport.SUCCESS; diff --git a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs index 34b12cce..ebb67a00 100644 --- a/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs +++ b/src/Firely.Fhir.Validation/Impl/ResourceSchema.cs @@ -61,7 +61,7 @@ public override ResultReport Validate(IEnumerable input, Validation // 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)))); - return ResultReport.FromEvidence(results.ToList()); + return ResultReport.Combine(results.ToList()); } /// @@ -104,7 +104,7 @@ public override ResultReport Validate(IScopedNode input, ValidationContext vc, V // 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(); - return ResultReport.FromEvidence(fetchErrors.Append(consistencyReport).Concat(validationResult).Concat(validationResultOther).ToArray()); + return ResultReport.Combine(fetchErrors.Append(consistencyReport).Concat(validationResult).Concat(validationResultOther).ToArray()); } /// diff --git a/src/Firely.Fhir.Validation/Impl/ResultReport.cs b/src/Firely.Fhir.Validation/Impl/ResultReport.cs index fa4c6376..f6d2a8fc 100644 --- a/src/Firely.Fhir.Validation/Impl/ResultReport.cs +++ b/src/Firely.Fhir.Validation/Impl/ResultReport.cs @@ -47,16 +47,17 @@ public class ResultReport public IReadOnlyList Evidence { get; } /// - /// Creates a new ResultAssertion where the result is derived from the evidence. + /// Creates a new where the result is derived from multiple others + /// reports. /// - /// The evidence is included in the returned result and the total result - /// for the the ResultAssertion is calculated to be the weakest of the evidence. - public static ResultReport FromEvidence(IReadOnlyCollection evidence) + /// All evidence is combined in the returned result and the + /// for the the combined report is determined to be the weakest result of the combined reports. + public static ResultReport Combine(IReadOnlyCollection reports) { - if (evidence.Count == 0) return SUCCESS; - if (evidence.Count == 1) return evidence.Single(); + if (reports.Count == 0) return SUCCESS; + if (reports.Count == 1) return reports.Single(); - var usefulEvidence = evidence.Where(e => !isSuccessWithoutDetails(e)).ToList(); + var usefulEvidence = reports.Where(e => !isSuccessWithoutDetails(e)).ToList(); if (usefulEvidence.Count == 1) return usefulEvidence.Single(); diff --git a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs index cfa5e8f2..234d821f 100644 --- a/src/Firely.Fhir.Validation/Impl/SliceValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/SliceValidator.cs @@ -192,7 +192,7 @@ public ResultReport Validate(IEnumerable input, ValidationContext v evidence.AddRange(buckets.Validate(vc, state)); - return ResultReport.FromEvidence(evidence); + return ResultReport.Combine(evidence); } /// diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index ecd945a4..ba9e22ef 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -1,6 +1,7 @@ #nullable enable const Firely.Fhir.Validation.IssueAssertion.Pattern.INSTANCETYPE = "%INSTANCETYPE%" -> string! const Firely.Fhir.Validation.IssueAssertion.Pattern.RESOURCEURL = "%RESOURCEURL%" -> string! +Firely.Fhir.Validation.AssertionToOperationOutcomeExtensions Firely.Fhir.Validation.Canonical Firely.Fhir.Validation.Canonical.Anchor.get -> string? Firely.Fhir.Validation.Canonical.Canonical(Firely.Fhir.Validation.Canonical! original) -> void @@ -77,6 +78,7 @@ override Firely.Fhir.Validation.IssueAssertion.Equals(object? obj) -> bool override Firely.Fhir.Validation.IssueAssertion.GetHashCode() -> int override Firely.Fhir.Validation.TraceAssertion.Equals(object? obj) -> bool override Firely.Fhir.Validation.TraceAssertion.GetHashCode() -> int +static Firely.Fhir.Validation.AssertionToOperationOutcomeExtensions.ToOperationOutcome(this Firely.Fhir.Validation.ResultReport! result) -> Hl7.Fhir.Model.OperationOutcome! static Firely.Fhir.Validation.Canonical.explicit operator string!(Firely.Fhir.Validation.Canonical! c) -> string! static Firely.Fhir.Validation.Canonical.ForCoreType(string! type) -> Firely.Fhir.Validation.Canonical! static Firely.Fhir.Validation.Canonical.implicit operator Firely.Fhir.Validation.Canonical!(string! s) -> Firely.Fhir.Validation.Canonical! @@ -84,7 +86,7 @@ static Firely.Fhir.Validation.Canonical.operator !=(Firely.Fhir.Validation.Canon static Firely.Fhir.Validation.Canonical.operator ==(Firely.Fhir.Validation.Canonical? left, Firely.Fhir.Validation.Canonical? right) -> bool 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.ResultReport.FromEvidence(System.Collections.Generic.IReadOnlyCollection! evidence) -> Firely.Fhir.Validation.ResultReport! +static Firely.Fhir.Validation.ResultReport.Combine(System.Collections.Generic.IReadOnlyCollection! reports) -> Firely.Fhir.Validation.ResultReport! static Firely.Fhir.Validation.TraceAssertion.operator !=(Firely.Fhir.Validation.TraceAssertion? left, Firely.Fhir.Validation.TraceAssertion? right) -> bool static Firely.Fhir.Validation.TraceAssertion.operator ==(Firely.Fhir.Validation.TraceAssertion? left, Firely.Fhir.Validation.TraceAssertion? right) -> bool static Firely.Fhir.Validation.ValidationResultExtensions.Combine(this Firely.Fhir.Validation.ValidationResult a, Firely.Fhir.Validation.ValidationResult b) -> Firely.Fhir.Validation.ValidationResult diff --git a/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs b/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs index 8e13356b..ffdb1701 100644 --- a/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs +++ b/src/Firely.Fhir.Validation/Schema/AssertionToOperationOutcomeExtensions.cs @@ -14,7 +14,7 @@ namespace Firely.Fhir.Validation /// /// Extension methods to interoperate with FHIR OperationOutcome /// - internal static class AssertionToOperationOutcomeExtensions + public static class AssertionToOperationOutcomeExtensions { /// /// Build an OperationOutcome from Assertion @@ -50,7 +50,7 @@ item.DefinitionPath is not null && item.DefinitionPath.HasDefinitionChoiceInform /// Removes duplicate issues from the . This may happen if an instance is validated against /// profiles that overlap (or where one profile is a base of the other). /// - public static ResultReport RemoveDuplicateEvidence(this ResultReport report) + internal static ResultReport RemoveDuplicateEvidence(this ResultReport report) { var issues = report.Evidence.Distinct().ToList(); // Those assertions for which equivalence is relevant will have implemented IEqualityComparer return new ResultReport(report.Result, issues); @@ -62,7 +62,7 @@ public static ResultReport RemoveDuplicateEvidence(this ResultReport report) /// /// /// - public static ResultReport CleanUp(this ResultReport report) + internal static ResultReport CleanUp(this ResultReport report) { return report.addSliceContextToErrorMessages() .RemoveDuplicateEvidence(); diff --git a/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs b/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs index 9ccbe9fc..695b61ef 100644 --- a/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs +++ b/src/Firely.Fhir.Validation/Schema/AssertionValidators.cs @@ -70,7 +70,7 @@ static ResultReport repeat(IValidatable assertion, IEnumerable inpu { Count: 0 } => ResultReport.SUCCESS, { Count: 1 } when input.Single() is ValueElementNode ve => assertion.Validate(ve, vc, state), // no index for ValueElementNode { Count: 1 } => assertion.Validate(input.Single(), vc, state.UpdateInstanceLocation(vs => vs.ToIndex(0))), - _ => ResultReport.FromEvidence(input.Select((ma, i) => assertion.Validate(ma, vc, state.UpdateInstanceLocation(vs => vs.ToIndex(i)))).ToList()) + _ => ResultReport.Combine(input.Select((ma, i) => assertion.Validate(ma, vc, state.UpdateInstanceLocation(vs => vs.ToIndex(i)))).ToList()) }; } } diff --git a/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs b/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs index d7fd1774..aefe7fda 100644 --- a/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs +++ b/src/Firely.Fhir.Validation/Support/FhirSchemaGroupAnalyzer.cs @@ -105,7 +105,7 @@ public static ResultReport ValidateConsistency(FhirSchema? actualType, Canonical } - return ResultReport.FromEvidence(issues); + return ResultReport.Combine(issues); } /// diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FakeExternalReferenceResolver.cs b/src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs similarity index 82% rename from test/Firely.Fhir.Validation.Compilation.Tests.Shared/FakeExternalReferenceResolver.cs rename to src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs index 5edcef48..5d693b0e 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FakeExternalReferenceResolver.cs +++ b/src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs @@ -11,7 +11,7 @@ namespace Firely.Fhir.Validation.Tests { - internal class FakeExternalReferenceResolver : Dictionary, IExternalReferenceResolver + internal class InMemoryExternalReferenceResolver : Dictionary, IExternalReferenceResolver { public Task ResolveAsync(string reference) => System.Threading.Tasks.Task.FromResult(this.TryGetValue(reference, out var resource) ? (object)resource : null); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs index b79558b0..f9e173a9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/FhirTests/WipValidator.cs @@ -56,7 +56,7 @@ public OperationOutcome Validate(ITypedElement instance, IResourceResolver? reso result.Add(validate(instance, profileUri)); } - outcome.Add(ResultReport.FromEvidence(result) + outcome.Add(ResultReport.Combine(result) .CleanUp() .ToOperationOutcome()); return outcome; diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems index 53f70726..ca3fdad2 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems @@ -1787,7 +1787,6 @@ - diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs index c7d51e75..36b96eef 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs @@ -45,7 +45,7 @@ public void NoReferenceResolution() validator.Validate(p, Canonical.ForCoreType("Patient").ToString()).IsSuccessful.Should().BeTrue(); // Now with reference resolution - var or = new FakeExternalReferenceResolver() { ["http://example.com/orgA"] = new Organization() }; + var or = new InMemoryExternalReferenceResolver() { ["http://example.com/orgA"] = new Organization() }; validator = new Validator(_fixture.ValidateCodeService, _fixture.ResourceResolver, or); var result = validator.Validate(p, Canonical.ForCoreType("Patient").ToString()); From 00aef7545e73bd6a9bef31864a9c3ca1a3a524f8 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 29 Nov 2023 17:30:30 +0100 Subject: [PATCH 16/27] - Use IScopedNode in FixedValidator and PatternValidator - Implemented Base -> IScopedNode --- .../SchemaBuilders/FixedBuilder.cs | 4 +- .../SchemaBuilders/PatternBuilder.cs | 4 +- .../ElementDefinitionValidator.cs | 2 +- .../StructureDefinitionValidator.cs | 4 +- .../Impl/FixedValidator.cs | 14 +-- .../Impl/PatternValidator.cs | 10 +- .../Support/ScopedNodeExtensions.cs | 107 ++++++++++++++++++ ...idation.Compilation.Tests.Shared.projitems | 1 + .../SlicingSchemaConverterTests.cs | 13 +-- .../Support/ScopedNodeOnDictionaryTests.cs | 73 ++++++++++++ .../Impl/FixedValidatorTests.cs | 75 ++++++------ .../Impl/PatternValidatorTests.cs | 28 ++--- .../Impl/SchemaReferenceValidatorTests.cs | 5 +- .../Impl/SliceValidatorTests.cs | 5 +- .../Impl/TestSerialization.cs | 14 ++- .../Support/ElementNodeAdapter.cs | 2 +- 16 files changed, 273 insertions(+), 88 deletions(-) create mode 100644 src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs create mode 100644 test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FixedBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FixedBuilder.cs index 23fcfb17..6d71730a 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FixedBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/FixedBuilder.cs @@ -4,8 +4,6 @@ * via any medium is strictly prohibited. */ -using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Introspection; using Hl7.Fhir.Specification.Navigation; using System.Collections.Generic; @@ -25,7 +23,7 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv var def = nav.Current; if (def.Fixed is not null) - yield return new FixedValidator(def.Fixed.ToTypedElement(ModelInspector.Base)); + yield return new FixedValidator(def.Fixed); } } } diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/PatternBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/PatternBuilder.cs index 24df8106..0f2f863b 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/PatternBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/PatternBuilder.cs @@ -4,8 +4,6 @@ * via any medium is strictly prohibited. */ -using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Introspection; using Hl7.Fhir.Specification.Navigation; using System.Collections.Generic; @@ -25,7 +23,7 @@ public IEnumerable Build(ElementDefinitionNavigator nav, ElementConv var def = nav.Current; if (def.Pattern is not null) - yield return new PatternValidator(def.Pattern.ToTypedElement(ModelInspector.Base)); + yield return new PatternValidator(def.Pattern); } } } diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs index 1c7f3d09..e049710f 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/ElementDefinitionValidator.cs @@ -71,7 +71,7 @@ private static IEnumerable getTypeNames(IScopedNode input) return typeComponents.SelectMany(t => t.Children("code") .TakeWhile(c => c.Value?.ToString() is not null)) - .Select(c => c.Value.ToString()); + .Select(c => c.Value.ToString()!); } private static IEnumerable validateType(IEnumerable valueTypes, string propertyName, IEnumerable typeNames, IScopedNode input, ValidationState state) diff --git a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs index 10e70594..6be30288 100644 --- a/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs +++ b/src/Firely.Fhir.Validation/EnterpriseValidators/StructureDefinitionValidator.cs @@ -51,9 +51,9 @@ private static List validateInvariantUniqueness(IEnumerable PathsPerInvariantKey = elements .SelectMany(e => e.Children("constraint") .Select(c => (Key: c.Children("key") - .Single().Value.ToString(), + .Single().Value.ToString()!, Path: e.Children("path") - .Single().Value.ToString()))); + .Single().Value.ToString()!))); IEnumerable<(string Key, IEnumerable Paths)> PathsPerDuplicateInvariantKey = PathsPerInvariantKey.GroupBy(pair => pair.Key) .Select(group => (Key: group.Key, Paths: group.Select(pair => pair.Path) // select all paths, per invariant key diff --git a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs index 4bb85fdd..ce4b488e 100644 --- a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs @@ -5,7 +5,7 @@ */ using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Serialization; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Newtonsoft.Json.Linq; using System; @@ -24,12 +24,12 @@ public class FixedValidator : IValidatable /// The fixed value to compare an instance against. /// [DataMember] - public ITypedElement FixedValue { get; private set; } + public IScopedNode FixedValue { get; private set; } /// /// Initializes a new FixedValidator given the fixed value. /// - public FixedValidator(ITypedElement fixedValue) + public FixedValidator(IScopedNode fixedValue) { FixedValue = fixedValue ?? throw new ArgumentNullException(nameof(fixedValue)); } @@ -40,21 +40,21 @@ public FixedValidator(ITypedElement fixedValue) /// The .NET primitive will be turned into a based /// fixed value using , so this constructor /// supports any conversion done there. - public FixedValidator(object fixedValue) : this(ElementNode.ForPrimitive(fixedValue)) { } + public FixedValidator(DataType fixedValue) : this(fixedValue.ToScopedNode()) { } /// public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { - if (!input.IsExactlyEqualTo(FixedValue.AsScopedNode(), ignoreOrder: true)) + if (!input.IsExactlyEqualTo(FixedValue, ignoreOrder: true)) { return new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, - $"Value '{displayValue(input.AsTypedElement())}' is not exactly equal to fixed value '{displayValue(FixedValue)}'") + $"Value '{displayValue(input)}' is not exactly equal to fixed value '{displayValue(FixedValue)}'") .AsResult(s); } return ResultReport.SUCCESS; - static string displayValue(ITypedElement te) => + static string displayValue(IScopedNode te) => te.Children().Any() ? te.ToJson() : te.Value.ToString()!; } diff --git a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs index 0721af01..1143abc7 100644 --- a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs @@ -5,7 +5,7 @@ */ using Hl7.Fhir.ElementModel; -using Hl7.Fhir.Serialization; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Newtonsoft.Json.Linq; using System; @@ -27,12 +27,12 @@ public class PatternValidator : IValidatable /// The pattern the instance will be validated against. /// [DataMember] - public ITypedElement PatternValue { get; private set; } + public IScopedNode PatternValue { get; private set; } /// /// Initializes a new PatternValidator given a pattern. /// - public PatternValidator(ITypedElement patternValue) + public PatternValidator(IScopedNode patternValue) { PatternValue = patternValue ?? throw new ArgumentNullException(nameof(patternValue)); } @@ -43,12 +43,12 @@ public PatternValidator(ITypedElement patternValue) /// The .NET primitive will be turned into a based /// pattern using , so this constructor /// supports any conversion done there. - public PatternValidator(object patternPrimitive) : this(ElementNode.ForPrimitive(patternPrimitive)) { } + public PatternValidator(DataType patternPrimitive) : this(patternPrimitive.ToScopedNode()) { } /// public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { - var result = input.Matches(PatternValue.AsScopedNode()) + var result = input.Matches(PatternValue) ? ResultReport.SUCCESS : new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, $"Value does not match pattern '{PatternValue.ToJson()}") .AsResult(s); diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs new file mode 100644 index 00000000..0e572c37 --- /dev/null +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs @@ -0,0 +1,107 @@ +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Introspection; +using Hl7.Fhir.Model; +using Hl7.Fhir.Specification; +using Newtonsoft.Json.Linq; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace Firely.Fhir.Validation +{ + /// + /// Helper methods to convert between and + /// + public static class ScopedNodeExtensions + { + /// + /// + /// + /// + /// + /// + public static IScopedNode ToScopedNode(this IReadOnlyDictionary node, ModelInspector? inspector = null) + { + inspector ??= ModelInspector.ForAssembly(node.GetType().Assembly); + return new ScopedNodeOnDictionary(inspector!, node.GetType().Name, node); + } + + public static string ToJson(this IScopedNode instance) => string.Empty; // TODO + + public static JObject ToJObject(this IScopedNode instance) => new(); // TODO + + public static JToken ToPropValue(this IScopedNode e) => e.Value is not null ? new JValue(e.Value) : e.ToJObject(); + } + + internal class ScopedNodeOnDictionary : IScopedNode + { + private readonly IReadOnlyDictionary _wrapped; + private readonly IScopedNode? _parentNode; + private readonly ModelInspector _inspector; + private readonly ClassMapping? _myClassMapping; + private readonly string _name; + + public ScopedNodeOnDictionary(ModelInspector inspector, string rootName, IReadOnlyDictionary wrapped, IScopedNode? parentNode = null) + { + (_wrapped, _parentNode, _inspector, _name) = (wrapped, parentNode, inspector, rootName); + _myClassMapping = _inspector.FindOrImportClassMapping(_wrapped.GetType()); + InstanceType = (_myClassMapping as IStructureDefinitionSummary)?.TypeName; + } + + public IScopedNode? Parent => _parentNode; + + public string Name => _name; + +#pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). + public string? InstanceType { get; private set; } + + public object? Value => _wrapped.TryGetValue("value", out var value) && _myClassMapping?.IsFhirPrimitive is true ? value : null; +#pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). + + public IEnumerable Children(string? name = null) + { + IEnumerable> children = name switch + { + not null => + _wrapped.TryGetValue(name, out var value) && value is not null ? + new KeyValuePair[] { KeyValuePair.Create(name, value) } + : Enumerable.Empty>(), + _ => _wrapped + }; + + foreach (var child in children) + { + if (child.Value is IList coll) + { + foreach (var childValue in coll) + //if (childValue is not null) + // yield return forObject(child.Key, childValue); + if (childValue is IReadOnlyDictionary dict) + yield return new ScopedNodeOnDictionary(_inspector, child.Key, dict, this); + + } + else if (child.Value is IReadOnlyDictionary re) + yield return new ScopedNodeOnDictionary(_inspector, child.Key, re, this); + else if (child.Key != "value" && child.Value is string or bool or decimal or DateTimeOffset or int or long or byte[] or XHtml) + yield return new ConstantElement(child.Key, child.Value.GetType().Name, child.Value, this); + //else + // yield return forObject(child.Key, child.Value); + + } + + //IScopedNode forObject(string name, object value) => value switch + //{ + // string or bool or decimal or DateTimeOffset or int or long or byte[] or XHtml => new ConstantElement(name, value.GetType().Name, value, this), + // IReadOnlyDictionary dict => new ScopedNodeOnDictionary(_inspector, name, dict, this), + // _ => throw new NotImplementedException() + //}; + } + + internal record ConstantElement(string Name, string InstanceType, object Value, IScopedNode Parent) : IScopedNode + { + public IEnumerable Children(string? name) => + Enumerable.Empty(); + } + } +} \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems index e472bdf7..d192ac1d 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Firely.Fhir.Validation.Compilation.Tests.Shared.projitems @@ -1805,6 +1805,7 @@ + diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SlicingSchemaConverterTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SlicingSchemaConverterTests.cs index b03b3079..0ee21ed2 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SlicingSchemaConverterTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/SlicingSchemaConverterTests.cs @@ -6,7 +6,6 @@ using FluentAssertions; using FluentAssertions.Equivalency; -using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Support; @@ -49,13 +48,13 @@ private async T.Task createSliceForElement(string canonical, str "Element does not match any slice and the group is closed."); private readonly SliceValidator.SliceCase _fixedSlice = new("Fixed", - new PathSelectorValidator("system", new FixedValidator(new FhirUri("http://example.com/some-bsn-uri").ToTypedElement())), + new PathSelectorValidator("system", new FixedValidator(new FhirUri("http://example.com/some-bsn-uri"))), new ElementSchema("#Patient.identifier:Fixed")); private static SliceValidator.SliceCase getPatternSlice(string profile) => new("PatternBinding", new PathSelectorValidator("system", new AllValidator(shortcircuitEvaluation: true, - new PatternValidator(new FhirUri("http://example.com/someuri").ToTypedElement()), + new PatternValidator(new FhirUri("http://example.com/someuri")), new BindingValidator("http://example.com/demobinding", strength: BindingValidator.BindingStrength.Required, context: $"{profile}#Patient.identifier.system"))), new ElementSchema("#Patient.identifier:PatternBinding")); @@ -222,11 +221,11 @@ public async T.Task TestResliceGeneration() #endif var expectedSlice = new SliceValidator(false, true, ResultAssertion.SUCCESS, new SliceValidator.SliceCase("phone", new PathSelectorValidator("system", new AllValidator(shortcircuitEvaluation: true, - new FixedValidator(new Code("phone").ToTypedElement()), + new FixedValidator(new Code("phone")), new BindingValidator(contactPointSystem, BindingValidator.BindingStrength.Required, context: "http://validationtest.org/fhir/StructureDefinition/ResliceTestcase#Patient.telecom.system"))), new ElementSchema("#Patient.telecom:phone")), new SliceValidator.SliceCase("email", new PathSelectorValidator("system", new AllValidator(shortcircuitEvaluation: true, - new FixedValidator(new Code("email").ToTypedElement()), + new FixedValidator(new Code("email")), new BindingValidator(contactPointSystem, BindingValidator.BindingStrength.Required, context: "http://validationtest.org/fhir/StructureDefinition/ResliceTestcase#Patient.telecom.system"))), new ElementSchema("#Patient.telecom:email")) ); @@ -244,11 +243,11 @@ void testResliceInSlice2(SliceValidator.SliceCase slice2) var email = new SliceValidator(false, false, _sliceClosedAssertion, new SliceValidator.SliceCase("email/home", new PathSelectorValidator("use", new AllValidator(shortcircuitEvaluation: true, - new FixedValidator(new Code("home").ToTypedElement()), + new FixedValidator(new Code("home")), new BindingValidator(contactPointUse, BindingValidator.BindingStrength.Required, context: "http://validationtest.org/fhir/StructureDefinition/ResliceTestcase#Patient.telecom.use"))), new ElementSchema("#Patient.telecom:email/home")), new SliceValidator.SliceCase("email/work", new PathSelectorValidator("use", new AllValidator(shortcircuitEvaluation: true, - new FixedValidator(new Code("work").ToTypedElement()), + new FixedValidator(new Code("work")), new BindingValidator(contactPointUse, BindingValidator.BindingStrength.Required, context: "http://validationtest.org/fhir/StructureDefinition/ResliceTestcase#Patient.telecom.use"))), new ElementSchema("#Patient.telecom:email/work")) ); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs new file mode 100644 index 00000000..626b2d25 --- /dev/null +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs @@ -0,0 +1,73 @@ +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace Firely.Fhir.Validation.Compilation.Tests +{ + [TestClass] + public class ScopedNodeOnDictionaryTests + { + + [TestMethod] + public void MyTestMethod() + { + var humanName = new HumanName() { Family = "Brown", Given = new[] { "Joe" } }; + var fhirBool = new FhirBoolean(true); + fhirBool.AddExtension("http://example.org/extension", new FhirString("some extension")); + var patient = new Patient() { ActiveElement = fhirBool }; + //patient.AddExtension("http://example.org/extension", new FhirString("some extension")); + //patient.AddExtension("http://example.org/otherextions", new FhirDecimal(12)); + //patient.Name.Add(new HumanName() { Family = "Doe", Given = ["John", "J."] }); + + + + Debug.WriteLine("ITypedElement"); + Debug.WriteLine(printNode(humanName.ToTypedElement())); + + Debug.WriteLine("====\nIScopedNode"); + + + //Debug.WriteLine(printDictionary(patient)); + + + Debug.WriteLine(printNode(humanName.ToScopedNode())); + + + string printNode(IBaseElementNavigator node, int depth = 0) where T : IBaseElementNavigator + { + var indent = new string(' ', depth * 2); + + var result = $"{indent}{{\n{indent} Name: {node.Name}\n{indent} Value: {node.Value}\n{indent} Type: {node.InstanceType}\n{indent}}}\n"; + foreach (var child in node.Children()) + result += printNode(child, depth + 1); + return result; + } + + string printDictionary(IReadOnlyDictionary dict, int depth = 0) + { + var result = string.Empty; + var indent = new string(' ', depth * 2); + foreach (var node in dict) + { + result += $"{indent}{{\n{indent} Key: {node.Key}\n{indent} Value: {printValue(node.Value, depth)}\n{indent}}}\n"; + } + return result; + } + + string printValue(object value, int depth) => + value switch + { + byte[] bt => string.Empty, + string or bool or decimal or DateTimeOffset or int or long or XHtml => value.ToString()!, + IReadOnlyDictionary dict => printDictionary(dict, depth + 1), + IEnumerable list => list.Select(l => printValue(l, depth)).Aggregate(string.Empty, (current, next) => current + next), + _ => throw new NotImplementedException() + }; + } + + } +} diff --git a/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs index b9c23bae..cbb4c24e 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs @@ -6,7 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; -using Hl7.Fhir.ElementModel.Types; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -21,74 +21,73 @@ internal class FixedValidationData : BasicValidatorDataAttribute // integer yield return new object?[] { - new FixedValidator(10), - ElementNode.ForPrimitive(91), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [int]" + new FixedValidator(new Hl7.Fhir.Model.Integer(10)), + ElementNode.ForPrimitive(91), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [int]" }; yield return new object?[] { - new FixedValidator(90), - ElementNode.ForPrimitive(90), - true, null, "result must be true [int]" + new FixedValidator(new Hl7.Fhir.Model.Integer(90)), + ElementNode.ForPrimitive(90), + true, null, "result must be true [int]" }; // string yield return new object?[] { - new FixedValidator("test"), - ElementNode.ForPrimitive("testfailure"), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [string]" + new FixedValidator(new FhirString("test")), + ElementNode.ForPrimitive("testfailure"), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [string]" }; yield return new object?[] { - new FixedValidator("test"), - ElementNode.ForPrimitive("test"), - true, null,"result must be true [string]" + new FixedValidator(new FhirString("test")), + ElementNode.ForPrimitive("test"), + true, null,"result must be true [string]" }; // boolean yield return new object?[] { - new FixedValidator(true), - ElementNode.ForPrimitive(false), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [boolean]" + new FixedValidator(new FhirBoolean(true)), + ElementNode.ForPrimitive(false), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [boolean]" }; yield return new object?[] { - new FixedValidator(true), - ElementNode.ForPrimitive(true), - true, null, "result must be true [boolean]" + new FixedValidator(new FhirBoolean(true)), + ElementNode.ForPrimitive(true), + true, null, "result must be true [boolean]" }; // mixed primitive types yield return new object[] { - new FixedValidator(Date.Parse("2019-09-05")), - ElementNode.ForPrimitive(20190905), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [mixed]" + new FixedValidator(new Hl7.Fhir.Model.Date("2019-09-05")), + ElementNode.ForPrimitive(20190905), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "result must be false [mixed]" }; // Complex Types yield return new object?[] { - new FixedValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), - ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } ), - true, null, "The input should match: family name should be Brown, and given name is Joe" + new FixedValidator(new HumanName() { Family = "Brown", Given = [ "Joe" ] } ), + ElementNodeAdapterExtensions.CreateHumanName("Brown", ["Joe"] ), + true, null, "The input should match: family name should be Brown, and given name is Joe" }; yield return new object?[] { - new FixedValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), - ElementNode.ForPrimitive("Brown, Joe Patrick"), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "String and HumanName are different" + new FixedValidator(new HumanName() { Family = "Brown", Given = [ "Joe" ] } ), + ElementNode.ForPrimitive("Brown, Joe Patrick"), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "String and HumanName are different" }; yield return new object?[] { - new FixedValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe", "Patrick" } )), - ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Patrick", "Joe" } ), - false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "The input should not match the fixed" + new FixedValidator(new HumanName() { Family = "Brown", Given = [ "Joe" , "Patricks"] }), + ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Patrick", "Joe" } ), + false, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "The input should not match the fixed" }; yield return new object?[] { - new FixedValidator( - ElementNodeAdapterExtensions.CreateCoding("code", "system", systemFirstInOrder: false)), - ElementNodeAdapterExtensions.CreateCoding("code", "system", systemFirstInOrder: true), - true, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "The input should match the fixed, also when the order is not the same" + new FixedValidator( new Coding("system", "code")), + ElementNodeAdapterExtensions.CreateCoding( "code", "system", false), + true, Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, "The input should match the fixed, also when the order is not the same" }; } } @@ -100,7 +99,7 @@ public class FixedValidatorTests : BasicValidatorTests public void InvalidConstructors() { #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Action action = () => _ = new FixedValidator(null); + Action action = () => _ = new FixedValidator((IScopedNode?)null); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. action.Should().Throw(); } @@ -108,10 +107,10 @@ public void InvalidConstructors() [TestMethod] public void CorrectConstructor() { - var assertion = new FixedValidator(4); + var assertion = new FixedValidator(new Hl7.Fhir.Model.Integer(4)); assertion.Should().NotBeNull(); - assertion.FixedValue.Should().BeAssignableTo(); + assertion.FixedValue.Should().BeAssignableTo(); } [DataTestMethod] diff --git a/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs index d3cfd44a..78328c69 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs @@ -6,7 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; -using Hl7.Fhir.ElementModel.Types; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -21,65 +21,65 @@ internal class PatternValidatorData : BasicValidatorDataAttribute // integer yield return new object?[] { - new PatternValidator(10), + new PatternValidator(new Hl7.Fhir.Model.Integer(10)), ElementNode.ForPrimitive(91), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "result must be false [int]" }; yield return new object?[] { - new PatternValidator(90), + new PatternValidator(new Hl7.Fhir.Model.Integer(90)), ElementNode.ForPrimitive(90), true, null, "result must be true [int]" }; // string yield return new object?[] { - new PatternValidator("test"), + new PatternValidator(new FhirString("test")), ElementNode.ForPrimitive("testfailure"), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "result must be false [string]" }; yield return new object?[] { - new PatternValidator("test"), + new PatternValidator(new FhirString("test")), ElementNode.ForPrimitive("test"), true, null,"result must be true [string]" }; // boolean yield return new object?[] { - new PatternValidator(true), + new PatternValidator(new FhirBoolean(true)), ElementNode.ForPrimitive(false), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "result must be false [boolean]" }; yield return new object?[] { - new PatternValidator(true), + new PatternValidator(new FhirBoolean(true)), ElementNode.ForPrimitive(true), true, null, "result must be true [boolean]" }; // mixed primitive types yield return new object?[] { - new PatternValidator(Date.Parse("2019-09-05")), + new PatternValidator( new Hl7.Fhir.Model.Date("2019-09-05")), ElementNode.ForPrimitive(20190905), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "result must be false [mixed]" }; // Complex types yield return new object?[] { - new PatternValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), + new PatternValidator(new HumanName() { Family = "Brown", Given = ["Joe" ]} ), ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe", "Patrick" } ), true, null, "The input should match the pattern: family name should be Brown, and given name is Joe" }; yield return new object?[] { - new PatternValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), + new PatternValidator(new HumanName() { Family = "Brown", Given = ["Joe" ]}), ElementNode.ForPrimitive("Brown, Joe Patrick"), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "String and HumanName are different" }; yield return new object?[] { - new PatternValidator(ElementNodeAdapterExtensions.CreateHumanName("Brown", new[] { "Joe" } )), + new PatternValidator(new HumanName() { Family = "Brown", Given = ["Joe" ]}), ElementNodeAdapterExtensions.CreateHumanName("Brown", Array.Empty() ), false, Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, "The input should not match the pattern" }; @@ -93,7 +93,7 @@ public class PatternValidatorTests : BasicValidatorTests public void InvalidConstructors() { #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Action action = () => _ = new PatternValidator(null); + Action action = () => _ = new PatternValidator((IScopedNode?)null); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. action.Should().Throw(); } @@ -101,10 +101,10 @@ public void InvalidConstructors() [TestMethod] public void CorrectConstructor() { - var assertion = new PatternValidator(4); + var assertion = new PatternValidator(new Hl7.Fhir.Model.Integer(4)); assertion.Should().NotBeNull(); - assertion.PatternValue.Should().BeAssignableTo(); + assertion.PatternValue.Should().BeAssignableTo(); } [DataTestMethod] diff --git a/test/Firely.Fhir.Validation.Tests/Impl/SchemaReferenceValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/SchemaReferenceValidatorTests.cs index 2e91088a..f6b76a84 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/SchemaReferenceValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/SchemaReferenceValidatorTests.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -18,7 +19,7 @@ public class SchemaReferenceValidatorTests public void InvokesCorrectSchema() { var schemaUri = "http://someotherschema"; - var schema = new ElementSchema(schemaUri, new ChildrenValidator(true, ("value", new FixedValidator("hi")))); + var schema = new ElementSchema(schemaUri, new ChildrenValidator(true, ("value", new FixedValidator(new FhirString("hi"))))); var resolver = new TestResolver() { schema }; var vc = ValidationContext.BuildMinimalContext(schemaResolver: resolver); @@ -46,7 +47,7 @@ public void ExtensionInvokesCorrectSchema() new StructureDefinitionInformation("http://hl7.org/fhir/StructureDefinition/Extension", null, "Extension", null, false)); var referredSchema = new ExtensionSchema( new StructureDefinitionInformation(schemaUri, null, "Extension", null, false), - new ChildrenValidator(true, ("value", new FixedValidator("hi")))); + new ChildrenValidator(true, ("value", new FixedValidator(new FhirString("hi"))))); var resolver = new TestResolver() { referredSchema }; var vc = ValidationContext.BuildMinimalContext(schemaResolver: resolver); diff --git a/test/Firely.Fhir.Validation.Tests/Impl/SliceValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/SliceValidatorTests.cs index cfd589b3..42affea8 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/SliceValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/SliceValidatorTests.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; @@ -132,8 +133,8 @@ private static IEnumerable buildTestcase(params string[] instance private SliceValidator buildSliceAssertion(bool ordered, bool openAtEnd) => new(ordered, openAtEnd, DefaultEvidence, - new SliceValidator.SliceCase("slice1", new FixedValidator(ElementNode.ForPrimitive("slice1")), Slice1Evidence), - new SliceValidator.SliceCase("slice2", new FixedValidator(ElementNode.ForPrimitive("slice2")), Slice2Evidence)); + new SliceValidator.SliceCase("slice1", new FixedValidator(new FhirString("slice1")), Slice1Evidence), + new SliceValidator.SliceCase("slice2", new FixedValidator(new FhirString("slice2")), Slice2Evidence)); } } diff --git a/test/Firely.Fhir.Validation.Tests/Impl/TestSerialization.cs b/test/Firely.Fhir.Validation.Tests/Impl/TestSerialization.cs index 0028dd4b..513ee8b5 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/TestSerialization.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/TestSerialization.cs @@ -6,6 +6,7 @@ using FluentAssertions; using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Model; using Hl7.Fhir.Support; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Collections.Generic; @@ -55,7 +56,7 @@ public void ValidateSchema() new SchemaReferenceValidator(stringSchema.Id), new CardinalityValidator(0, 1), new MaxLengthValidator(40), - new FixedValidator("Brown") + new FixedValidator(new FhirString("Brown")) ); var givenSchema = new ElementSchema("#given", @@ -143,13 +144,20 @@ static ITypedElement buildCodeableConcept(string system, string code) return result; } + static CodeableConcept buildCodeableConceptPoco(string system, string code) + { + var result = new CodeableConcept(); + result.Coding.Add(new Coding(system, code)); + return result; + } + var systolicSlice = new SliceValidator.SliceCase("systolic", - new PathSelectorValidator("code", new FixedValidator(buildCodeableConcept("http://loinc.org", "8480-6"))), + new PathSelectorValidator("code", new FixedValidator(buildCodeableConceptPoco("http://loinc.org", "8480-6"))), bpComponentSchema ); var dystolicSlice = new SliceValidator.SliceCase("dystolic", - new PathSelectorValidator("code", new FixedValidator(buildCodeableConcept("http://loinc.org", "8462-4"))), + new PathSelectorValidator("code", new FixedValidator(buildCodeableConceptPoco("http://loinc.org", "8462-4"))), bpComponentSchema ); diff --git a/test/Firely.Fhir.Validation.Tests/Support/ElementNodeAdapter.cs b/test/Firely.Fhir.Validation.Tests/Support/ElementNodeAdapter.cs index e4f1d766..6f083c08 100644 --- a/test/Firely.Fhir.Validation.Tests/Support/ElementNodeAdapter.cs +++ b/test/Firely.Fhir.Validation.Tests/Support/ElementNodeAdapter.cs @@ -68,7 +68,7 @@ internal static class ElementNodeAdapterExtensions public static ITypedElement CreateHumanName(string familyName, string[] givenNames) { var node = ElementNodeAdapter.Root("HumanName"); - if (string.IsNullOrEmpty(familyName)) + if (!string.IsNullOrEmpty(familyName)) node.Add("family", familyName, "string"); foreach (var givenName in givenNames) node.Add("given", givenName, "string"); From 13fa4083923375ec563f3cc7d0f6a2c36196f220 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 29 Nov 2023 22:30:46 +0100 Subject: [PATCH 17/27] Solving warning as errors --- firely-validator-api.props | 1 + .../Properties/AssemblyInfo.cs | 4 ++-- .../Impl/FhirPathValidator.cs | 4 ++++ .../Impl/PathSelectorValidator.cs | 2 ++ .../Properties/AssemblyInfo.cs | 17 ++++++----------- .../Support/ScopedNodeExtensions.cs | 2 +- ...hir.Validation.Compilation.STU3.Tests.csproj | 2 +- 7 files changed, 17 insertions(+), 15 deletions(-) diff --git a/firely-validator-api.props b/firely-validator-api.props index a43a5311..1005ae63 100644 --- a/firely-validator-api.props +++ b/firely-validator-api.props @@ -38,6 +38,7 @@ True snupkg true + NU5104 diff --git a/src/Firely.Fhir.Validation.Compilation.STU3/Properties/AssemblyInfo.cs b/src/Firely.Fhir.Validation.Compilation.STU3/Properties/AssemblyInfo.cs index a5300c31..6159dfcf 100644 --- a/src/Firely.Fhir.Validation.Compilation.STU3/Properties/AssemblyInfo.cs +++ b/src/Firely.Fhir.Validation.Compilation.STU3/Properties/AssemblyInfo.cs @@ -13,10 +13,10 @@ #if DEBUG [assembly: InternalsVisibleTo("Firely.Fhir.Validation.Tests")] -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.STU3")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.STU3.Tests")] #endif #if RELEASE [assembly: InternalsVisibleTo("Firely.Fhir.Validation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.STU3, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.STU3.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] #endif diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index 98e6b9d1..4c92baa3 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -101,7 +101,9 @@ protected override (bool, ResultReport?) RunInvariant(IScopedNode input, Validat { try { +#pragma warning disable CS0618 // Type or member is obsolete var node = input as ScopedNode ?? new ScopedNode(input.AsTypedElement()); +#pragma warning restore CS0618 // Type or member is obsolete var context = new FhirEvaluationContext(node.ResourceContext) { TerminologyService = new ValidateCodeServiceToTerminologyServiceAdapter(vc.ValidateCodeService) @@ -157,7 +159,9 @@ private bool predicate(IScopedNode input, EvaluationContext context, ValidationC var compiler = vc?.FhirPathCompiler ?? DefaultCompiler; var compiledExpression = getDefaultCompiledExpression(compiler); +#pragma warning disable CS0618 // Type or member is obsolete return compiledExpression.IsTrue(input.AsTypedElement(), context); +#pragma warning restore CS0618 // Type or member is obsolete } /// diff --git a/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs b/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs index a44da508..69430239 100644 --- a/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PathSelectorValidator.cs @@ -52,7 +52,9 @@ public ResultReport Validate(IScopedNode input, ValidationContext vc, Validation { initializeFhirPathCache(vc, state); +#pragma warning disable CS0618 // Type or member is obsolete var selected = state.Global.FPCompilerCache!.Select(input.AsTypedElement(), Path).ToList(); +#pragma warning restore CS0618 // Type or member is obsolete if (selected.Any()) { diff --git a/src/Firely.Fhir.Validation/Properties/AssemblyInfo.cs b/src/Firely.Fhir.Validation/Properties/AssemblyInfo.cs index 35137495..9ad29ba5 100644 --- a/src/Firely.Fhir.Validation/Properties/AssemblyInfo.cs +++ b/src/Firely.Fhir.Validation/Properties/AssemblyInfo.cs @@ -12,21 +12,16 @@ [assembly: CLSCompliant(true)] #if DEBUG [assembly: InternalsVisibleTo("Firely.Fhir.Validation.Tests")] - -#if STU3 -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.STU3")] -#else -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.R4")] -#endif +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.STU3.Tests")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.R4.Tests")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.R5.Tests")] #endif #if RELEASE [assembly: InternalsVisibleTo("Firely.Fhir.Validation.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] -#if STU3 -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.STU3, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] -#else -[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.Tests.R4, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] -#endif +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.STU3.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.R4.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] +[assembly: InternalsVisibleTo("Firely.Fhir.Validation.Compilation.R5.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c11eea5df3095844b027f018b356bc326a5a30b1f245010ad789589aa685569b2eb7f5f2ea5c49dafed338e3d9969eab21848c6c20a6b0a22c5ff7797d9a5062d7f3e42478e905d72a3dde344086a003f2df9deeb838e206d92c8cc59150c3151e9490381321f77a716e0a2b24a585b302ba2b3db37966a3da9abe4c601ba4c1")] #endif diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs index 0e572c37..bc899f2f 100644 --- a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs @@ -13,7 +13,7 @@ namespace Firely.Fhir.Validation /// /// Helper methods to convert between and /// - public static class ScopedNodeExtensions + internal static class ScopedNodeExtensions { /// /// diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj index 747d8f0c..33fa3d48 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj +++ b/test/Firely.Fhir.Validation.Compilation.Tests.STU3/Firely.Fhir.Validation.Compilation.STU3.Tests.csproj @@ -7,7 +7,7 @@ - Firely.Fhir.Validation.Compilation.Tests.STU3 + Firely.Fhir.Validation.Compilation.STU3.Tests From bcaf570913b54c8276f70884a4ec3cbf11547a82 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 29 Nov 2023 22:58:01 +0100 Subject: [PATCH 18/27] A temporary solution for the JSON extension methods --- .../Support/ScopedNodeExtensions.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs index bc899f2f..2a37bea4 100644 --- a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs @@ -27,9 +27,21 @@ public static IScopedNode ToScopedNode(this IReadOnlyDictionary return new ScopedNodeOnDictionary(inspector!, node.GetType().Name, node); } - public static string ToJson(this IScopedNode instance) => string.Empty; // TODO + public static string ToJson(this IScopedNode instance) => instance.ToJObject().ToString(Newtonsoft.Json.Formatting.None); - public static JObject ToJObject(this IScopedNode instance) => new(); // TODO + // TODO - this is a temporary solution, we need to find a better way to serialize the IScopedNode to JObject + public static JObject ToJObject(this IScopedNode instance) + { + var result = new JObject(); + + foreach (var child in instance.Children()) + { + if (child.Value is not null) + result.Add(child.Name, new JValue(child.Value)); + } + + return result; + } public static JToken ToPropValue(this IScopedNode e) => e.Value is not null ? new JValue(e.Value) : e.ToJObject(); } From e34d9b51c9d439b9d8d44f801c6a90f5d26d373a Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Fri, 1 Dec 2023 15:54:02 +0100 Subject: [PATCH 19/27] Found some rough edges while creating the demo application. --- firely-validator-api-tests.props | 2 +- firely-validator-api.props | 4 +- .../CommonTypeRefComponentExtensions.cs | 1 - .../DiscriminatorFactory.cs | 1 - .../SchemaBuilder.cs | 1 - .../SchemaBuilders/BindingBuilder.cs | 1 - .../SchemaBuilders/TypeReferenceBuilder.cs | 1 - ...uctureDefinitionToElementSchemaResolver.cs | 23 ++++-- .../Impl/CardinalityValidator.cs | 1 - .../Impl/FhirPathValidator.cs | 1 - .../Impl/MaxLengthValidator.cs | 1 - .../Impl/MinMaxValueValidator.cs | 1 - .../PublicAPI.Unshipped.txt | 5 ++ .../Schema/IElementSchemaResolver.cs | 1 + .../Schema/SchemaResolutionFailedException.cs | 39 +++++++++++ .../Schema/ValidationContext.cs | 4 +- .../IncorrectElementDefinitionException.cs | 2 +- .../PublicAPI.Unshipped.txt | 19 ++--- .../PublicAPI.Unshipped.txt | 19 ++--- src/Hl7.Fhir.Validation.Shared/Validator.cs | 70 ++++++++++++------- test/Benchmarks/ValidatorBenchmarks.cs | 2 +- .../ValidatorHighLevelApiTests.cs | 12 ++-- 22 files changed, 144 insertions(+), 67 deletions(-) create mode 100644 src/Firely.Fhir.Validation/Schema/SchemaResolutionFailedException.cs diff --git a/firely-validator-api-tests.props b/firely-validator-api-tests.props index a84cbccc..88469a99 100644 --- a/firely-validator-api-tests.props +++ b/firely-validator-api-tests.props @@ -10,7 +10,7 @@ - 5.4.1-20231114.4 + 5.4.3-alpha1 5.1.0 diff --git a/firely-validator-api.props b/firely-validator-api.props index a7da46ee..a67b6fa9 100644 --- a/firely-validator-api.props +++ b/firely-validator-api.props @@ -2,7 +2,7 @@ - 1.2.0-alpha1 + 1.2.0-alpha5 Firely Firely (https://fire.ly) Copyright 2015-2022 Firely @@ -19,7 +19,7 @@ - 5.4.1-20231114.4 + 5.4.3-alpha1 diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/CommonTypeRefComponentExtensions.cs b/src/Firely.Fhir.Validation.Compilation.Shared/CommonTypeRefComponentExtensions.cs index a451ab9d..dccee74d 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/CommonTypeRefComponentExtensions.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/CommonTypeRefComponentExtensions.cs @@ -5,7 +5,6 @@ */ using Hl7.Fhir.Model; -using Hl7.Fhir.Validation; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/DiscriminatorFactory.cs b/src/Firely.Fhir.Validation.Compilation.Shared/DiscriminatorFactory.cs index d3b122a1..2c1e0e5c 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/DiscriminatorFactory.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/DiscriminatorFactory.cs @@ -9,7 +9,6 @@ using Hl7.Fhir.Specification.Navigation; using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs index f3866b15..fb1a92dd 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilder.cs @@ -9,7 +9,6 @@ using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BindingBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BindingBuilder.cs index cc71f7dc..b67dd569 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BindingBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/BindingBuilder.cs @@ -6,7 +6,6 @@ using Hl7.Fhir.Model; using Hl7.Fhir.Specification.Navigation; -using Hl7.Fhir.Validation; using System.Collections.Generic; namespace Firely.Fhir.Validation.Compilation diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs index 8a78723d..fe91934b 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/SchemaBuilders/TypeReferenceBuilder.cs @@ -10,7 +10,6 @@ using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs b/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs index 7f482e54..7b42ccb3 100644 --- a/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation.Compilation.Shared/StructureDefinitionToElementSchemaResolver.cs @@ -78,7 +78,9 @@ internal StructureDefinitionToElementSchemaResolver(IAsyncResourceResolver sourc /// source StructureDefinition for a schema uri. internal StructureDefinitionToElementSchemaResolver(IAsyncResourceResolver source) : this(source, new[] { new StandardBuilders(source) }) - { } + { + // Nothing + } /// /// Builds a schema directly from an without fetching @@ -93,9 +95,20 @@ internal StructureDefinitionToElementSchemaResolver(IAsyncResourceResolver sourc /// The canonical url of the StructureDefinition. /// The schema, or null if the schema uri could not be resolved as a /// StructureDefinition canonical. - public ElementSchema? GetSchema(Canonical schemaUri) => - TaskHelper.Await(() => Source.FindStructureDefinitionAsync((string)schemaUri)) is StructureDefinition sd - ? _schemaBuilder.BuildSchema(sd) - : null; + public ElementSchema? GetSchema(Canonical schemaUri) + { + try + { + return TaskHelper.Await(() => Source.FindStructureDefinitionAsync((string)schemaUri)) is StructureDefinition sd + ? _schemaBuilder.BuildSchema(sd) + : null; + } + catch (Exception e) + { + throw new SchemaResolutionFailedException( + $"Encountered an error while loading schema '{schemaUri}': {e.Message}", + schemaUri, e); + } + } } } \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs b/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs index 1c911f4c..afb55653 100644 --- a/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/CardinalityValidator.cs @@ -6,7 +6,6 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Support; -using Hl7.Fhir.Validation; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Linq; diff --git a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs index 0fb20708..466dd9ec 100644 --- a/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FhirPathValidator.cs @@ -10,7 +10,6 @@ using Hl7.Fhir.Specification.Terminology; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using Hl7.FhirPath; using Hl7.FhirPath.Expressions; using Newtonsoft.Json.Linq; diff --git a/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs b/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs index a922fdfb..d9417e97 100644 --- a/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/MaxLengthValidator.cs @@ -8,7 +8,6 @@ using Hl7.Fhir.ElementModel.Types; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using System.Runtime.Serialization; namespace Firely.Fhir.Validation diff --git a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs index e2869cc6..6f9c31a9 100644 --- a/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/MinMaxValueValidator.cs @@ -8,7 +8,6 @@ using Hl7.Fhir.ElementModel.Types; using Hl7.Fhir.Support; using Hl7.Fhir.Utility; -using Hl7.Fhir.Validation; using Newtonsoft.Json.Linq; using System; using System.Runtime.Serialization; diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index ba9e22ef..d4f49c5c 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -50,6 +50,11 @@ Firely.Fhir.Validation.ResultReport.Result.get -> Firely.Fhir.Validation.Validat Firely.Fhir.Validation.ResultReport.ResultReport(Firely.Fhir.Validation.ValidationResult result, params Firely.Fhir.Validation.IAssertion![]! evidence) -> void Firely.Fhir.Validation.ResultReport.ResultReport(Firely.Fhir.Validation.ValidationResult result, System.Collections.Generic.IEnumerable! evidence) -> void Firely.Fhir.Validation.ResultReport.Warnings.get -> System.Collections.Generic.IReadOnlyCollection! +Firely.Fhir.Validation.SchemaResolutionFailedException +Firely.Fhir.Validation.SchemaResolutionFailedException.SchemaResolutionFailedException(string! message, Firely.Fhir.Validation.Canonical! schemaUri) -> void +Firely.Fhir.Validation.SchemaResolutionFailedException.SchemaResolutionFailedException(string! message, Firely.Fhir.Validation.Canonical! schemaUri, System.Exception! inner) -> void +Firely.Fhir.Validation.SchemaResolutionFailedException.SchemaUri.get -> Firely.Fhir.Validation.Canonical! +Firely.Fhir.Validation.SchemaResolutionFailedException.SchemaUri.set -> void Firely.Fhir.Validation.TerminologyServiceExceptionResult Firely.Fhir.Validation.TerminologyServiceExceptionResult.Error = 1 -> Firely.Fhir.Validation.TerminologyServiceExceptionResult Firely.Fhir.Validation.TerminologyServiceExceptionResult.Warning = 0 -> Firely.Fhir.Validation.TerminologyServiceExceptionResult diff --git a/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs b/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs index cb03c099..7790c132 100644 --- a/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs +++ b/src/Firely.Fhir.Validation/Schema/IElementSchemaResolver.cs @@ -16,6 +16,7 @@ internal interface IElementSchemaResolver /// /// /// Returns null if the schema was not found. + /// Thrown when the schema was found, but could not be loaded or parsed. ElementSchema? GetSchema(Canonical schemaUri); } } \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Schema/SchemaResolutionFailedException.cs b/src/Firely.Fhir.Validation/Schema/SchemaResolutionFailedException.cs new file mode 100644 index 00000000..08eac1bb --- /dev/null +++ b/src/Firely.Fhir.Validation/Schema/SchemaResolutionFailedException.cs @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021, Firely (info@fire.ly) - All Rights Reserved + * Proprietary and confidential. Unauthorized copying of this file, + * via any medium is strictly prohibited. + */ + +namespace Firely.Fhir.Validation +{ + /// + /// Represents an error that occurs when a schema exists, but could not be returned. The reasons + /// are diverse, but examples are unparseable schema's (or underlying StructureDefinitions), specification version errors, + /// duplicate canonical urls, etc. Implementors do not have to raise this exception, but may choose to return null + /// as a result of instead. + /// + public class SchemaResolutionFailedException : System.Exception + { + /// + /// The schema uri that was used to resolve the schema. + /// + public Canonical SchemaUri { get; set; } + + /// + /// Construct an exception given the message and the schema uri. + /// + public SchemaResolutionFailedException(string message, Canonical schemaUri) : base(message) + { + SchemaUri = schemaUri; + } + + /// + /// Construct an exception given the message, the schema uri and the inner exception with details about the reason + /// why the load failed. + /// + public SchemaResolutionFailedException(string message, Canonical schemaUri, System.Exception inner) : base(message, inner) + { + SchemaUri = schemaUri; + } + } +} \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs index be43b8ce..6156165d 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs @@ -42,7 +42,7 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// A function that maps a type name found in TypeRefComponent.Code to a resolvable canonical. /// If not set, it will prefix the type with the standard http://hl7.org/fhir/StructureDefinition prefix. /// - public TypeNameMapper? TypeNameMapper { get; set; } + public TypeNameMapper? TypeNameMapper = null; /// /// The to invoke when the validator calls out to a terminology service and this call @@ -118,7 +118,7 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// and . /// public bool Filter(IAssertion a) => - (!IncludeFilters.Any() || IncludeFilters.Any(inc => inc(a))) && + (IncludeFilters.Count == 0 || IncludeFilters.Any(inc => inc(a))) && !ExcludeFilters.Any(exc => exc(a)); /// diff --git a/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs b/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs index 085b3473..319aade6 100644 --- a/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs +++ b/src/Firely.Fhir.Validation/Support/IncorrectElementDefinitionException.cs @@ -8,7 +8,7 @@ using System; -namespace Hl7.Fhir.Validation +namespace Firely.Fhir.Validation { /// /// Exception about an error encountered in an ElementDefinitions. diff --git a/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt b/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt index ec99d819..804403a0 100644 --- a/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt +++ b/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt @@ -1,12 +1,13 @@ #nullable enable Firely.Fhir.Validation.Validator Firely.Fhir.Validation.Validator.ConstraintBestPractices -> Firely.Fhir.Validation.ValidateBestPracticesSeverity -Firely.Fhir.Validation.Validator.ExtensionUrlFollower -> Firely.Fhir.Validation.ExtensionUrlFollower? -Firely.Fhir.Validation.Validator.MetaProfileSelector -> Firely.Fhir.Validation.MetaProfileSelector? -Firely.Fhir.Validation.Validator.SkipConstraintValidation.get -> bool -Firely.Fhir.Validation.Validator.SkipConstraintValidation.set -> void -Firely.Fhir.Validation.Validator.TypeNameMapper.get -> Firely.Fhir.Validation.TypeNameMapper? -Firely.Fhir.Validation.Validator.TypeNameMapper.set -> void -Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string! profile) -> Firely.Fhir.Validation.ResultReport! -Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string! profile) -> Firely.Fhir.Validation.ResultReport! -Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver, Hl7.FhirPath.FhirPathCompiler? fhirPathCompiler = null) -> void \ No newline at end of file +Firely.Fhir.Validation.Validator.FhirPathCompiler -> Hl7.FhirPath.FhirPathCompiler? +Firely.Fhir.Validation.Validator.FollowExtensionUrl -> Firely.Fhir.Validation.ExtensionUrlFollower? +Firely.Fhir.Validation.Validator.ResolveExternalReference -> Firely.Fhir.Validation.IExternalReferenceResolver? +Firely.Fhir.Validation.Validator.SelectMetaProfiles -> Firely.Fhir.Validation.MetaProfileSelector? +Firely.Fhir.Validation.Validator.SkipConstraintValidation -> bool +Firely.Fhir.Validation.Validator.TerminologyService -> Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! +Firely.Fhir.Validation.Validator.TypeNameMapper -> Firely.Fhir.Validation.TypeNameMapper? +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string? profile = null) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string? profile = null) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver = null, Hl7.FhirPath.FhirPathCompiler? fhirPathCompiler = null) -> void \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt b/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt index ec99d819..804403a0 100644 --- a/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt +++ b/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt @@ -1,12 +1,13 @@ #nullable enable Firely.Fhir.Validation.Validator Firely.Fhir.Validation.Validator.ConstraintBestPractices -> Firely.Fhir.Validation.ValidateBestPracticesSeverity -Firely.Fhir.Validation.Validator.ExtensionUrlFollower -> Firely.Fhir.Validation.ExtensionUrlFollower? -Firely.Fhir.Validation.Validator.MetaProfileSelector -> Firely.Fhir.Validation.MetaProfileSelector? -Firely.Fhir.Validation.Validator.SkipConstraintValidation.get -> bool -Firely.Fhir.Validation.Validator.SkipConstraintValidation.set -> void -Firely.Fhir.Validation.Validator.TypeNameMapper.get -> Firely.Fhir.Validation.TypeNameMapper? -Firely.Fhir.Validation.Validator.TypeNameMapper.set -> void -Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string! profile) -> Firely.Fhir.Validation.ResultReport! -Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string! profile) -> Firely.Fhir.Validation.ResultReport! -Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver, Hl7.FhirPath.FhirPathCompiler? fhirPathCompiler = null) -> void \ No newline at end of file +Firely.Fhir.Validation.Validator.FhirPathCompiler -> Hl7.FhirPath.FhirPathCompiler? +Firely.Fhir.Validation.Validator.FollowExtensionUrl -> Firely.Fhir.Validation.ExtensionUrlFollower? +Firely.Fhir.Validation.Validator.ResolveExternalReference -> Firely.Fhir.Validation.IExternalReferenceResolver? +Firely.Fhir.Validation.Validator.SelectMetaProfiles -> Firely.Fhir.Validation.MetaProfileSelector? +Firely.Fhir.Validation.Validator.SkipConstraintValidation -> bool +Firely.Fhir.Validation.Validator.TerminologyService -> Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! +Firely.Fhir.Validation.Validator.TypeNameMapper -> Firely.Fhir.Validation.TypeNameMapper? +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string? profile = null) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string? profile = null) -> Firely.Fhir.Validation.ResultReport! +Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver = null, Hl7.FhirPath.FhirPathCompiler? fhirPathCompiler = null) -> void \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/Validator.cs b/src/Hl7.Fhir.Validation.Shared/Validator.cs index 291f61b2..62bb42f5 100644 --- a/src/Hl7.Fhir.Validation.Shared/Validator.cs +++ b/src/Hl7.Fhir.Validation.Shared/Validator.cs @@ -22,33 +22,34 @@ public class Validator /// /// Constructs a validator, passing in the services the validator depends on. /// + /// An that is used to resolve the StructureDefinitions to validate against. /// An that is used when the validator must validate a code against a /// terminology service. - /// An that is used to resolve the StructureDefinitions to validate against. /// A that resolves an url to an external instance, represented as a Model POCO. /// Optionally, a FhirPath compiler to use when evaluating constraints /// (provide this if you have custom functions included in the symbol table). public Validator( - ICodeValidationTerminologyService terminologyService, IAsyncResourceResolver resourceResolver, - IExternalReferenceResolver? referenceResolver, + ICodeValidationTerminologyService terminologyService, + IExternalReferenceResolver? referenceResolver = null, FhirPathCompiler? fhirPathCompiler = null) { _elementSchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(resourceResolver); - _terminologyService = terminologyService; - _referenceResolver = referenceResolver; - _fhirPathCompiler = fhirPathCompiler; + + TerminologyService = terminologyService; + ResolveExternalReference = referenceResolver; + FhirPathCompiler = fhirPathCompiler; } private ValidationContext buildContext() { - var validationContext = new ValidationContext(_elementSchemaResolver, _terminologyService) + var validationContext = new ValidationContext(_elementSchemaResolver, TerminologyService) { - FhirPathCompiler = _fhirPathCompiler, - ResolveExternalReference = _referenceResolver is not null ? resolve : null, + FhirPathCompiler = FhirPathCompiler, + ResolveExternalReference = ResolveExternalReference is not null ? resolve : null, ConstraintBestPractices = ConstraintBestPractices, - SelectMetaProfiles = MetaProfileSelector, - FollowExtensionUrl = ExtensionUrlFollower, + SelectMetaProfiles = SelectMetaProfiles, + FollowExtensionUrl = FollowExtensionUrl, TypeNameMapper = TypeNameMapper }; @@ -59,7 +60,7 @@ private ValidationContext buildContext() ITypedElement? resolve(string reference, string location) { - var r = TaskHelper.Await(() => _referenceResolver.ResolveAsync(reference)); + var r = TaskHelper.Await(() => ResolveExternalReference.ResolveAsync(reference)); return toTypedElement(r); } } @@ -74,9 +75,24 @@ private ValidationContext buildContext() }; private readonly IElementSchemaResolver _elementSchemaResolver; - private readonly ICodeValidationTerminologyService _terminologyService; - private readonly IExternalReferenceResolver? _referenceResolver; - private readonly FhirPathCompiler? _fhirPathCompiler; + + /// + /// An that is used when the validator must validate a code against a + /// terminology service. + /// + public ICodeValidationTerminologyService TerminologyService; + + + /// + /// A that resolves an url to an external instance, represented as a Model POCO. + /// + public IExternalReferenceResolver? ResolveExternalReference = null; + + /// + /// Optionally, a FhirPath compiler to use when evaluating constraints. + /// + /// Provide this if you have custom functions included in the symbol table. + public FhirPathCompiler? FhirPathCompiler = null; /// /// Determines how to deal with failures of FhirPath constraints marked as "best practice". Default is . @@ -86,46 +102,52 @@ private ValidationContext buildContext() public ValidateBestPracticesSeverity ConstraintBestPractices = ValidateBestPracticesSeverity.Warning; /// - /// The to invoke when a is encountered. If not set, the list of profiles + /// The to invoke when a is encountered. If not set, the list of profiles /// is used as encountered in the instance. /// - public MetaProfileSelector? MetaProfileSelector = null; + public MetaProfileSelector? SelectMetaProfiles = null; /// /// The to invoke when an is encountered in an instance. /// If not set, then a validation of an Extension will warn if the extension cannot be resolved, or will return an error when /// the extension cannot be resolved and is a modififier extension. /// - public ExtensionUrlFollower? ExtensionUrlFollower = null; + public ExtensionUrlFollower? FollowExtensionUrl = null; /// /// A function that maps a type name found in TypeRefComponent.Code to a resolvable canonical. /// If not set, it will prefix the type with the standard http://hl7.org/fhir/StructureDefinition prefix. /// - public TypeNameMapper? TypeNameMapper { get; set; } + public TypeNameMapper? TypeNameMapper = null; /// /// StructureDefinition may contain FhirPath constraints to enfore invariants in the data that cannot /// be expresses using StructureDefinition alone. This validation can be turned off for performance or /// debugging purposes. Default is 'false'. /// - public bool SkipConstraintValidation { get; set; } // = false; - + public bool SkipConstraintValidation = false; /// /// Validates an instance against a profile. /// /// A report containing the issues found during validation. - public ResultReport Validate(Resource instance, string profile) => Validate(instance.ToTypedElement(ModelInfo.ModelInspector).AsScopedNode(), profile); +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + // Suppressing this issue since this will be a single method call when we introduce IScopedNode. + public ResultReport Validate(Resource instance, string? profile = null) => Validate(instance.ToTypedElement(ModelInfo.ModelInspector).AsScopedNode(), profile); +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters /// /// Validates an instance against a profile. /// /// A report containing the issues found during validation. - public ResultReport Validate(ElementNode instance, string profile) => Validate(instance.AsScopedNode(), profile); +#pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters + public ResultReport Validate(ElementNode instance, string? profile = null) => Validate(instance.AsScopedNode(), profile); +#pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - internal ResultReport Validate(IScopedNode sn, string profile) + internal ResultReport Validate(IScopedNode sn, string? profile = null) { + profile ??= Canonical.ForCoreType(sn.InstanceType).ToString(); + var validator = new SchemaReferenceValidator(profile); var ctx = buildContext(); return validator.Validate(sn, ctx); diff --git a/test/Benchmarks/ValidatorBenchmarks.cs b/test/Benchmarks/ValidatorBenchmarks.cs index fde9232c..f743c68c 100644 --- a/test/Benchmarks/ValidatorBenchmarks.cs +++ b/test/Benchmarks/ValidatorBenchmarks.cs @@ -63,7 +63,7 @@ private static ResultReport validateWip(ElementNode typedElement, string schema, var arr = rr.AsAsync(); var ts = new LocalTerminologyService(arr); - var validator = new Validator(ts, arr, new TestExternalReferenceResolver(rr)); + var validator = new Validator(arr, ts, new TestExternalReferenceResolver(rr)); var result = validator.Validate(typedElement, schema); return result; } diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs index 36b96eef..6a21244e 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs @@ -29,11 +29,15 @@ public ValidatorHighLevelApiTests(SchemaBuilderFixture fixture) public void ValidatesAndReportsError() { var p = new Patient() { Deceased = new FhirString("wrong") }; - var validator = new Validator(_fixture.ValidateCodeService, _fixture.ResourceResolver, null); + var validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, null); var result = validator.Validate(p, Canonical.ForCoreType("Patient").ToString()); result.Errors.Should().ContainSingle().Which.IssueNumber.Should().Be(Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code); result.IsSuccessful.Should().BeFalse(); + + result = validator.Validate(p); + result.Errors.Should().ContainSingle().Which.IssueNumber.Should().Be(Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code); + result.IsSuccessful.Should().BeFalse(); } [Fact] @@ -41,12 +45,12 @@ public void NoReferenceResolution() { // First, without a resolver, we do not follow references and thus do not validate the referenced resource. var p = new Patient() { ManagingOrganization = new ResourceReference("http://example.com/orgA") }; - var validator = new Validator(_fixture.ValidateCodeService, _fixture.ResourceResolver, null); + var validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, null); validator.Validate(p, Canonical.ForCoreType("Patient").ToString()).IsSuccessful.Should().BeTrue(); // Now with reference resolution var or = new InMemoryExternalReferenceResolver() { ["http://example.com/orgA"] = new Organization() }; - validator = new Validator(_fixture.ValidateCodeService, _fixture.ResourceResolver, or); + validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, or); var result = validator.Validate(p, Canonical.ForCoreType("Patient").ToString()); // Organization should fail constraint org-1 @@ -57,7 +61,7 @@ public void NoReferenceResolution() public void SkipConstraintValidation() { var o = new Organization(); - var validator = new Validator(_fixture.ValidateCodeService, _fixture.ResourceResolver, null); + var validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, null); // validate with constraint validation, it should fail on org-1 var result = validator.Validate(o, Canonical.ForCoreType("Organization").ToString()); From 5d6412b1c319cfd2acf40032ba117e92ef70ef97 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 5 Dec 2023 14:54:06 +0100 Subject: [PATCH 20/27] Use DataType as the only input for fixed and pattern validator --- .../Impl/FixedValidator.cs | 34 +++++------ .../Impl/PatternValidator.cs | 31 +++++----- .../Support/ScopedNodeExtensions.cs | 19 +----- .../Support/ScopedNodeJsonExtensions.cs | 34 +++++++++++ .../Impl/FixedValidatorTests.cs | 4 +- .../Impl/PatternValidatorTests.cs | 4 +- .../Support/ScopedNodeJsonTests.cs | 61 +++++++++++++++++++ 7 files changed, 133 insertions(+), 54 deletions(-) create mode 100644 src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs create mode 100644 test/Firely.Fhir.Validation.Tests/Support/ScopedNodeJsonTests.cs diff --git a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs index ce4b488e..d3354239 100644 --- a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs @@ -20,45 +20,45 @@ namespace Firely.Fhir.Validation [DataContract] public class FixedValidator : IValidatable { + private readonly JToken _fixedJToken; + /// - /// The fixed value to compare an instance against. + /// The fixed value to compare against. /// [DataMember] - public IScopedNode FixedValue { get; private set; } + public DataType FixedValue { get; } /// - /// Initializes a new FixedValidator given the fixed value. + /// Initializes a new FixedValidator given a (primitive) .NET value. /// - public FixedValidator(IScopedNode fixedValue) + public FixedValidator(DataType fixedValue) { FixedValue = fixedValue ?? throw new ArgumentNullException(nameof(fixedValue)); + _fixedJToken = FixedValue.ToJToken(); } - /// - /// Initializes a new FixedValidator given a (primitive) .NET value. - /// - /// The .NET primitive will be turned into a based - /// fixed value using , so this constructor - /// supports any conversion done there. - public FixedValidator(DataType fixedValue) : this(fixedValue.ToScopedNode()) { } - /// public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { - if (!input.IsExactlyEqualTo(FixedValue, ignoreOrder: true)) + var fixedValue = FixedValue.ToScopedNode(); + if (!input.IsExactlyEqualTo(fixedValue, ignoreOrder: true)) { return new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, - $"Value '{displayValue(input)}' is not exactly equal to fixed value '{displayValue(FixedValue)}'") + $"Value '{displayValue(input)}' is not exactly equal to fixed value '{displayJToken(_fixedJToken)}'") .AsResult(s); } return ResultReport.SUCCESS; - static string displayValue(IScopedNode te) => - te.Children().Any() ? te.ToJson() : te.Value.ToString()!; + static string displayValue(IScopedNode te) => te.Children().Any() ? te.ToJson() : te.Value.ToString()!; + + static string displayJToken(JToken jToken) => + jToken is JValue val + ? val.ToString() + : jToken.ToString(Newtonsoft.Json.Formatting.None); } /// - public JToken ToJson() => new JProperty($"Fixed[{FixedValue.InstanceType}]", FixedValue.ToPropValue()); + public JToken ToJson() => new JProperty($"Fixed[{FixedValue.TypeName}]", _fixedJToken); } } diff --git a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs index 1143abc7..ed63b835 100644 --- a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs @@ -23,40 +23,41 @@ namespace Firely.Fhir.Validation [DataContract] public class PatternValidator : IValidatable { + private readonly JToken _patternJToken; + /// - /// The pattern the instance will be validated against. + /// The pattern value to compare against. /// [DataMember] - public IScopedNode PatternValue { get; private set; } + public DataType PatternValue { get; } /// - /// Initializes a new PatternValidator given a pattern. + /// Initializes a new PatternValidator given a pattern using a (primitive) .NET value. /// - public PatternValidator(IScopedNode patternValue) + public PatternValidator(DataType patternValue) { PatternValue = patternValue ?? throw new ArgumentNullException(nameof(patternValue)); + _patternJToken = PatternValue.ToJToken(); } - /// - /// Initializes a new PatternValidator given a pattern using a (primitive) .NET value. - /// - /// The .NET primitive will be turned into a based - /// pattern using , so this constructor - /// supports any conversion done there. - public PatternValidator(DataType patternPrimitive) : this(patternPrimitive.ToScopedNode()) { } - /// public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationState s) { - var result = input.Matches(PatternValue) + var patternValue = PatternValue.ToScopedNode(); + var result = input.Matches(patternValue) ? ResultReport.SUCCESS - : new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, $"Value does not match pattern '{PatternValue.ToJson()}") + : new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, $"Value does not match pattern '{displayJToken(_patternJToken)}") .AsResult(s); return result; + + static string displayJToken(JToken jToken) => + jToken is JValue val + ? val.ToString() + : jToken.ToString(Newtonsoft.Json.Formatting.None); } /// - public JToken ToJson() => new JProperty($"pattern[{PatternValue.InstanceType}]", PatternValue.ToPropValue()); + public JToken ToJson() => new JProperty($"pattern[{PatternValue.TypeName}]", _patternJToken); } } diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs index 2a37bea4..bdc23cf8 100644 --- a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs @@ -2,7 +2,6 @@ using Hl7.Fhir.Introspection; using Hl7.Fhir.Model; using Hl7.Fhir.Specification; -using Newtonsoft.Json.Linq; using System; using System.Collections; using System.Collections.Generic; @@ -24,26 +23,10 @@ internal static class ScopedNodeExtensions public static IScopedNode ToScopedNode(this IReadOnlyDictionary node, ModelInspector? inspector = null) { inspector ??= ModelInspector.ForAssembly(node.GetType().Assembly); - return new ScopedNodeOnDictionary(inspector!, node.GetType().Name, node); + return new ScopedNodeOnDictionary(inspector, node.GetType().Name, node); } - public static string ToJson(this IScopedNode instance) => instance.ToJObject().ToString(Newtonsoft.Json.Formatting.None); - // TODO - this is a temporary solution, we need to find a better way to serialize the IScopedNode to JObject - public static JObject ToJObject(this IScopedNode instance) - { - var result = new JObject(); - - foreach (var child in instance.Children()) - { - if (child.Value is not null) - result.Add(child.Name, new JValue(child.Value)); - } - - return result; - } - - public static JToken ToPropValue(this IScopedNode e) => e.Value is not null ? new JValue(e.Value) : e.ToJObject(); } internal class ScopedNodeOnDictionary : IScopedNode diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs new file mode 100644 index 00000000..d43718a7 --- /dev/null +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs @@ -0,0 +1,34 @@ +using Hl7.Fhir.ElementModel; +using Hl7.Fhir.Introspection; +using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; +using Newtonsoft.Json.Linq; + +namespace Firely.Fhir.Validation +{ + internal static class ScopedNodeJsonExtensions + { + /// + /// Converts a IScopedNode to a Json string. + /// + /// + /// + /// Be careful using this function, because we move first to an ITypedElement. + public static string ToJson(this IScopedNode instance) + { +#pragma warning disable CS0618 // Type or member is obsolete + var node = instance.AsTypedElement(); +#pragma warning restore CS0618 // Type or member is obsolete + return node.ToJObject().ToString(Newtonsoft.Json.Formatting.None); + } + + public static JToken ToJToken(this DataType instance) + { + if (instance is PrimitiveType pt) + return pt.ObjectValue is not null ? new JValue(pt.ObjectValue) : JValue.CreateNull(); + + var modelInspector = ModelInspector.ForAssembly(instance.GetType().Assembly); + return instance.ToTypedElement(modelInspector).ToJObject(); + } + } +} \ No newline at end of file diff --git a/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs index cbb4c24e..bc791450 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/FixedValidatorTests.cs @@ -99,7 +99,7 @@ public class FixedValidatorTests : BasicValidatorTests public void InvalidConstructors() { #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Action action = () => _ = new FixedValidator((IScopedNode?)null); + Action action = () => _ = new FixedValidator((DataType?)null); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. action.Should().Throw(); } @@ -110,7 +110,7 @@ public void CorrectConstructor() var assertion = new FixedValidator(new Hl7.Fhir.Model.Integer(4)); assertion.Should().NotBeNull(); - assertion.FixedValue.Should().BeAssignableTo(); + assertion.FixedValue.Should().BeAssignableTo(); } [DataTestMethod] diff --git a/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs b/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs index 78328c69..1a3eac8b 100644 --- a/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs +++ b/test/Firely.Fhir.Validation.Tests/Impl/PatternValidatorTests.cs @@ -93,7 +93,7 @@ public class PatternValidatorTests : BasicValidatorTests public void InvalidConstructors() { #pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type. - Action action = () => _ = new PatternValidator((IScopedNode?)null); + Action action = () => _ = new PatternValidator((DataType?)null); #pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type. action.Should().Throw(); } @@ -104,7 +104,7 @@ public void CorrectConstructor() var assertion = new PatternValidator(new Hl7.Fhir.Model.Integer(4)); assertion.Should().NotBeNull(); - assertion.PatternValue.Should().BeAssignableTo(); + assertion.PatternValue.Should().BeAssignableTo(); } [DataTestMethod] diff --git a/test/Firely.Fhir.Validation.Tests/Support/ScopedNodeJsonTests.cs b/test/Firely.Fhir.Validation.Tests/Support/ScopedNodeJsonTests.cs new file mode 100644 index 00000000..e05103ec --- /dev/null +++ b/test/Firely.Fhir.Validation.Tests/Support/ScopedNodeJsonTests.cs @@ -0,0 +1,61 @@ +using FluentAssertions; +using Hl7.Fhir.Model; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Firely.Fhir.Validation.Tests.Support +{ + [TestClass] + public class ScopedNodeJsonTests + { + [TestMethod] + [DynamicData(nameof(AdditionData), DynamicDataSourceType.Method, DynamicDataDisplayName = nameof(GetTestDisplayNames))] + public void ToJTokenTest(DataType dataType, string expectedResult) + { + dataType.ToJToken().ToString().Should().Be(expectedResult); + } + + public static IEnumerable AdditionData() => GetDataTypeExamples().Select(x => new object[] { x.Item1, x.Item2 }); + + public static string GetTestDisplayNames(MethodInfo methodInfo, object[] values) => + $"{methodInfo.Name}({(values[0] is DataType dt ? dt.TypeName : values[0].GetType().Name)}, '{values[1]}')"; + + public static IEnumerable<(DataType, string)> GetDataTypeExamples() + { + yield return (new CodeableConcept() { Coding = [new("System", "code"), new("Sys2", "code2")] }, """ + { + "coding": [ + { + "system": "System", + "code": "code" + }, + { + "system": "Sys2", + "code": "code2" + } + ] + } + """); + yield return (new Coding("System", "Code"), """ + { + "system": "System", + "code": "Code" + } + """); + + yield return (new Money() { Currency = Money.Currencies.EUR, Value = 3 }, """ + { + "value": 3.0, + "currency": "EUR" + } + """); + yield return (new FhirBoolean(true), "True"); + yield return (new Code("male"), "male"); + yield return (new FhirDateTime(2023, 12, 05), "2023-12-05"); + yield return (new FhirDecimal(5), "5"); + yield return (new FhirString("a string"), "a string"); + } + } +} From 930414ad649b25ae506917005273ef087bc4eb80 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 5 Dec 2023 15:33:57 +0100 Subject: [PATCH 21/27] Adding extra test for ToScopedNode(Base) --- .../Support/ScopedNodeExtensions.cs | 2 - .../Support/ScopedNodeOnDictionaryTests.cs | 118 +++++++++++++++--- 2 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs index bdc23cf8..d4938b73 100644 --- a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs @@ -25,8 +25,6 @@ public static IScopedNode ToScopedNode(this IReadOnlyDictionary inspector ??= ModelInspector.ForAssembly(node.GetType().Assembly); return new ScopedNodeOnDictionary(inspector, node.GetType().Name, node); } - - } internal class ScopedNodeOnDictionary : IScopedNode diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs index 626b2d25..f867abf9 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs @@ -1,9 +1,9 @@ -using Hl7.Fhir.ElementModel; +using FluentAssertions; +using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; namespace Firely.Fhir.Validation.Compilation.Tests @@ -13,35 +13,111 @@ public class ScopedNodeOnDictionaryTests { [TestMethod] - public void MyTestMethod() + public void BaseToScopedNodeTests() { var humanName = new HumanName() { Family = "Brown", Given = new[] { "Joe" } }; var fhirBool = new FhirBoolean(true); fhirBool.AddExtension("http://example.org/extension", new FhirString("some extension")); var patient = new Patient() { ActiveElement = fhirBool }; - //patient.AddExtension("http://example.org/extension", new FhirString("some extension")); - //patient.AddExtension("http://example.org/otherextions", new FhirDecimal(12)); - //patient.Name.Add(new HumanName() { Family = "Doe", Given = ["John", "J."] }); + patient.AddExtension("http://example.org/extension", new FhirString("some extension")); + patient.AddExtension("http://example.org/otherextions", new FhirDecimal(12)); + patient.Name.Add(new HumanName() { Family = "Doe", Given = ["John", "J."] }); + var node = printNode(patient.ToScopedNode()); - Debug.WriteLine("ITypedElement"); - Debug.WriteLine(printNode(humanName.ToTypedElement())); - - Debug.WriteLine("====\nIScopedNode"); - - - //Debug.WriteLine(printDictionary(patient)); - - - Debug.WriteLine(printNode(humanName.ToScopedNode())); + node.Should().BeEquivalentTo(""" + { + Name: Patient + Value: + Type: Patient + } + { + Name: extension + Value: + Type: Extension + } + { + Name: url + Value: http://example.org/extension + Type: String + } + { + Name: value + Value: some extension + Type: string + } + { + Name: extension + Value: + Type: Extension + } + { + Name: url + Value: http://example.org/otherextions + Type: String + } + { + Name: value + Value: 12 + Type: decimal + } + { + Name: active + Value: True + Type: boolean + } + { + Name: extension + Value: + Type: Extension + } + { + Name: url + Value: http://example.org/extension + Type: String + } + { + Name: value + Value: some extension + Type: string + } + { + Name: name + Value: + Type: HumanName + } + { + Name: family + Value: Doe + Type: string + } + { + Name: given + Value: John + Type: string + } + { + Name: given + Value: J. + Type: string + } + + """); string printNode(IBaseElementNavigator node, int depth = 0) where T : IBaseElementNavigator { var indent = new string(' ', depth * 2); - var result = $"{indent}{{\n{indent} Name: {node.Name}\n{indent} Value: {node.Value}\n{indent} Type: {node.InstanceType}\n{indent}}}\n"; + var result = $$""" + {{indent}}{ + {{indent}} Name: {{node.Name}} + {{indent}} Value: {{node.Value}} + {{indent}} Type: {{node.InstanceType}} + {{indent}}} + + """; foreach (var child in node.Children()) result += printNode(child, depth + 1); return result; @@ -53,7 +129,13 @@ string printDictionary(IReadOnlyDictionary dict, int depth = 0) var indent = new string(' ', depth * 2); foreach (var node in dict) { - result += $"{indent}{{\n{indent} Key: {node.Key}\n{indent} Value: {printValue(node.Value, depth)}\n{indent}}}\n"; + result += $$""" + {{indent}}{ + {{indent}} Key: {{node.Key}} + {{indent}} Value: {{printValue(node.Value, depth)}} + {{indent}}} + + """; } return result; } From 17fc0e726514b75f7ef720cbd0f246460c52ffdc Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Tue, 5 Dec 2023 15:44:46 +0100 Subject: [PATCH 22/27] Cleaning up --- .../Support/ScopedNodeExtensions.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs index d4938b73..c4a578bb 100644 --- a/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeExtensions.cs @@ -49,7 +49,7 @@ public ScopedNodeOnDictionary(ModelInspector inspector, string rootName, IReadOn #pragma warning disable CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). public string? InstanceType { get; private set; } - public object? Value => _wrapped.TryGetValue("value", out var value) && _myClassMapping?.IsFhirPrimitive is true ? value : null; + public object? Value => _wrapped.TryGetValue("value", out var value) && isNETPrimitiveType(value) ? value : null; #pragma warning restore CS8766 // Nullability of reference types in return type doesn't match implicitly implemented member (possibly because of nullability attributes). public IEnumerable Children(string? name = null) @@ -68,29 +68,20 @@ public IEnumerable Children(string? name = null) if (child.Value is IList coll) { foreach (var childValue in coll) - //if (childValue is not null) - // yield return forObject(child.Key, childValue); if (childValue is IReadOnlyDictionary dict) yield return new ScopedNodeOnDictionary(_inspector, child.Key, dict, this); - } else if (child.Value is IReadOnlyDictionary re) yield return new ScopedNodeOnDictionary(_inspector, child.Key, re, this); - else if (child.Key != "value" && child.Value is string or bool or decimal or DateTimeOffset or int or long or byte[] or XHtml) + else if (child.Key != "value" && isNETPrimitiveType(child.Value)) yield return new ConstantElement(child.Key, child.Value.GetType().Name, child.Value, this); - //else - // yield return forObject(child.Key, child.Value); } - - //IScopedNode forObject(string name, object value) => value switch - //{ - // string or bool or decimal or DateTimeOffset or int or long or byte[] or XHtml => new ConstantElement(name, value.GetType().Name, value, this), - // IReadOnlyDictionary dict => new ScopedNodeOnDictionary(_inspector, name, dict, this), - // _ => throw new NotImplementedException() - //}; } + private bool isNETPrimitiveType(object a) + => a is string or bool or decimal or DateTimeOffset or int or long or byte[] or XHtml; + internal record ConstantElement(string Name, string InstanceType, object Value, IScopedNode Parent) : IScopedNode { public IEnumerable Children(string? name) => From a6c11fbf482c081328029fa620d2ea555622e3e6 Mon Sep 17 00:00:00 2001 From: Marco Visser Date: Wed, 6 Dec 2023 17:49:11 +0100 Subject: [PATCH 23/27] public static string ToJson moved to inline method at FixedValidator --- firely-validator-api-tests.props | 2 +- firely-validator-api.props | 2 +- src/Firely.Fhir.Validation/Impl/FixedValidator.cs | 11 ++++++++++- .../Impl/PatternValidator.cs | 2 +- .../Support/ScopedNodeJsonExtensions.cs | 14 -------------- .../Support/ScopedNodeOnDictionaryTests.cs | 3 +++ 6 files changed, 16 insertions(+), 18 deletions(-) diff --git a/firely-validator-api-tests.props b/firely-validator-api-tests.props index 3981111e..3a3cb484 100644 --- a/firely-validator-api-tests.props +++ b/firely-validator-api-tests.props @@ -10,7 +10,7 @@ - 5.4.1-20231122.7 + 5.4.1-20231206.2 5.1.0 diff --git a/firely-validator-api.props b/firely-validator-api.props index 1005ae63..7a2f733d 100644 --- a/firely-validator-api.props +++ b/firely-validator-api.props @@ -19,7 +19,7 @@ - 5.4.1-20231122.7 + 5.4.1-20231206.2 diff --git a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs index d3354239..998231f1 100644 --- a/src/Firely.Fhir.Validation/Impl/FixedValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/FixedValidator.cs @@ -6,6 +6,7 @@ using Hl7.Fhir.ElementModel; using Hl7.Fhir.Model; +using Hl7.Fhir.Serialization; using Hl7.Fhir.Support; using Newtonsoft.Json.Linq; using System; @@ -50,12 +51,20 @@ public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationS return ResultReport.SUCCESS; - static string displayValue(IScopedNode te) => te.Children().Any() ? te.ToJson() : te.Value.ToString()!; + static string displayValue(IScopedNode te) => te.Children().Any() ? ToJson(te) : te.Value.ToString()!; static string displayJToken(JToken jToken) => jToken is JValue val ? val.ToString() : jToken.ToString(Newtonsoft.Json.Formatting.None); + + static string ToJson(IScopedNode instance) + { +#pragma warning disable CS0618 // Type or member is obsolete + var node = instance.AsTypedElement(); +#pragma warning restore CS0618 // Type or member is obsolete + return node.ToJObject().ToString(Newtonsoft.Json.Formatting.None); + } } /// diff --git a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs index ed63b835..e72bd1e6 100644 --- a/src/Firely.Fhir.Validation/Impl/PatternValidator.cs +++ b/src/Firely.Fhir.Validation/Impl/PatternValidator.cs @@ -46,7 +46,7 @@ public ResultReport Validate(IScopedNode input, ValidationContext _, ValidationS var patternValue = PatternValue.ToScopedNode(); var result = input.Matches(patternValue) ? ResultReport.SUCCESS - : new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, $"Value does not match pattern '{displayJToken(_patternJToken)}") + : new IssueAssertion(Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, $"Value does not match pattern '{displayJToken(_patternJToken)}") // TODO: add value to message .AsResult(s); return result; diff --git a/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs b/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs index d43718a7..6c4eff13 100644 --- a/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs +++ b/src/Firely.Fhir.Validation/Support/ScopedNodeJsonExtensions.cs @@ -8,20 +8,6 @@ namespace Firely.Fhir.Validation { internal static class ScopedNodeJsonExtensions { - /// - /// Converts a IScopedNode to a Json string. - /// - /// - /// - /// Be careful using this function, because we move first to an ITypedElement. - public static string ToJson(this IScopedNode instance) - { -#pragma warning disable CS0618 // Type or member is obsolete - var node = instance.AsTypedElement(); -#pragma warning restore CS0618 // Type or member is obsolete - return node.ToJObject().ToString(Newtonsoft.Json.Formatting.None); - } - public static JToken ToJToken(this DataType instance) { if (instance is PrimitiveType pt) diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs index f867abf9..94b54c77 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/Support/ScopedNodeOnDictionaryTests.cs @@ -106,7 +106,9 @@ public void BaseToScopedNodeTests() """); +#pragma warning disable CS0618 // Type or member is obsolete string printNode(IBaseElementNavigator node, int depth = 0) where T : IBaseElementNavigator +#pragma warning restore CS0618 // Type or member is obsolete { var indent = new string(' ', depth * 2); @@ -123,6 +125,7 @@ string printNode(IBaseElementNavigator node, int depth = 0) where T : IBas return result; } + string printDictionary(IReadOnlyDictionary dict, int depth = 0) { var result = string.Empty; From b94c1b270c5bd1cf1cc5830ba9b2dce85061e4d4 Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Thu, 7 Dec 2023 14:38:13 +0100 Subject: [PATCH 24/27] First batch of changes after Marco's PR review. --- .../PublicAPI.Unshipped.txt | 17 ++- .../Schema/ValidationContext.cs | 30 ++++-- .../InMemoryExternalReferenceResolver.cs | 13 ++- .../PublicAPI.Unshipped.txt | 13 +-- .../PublicAPI.Unshipped.txt | 10 -- src/Hl7.Fhir.Validation.Shared/Validator.cs | 101 +++++------------- test/Benchmarks/ValidatorBenchmarks.cs | 4 +- .../ValidatorHighLevelApiTests.cs | 21 ++-- 8 files changed, 86 insertions(+), 123 deletions(-) diff --git a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt index d4f49c5c..7df5e0df 100644 --- a/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt +++ b/src/Firely.Fhir.Validation/PublicAPI.Unshipped.txt @@ -20,12 +20,14 @@ 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.ExternalReferenceResolver Firely.Fhir.Validation.IAssertion Firely.Fhir.Validation.IExternalReferenceResolver Firely.Fhir.Validation.IExternalReferenceResolver.ResolveAsync(string! reference) -> System.Threading.Tasks.Task! Firely.Fhir.Validation.IJsonSerializable Firely.Fhir.Validation.IJsonSerializable.ToJson() -> Newtonsoft.Json.Linq.JToken! +Firely.Fhir.Validation.InMemoryExternalReferenceResolver +Firely.Fhir.Validation.InMemoryExternalReferenceResolver.InMemoryExternalReferenceResolver() -> void +Firely.Fhir.Validation.InMemoryExternalReferenceResolver.ResolveAsync(string! reference) -> System.Threading.Tasks.Task! Firely.Fhir.Validation.IssueAssertion Firely.Fhir.Validation.IssueAssertion.AsResult(string! location) -> Firely.Fhir.Validation.ResultReport! Firely.Fhir.Validation.IssueAssertion.Equals(Firely.Fhir.Validation.IssueAssertion? other) -> bool @@ -71,6 +73,17 @@ Firely.Fhir.Validation.ValidateBestPracticesSeverity Firely.Fhir.Validation.ValidateBestPracticesSeverity.Error = 1 -> Firely.Fhir.Validation.ValidateBestPracticesSeverity Firely.Fhir.Validation.ValidateBestPracticesSeverity.Warning = 0 -> Firely.Fhir.Validation.ValidateBestPracticesSeverity Firely.Fhir.Validation.ValidateCodeServiceFailureHandler +Firely.Fhir.Validation.ValidationContext +Firely.Fhir.Validation.ValidationContext.ConstraintBestPractices -> Firely.Fhir.Validation.ValidateBestPracticesSeverity +Firely.Fhir.Validation.ValidationContext.ExcludeFilters -> System.Collections.Generic.ICollection!>! +Firely.Fhir.Validation.ValidationContext.FhirPathCompiler -> Hl7.FhirPath.FhirPathCompiler? +Firely.Fhir.Validation.ValidationContext.FollowExtensionUrl -> Firely.Fhir.Validation.ExtensionUrlFollower? +Firely.Fhir.Validation.ValidationContext.HandleValidateCodeServiceFailure -> Firely.Fhir.Validation.ValidateCodeServiceFailureHandler? +Firely.Fhir.Validation.ValidationContext.IncludeFilters -> System.Collections.Generic.ICollection!>! +Firely.Fhir.Validation.ValidationContext.SelectMetaProfiles -> Firely.Fhir.Validation.MetaProfileSelector? +Firely.Fhir.Validation.ValidationContext.TypeNameMapper -> Firely.Fhir.Validation.TypeNameMapper? +Firely.Fhir.Validation.ValidationContext.ValidateCodeService -> Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! +Firely.Fhir.Validation.ValidationContext.ValidationContext() -> void Firely.Fhir.Validation.ValidationResult Firely.Fhir.Validation.ValidationResult.Failure = 1 -> Firely.Fhir.Validation.ValidationResult Firely.Fhir.Validation.ValidationResult.Success = 0 -> Firely.Fhir.Validation.ValidationResult @@ -98,12 +111,12 @@ static Firely.Fhir.Validation.ValidationResultExtensions.Combine(this Firely.Fhi static readonly Firely.Fhir.Validation.ResultReport.FAILURE -> Firely.Fhir.Validation.ResultReport! static readonly Firely.Fhir.Validation.ResultReport.SUCCESS -> Firely.Fhir.Validation.ResultReport! static readonly Firely.Fhir.Validation.ResultReport.UNDECIDED -> Firely.Fhir.Validation.ResultReport! +static readonly Firely.Fhir.Validation.ValidationContext.DEFAULTEXCLUDEFILTER -> System.Predicate! virtual Firely.Fhir.Validation.Canonical.$() -> Firely.Fhir.Validation.Canonical! 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.ExtensionUrlFollower.Invoke(string! location, Firely.Fhir.Validation.Canonical? url) -> Firely.Fhir.Validation.ExtensionUrlHandling -virtual Firely.Fhir.Validation.ExternalReferenceResolver.Invoke(string! reference, string! location) -> Hl7.Fhir.ElementModel.ITypedElement? 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? virtual Firely.Fhir.Validation.ValidateCodeServiceFailureHandler.Invoke(Hl7.Fhir.Specification.Terminology.ValidateCodeParameters! p, Hl7.Fhir.Rest.FhirOperationException! e) -> Firely.Fhir.Validation.TerminologyServiceExceptionResult \ No newline at end of file diff --git a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs index 6156165d..69f21522 100644 --- a/src/Firely.Fhir.Validation/Schema/ValidationContext.cs +++ b/src/Firely.Fhir.Validation/Schema/ValidationContext.cs @@ -21,17 +21,27 @@ namespace Firely.Fhir.Validation /// /// Generally, you will configure one such within a /// subsystem doing validation. - internal class ValidationContext + public class ValidationContext { /// /// Initializes a new ValidationContext with the minimal dependencies. /// - public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationTerminologyService validateCodeService) + internal ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationTerminologyService validateCodeService) { ElementSchemaResolver = schemaResolver ?? throw new ArgumentNullException(nameof(schemaResolver)); ValidateCodeService = validateCodeService ?? throw new ArgumentNullException(nameof(validateCodeService)); } + /// + /// Initializes a new ValidationContext with no dependencies. At least and + /// must be set before the validator can be used. + /// + public ValidationContext() + { + ElementSchemaResolver = new NoopSchemaResolver(); + ValidateCodeService = new NoopTerminologyService(); + } + /// /// An that is used when the validator must validate a code against a /// terminology service. @@ -57,7 +67,7 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// Note that this is not just for "external" schemas (e.g. those referred to by Extension.url). The much more common /// case is where the schema for a resource references the schema for the type of one of its elements. /// - public IElementSchemaResolver ElementSchemaResolver; + internal IElementSchemaResolver ElementSchemaResolver; /// /// A function that resolves an url to an external instance, parsed as an . @@ -68,7 +78,7 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// to contained resources will always be followed. If this property is not set, references will be /// ignored. /// - public ExternalReferenceResolver? ResolveExternalReference = null; + internal ExternalReferenceResolver? ResolveExternalReference = null; /// /// An instance of the FhirPath compiler to use when evaluating constraints @@ -106,32 +116,32 @@ public ValidationContext(IElementSchemaResolver schemaResolver, ICodeValidationT /// A function to exclude the assertion in the validation or not. If the function is left empty (null) then all the /// assertions are processed in the validation. /// - public ICollection> ExcludeFilters = new List> { DEFAULT_EXCLUDE_FILTER }; + public ICollection> ExcludeFilters = new List> { DEFAULTEXCLUDEFILTER }; /// /// The default for , which will exclude FhirPath invariant dom-6 from triggering. /// - public static readonly Predicate DEFAULT_EXCLUDE_FILTER = a => a is FhirPathValidator fhirPathAssertion && fhirPathAssertion.Key == "dom-6"; + public static readonly Predicate DEFAULTEXCLUDEFILTER = a => a is FhirPathValidator fhirPathAssertion && fhirPathAssertion.Key == "dom-6"; /// /// Determines whether a given assertion is included in the validation. The outcome is determined by /// and . /// - public bool Filter(IAssertion a) => + internal bool Filter(IAssertion a) => (IncludeFilters.Count == 0 || IncludeFilters.Any(inc => inc(a))) && !ExcludeFilters.Any(exc => exc(a)); /// /// Whether to add trace messages to the validation result. /// - public bool TraceEnabled = false; + internal bool TraceEnabled = false; /// /// Invokes a factory method for assertions only when tracing is on. /// /// /// - public ResultReport TraceResult(Func p) => + internal ResultReport TraceResult(Func p) => TraceEnabled ? p().AsResult() : ResultReport.SUCCESS; /// @@ -217,7 +227,7 @@ public enum TerminologyServiceExceptionResult /// /// A delegate that resolves a reference to another resource, outside of the current instance under validation. /// - public delegate ITypedElement? ExternalReferenceResolver(string reference, string location); + internal delegate ITypedElement? ExternalReferenceResolver(string reference, string location); /// /// A function that determines which profiles in the validator should use to validate this instance. diff --git a/src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs b/src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs index 5d693b0e..de8bfb8b 100644 --- a/src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs +++ b/src/Firely.Fhir.Validation/Support/InMemoryExternalReferenceResolver.cs @@ -1,19 +1,22 @@ /* - * Copyright (C) 2022, Firely (info@fire.ly) - All Rights Reserved + * Copyright (C) 2023, Firely (info@fire.ly) - All Rights Reserved * Proprietary and confidential. Unauthorized copying of this file, * via any medium is strictly prohibited. */ -using Hl7.Fhir.Model; using System.Collections.Generic; using System.Threading.Tasks; -namespace Firely.Fhir.Validation.Tests +namespace Firely.Fhir.Validation { - internal class InMemoryExternalReferenceResolver : Dictionary, IExternalReferenceResolver + /// + /// An implementation of that uses an in-memory dictionary. + /// + public class InMemoryExternalReferenceResolver : Dictionary, IExternalReferenceResolver { + /// public Task ResolveAsync(string reference) => - System.Threading.Tasks.Task.FromResult(this.TryGetValue(reference, out var resource) ? (object)resource : null); + Task.FromResult(TryGetValue(reference, out var resource) ? resource : null); } } \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt b/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt index 804403a0..019838f6 100644 --- a/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt +++ b/src/Hl7.Fhir.Validation.R4/PublicAPI.Unshipped.txt @@ -1,13 +1,6 @@ #nullable enable Firely.Fhir.Validation.Validator -Firely.Fhir.Validation.Validator.ConstraintBestPractices -> Firely.Fhir.Validation.ValidateBestPracticesSeverity -Firely.Fhir.Validation.Validator.FhirPathCompiler -> Hl7.FhirPath.FhirPathCompiler? -Firely.Fhir.Validation.Validator.FollowExtensionUrl -> Firely.Fhir.Validation.ExtensionUrlFollower? -Firely.Fhir.Validation.Validator.ResolveExternalReference -> Firely.Fhir.Validation.IExternalReferenceResolver? -Firely.Fhir.Validation.Validator.SelectMetaProfiles -> Firely.Fhir.Validation.MetaProfileSelector? Firely.Fhir.Validation.Validator.SkipConstraintValidation -> bool -Firely.Fhir.Validation.Validator.TerminologyService -> Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! -Firely.Fhir.Validation.Validator.TypeNameMapper -> Firely.Fhir.Validation.TypeNameMapper? -Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string? profile = null) -> Firely.Fhir.Validation.ResultReport! -Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string? profile = null) -> Firely.Fhir.Validation.ResultReport! -Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver = null, Hl7.FhirPath.FhirPathCompiler? fhirPathCompiler = null) -> void \ No newline at end of file +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string? profile = null) -> Hl7.Fhir.Model.OperationOutcome! +Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string? profile = null) -> Hl7.Fhir.Model.OperationOutcome! +Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver = null, Firely.Fhir.Validation.ValidationContext? settings = null) -> void \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt b/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt index 804403a0..d739b63c 100644 --- a/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt +++ b/src/Hl7.Fhir.Validation.STU3/PublicAPI.Unshipped.txt @@ -1,13 +1,3 @@ #nullable enable Firely.Fhir.Validation.Validator -Firely.Fhir.Validation.Validator.ConstraintBestPractices -> Firely.Fhir.Validation.ValidateBestPracticesSeverity -Firely.Fhir.Validation.Validator.FhirPathCompiler -> Hl7.FhirPath.FhirPathCompiler? -Firely.Fhir.Validation.Validator.FollowExtensionUrl -> Firely.Fhir.Validation.ExtensionUrlFollower? -Firely.Fhir.Validation.Validator.ResolveExternalReference -> Firely.Fhir.Validation.IExternalReferenceResolver? -Firely.Fhir.Validation.Validator.SelectMetaProfiles -> Firely.Fhir.Validation.MetaProfileSelector? Firely.Fhir.Validation.Validator.SkipConstraintValidation -> bool -Firely.Fhir.Validation.Validator.TerminologyService -> Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! -Firely.Fhir.Validation.Validator.TypeNameMapper -> Firely.Fhir.Validation.TypeNameMapper? -Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.ElementModel.ElementNode! instance, string? profile = null) -> Firely.Fhir.Validation.ResultReport! -Firely.Fhir.Validation.Validator.Validate(Hl7.Fhir.Model.Resource! instance, string? profile = null) -> Firely.Fhir.Validation.ResultReport! -Firely.Fhir.Validation.Validator.Validator(Hl7.Fhir.Specification.Source.IAsyncResourceResolver! resourceResolver, Hl7.Fhir.Specification.Terminology.ICodeValidationTerminologyService! terminologyService, Firely.Fhir.Validation.IExternalReferenceResolver? referenceResolver = null, Hl7.FhirPath.FhirPathCompiler? fhirPathCompiler = null) -> void \ No newline at end of file diff --git a/src/Hl7.Fhir.Validation.Shared/Validator.cs b/src/Hl7.Fhir.Validation.Shared/Validator.cs index 62bb42f5..8526d4d0 100644 --- a/src/Hl7.Fhir.Validation.Shared/Validator.cs +++ b/src/Hl7.Fhir.Validation.Shared/Validator.cs @@ -9,7 +9,6 @@ using Hl7.Fhir.Specification.Source; using Hl7.Fhir.Specification.Terminology; using Hl7.Fhir.Utility; -using Hl7.FhirPath; using System; namespace Firely.Fhir.Validation @@ -26,45 +25,39 @@ public class Validator /// An that is used when the validator must validate a code against a /// terminology service. /// A that resolves an url to an external instance, represented as a Model POCO. - /// Optionally, a FhirPath compiler to use when evaluating constraints - /// (provide this if you have custom functions included in the symbol table). + /// A that contains settings for the validator. public Validator( IAsyncResourceResolver resourceResolver, ICodeValidationTerminologyService terminologyService, IExternalReferenceResolver? referenceResolver = null, - FhirPathCompiler? fhirPathCompiler = null) + ValidationContext? settings = null) { - _elementSchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(resourceResolver); + var elementSchemaResolver = StructureDefinitionToElementSchemaResolver.CreatedCached(resourceResolver); - TerminologyService = terminologyService; - ResolveExternalReference = referenceResolver; - FhirPathCompiler = fhirPathCompiler; - } - - private ValidationContext buildContext() - { - var validationContext = new ValidationContext(_elementSchemaResolver, TerminologyService) - { - FhirPathCompiler = FhirPathCompiler, - ResolveExternalReference = ResolveExternalReference is not null ? resolve : null, - ConstraintBestPractices = ConstraintBestPractices, - SelectMetaProfiles = SelectMetaProfiles, - FollowExtensionUrl = FollowExtensionUrl, - TypeNameMapper = TypeNameMapper - }; - - if (SkipConstraintValidation) - validationContext.ExcludeFilters.Add(ass => ass is FhirPathValidator); + _settings = settings ?? new ValidationContext(); - return validationContext; + // Set the internal settings that we have hidden in this high-level API. + _settings.ElementSchemaResolver = elementSchemaResolver; + _settings.ValidateCodeService = terminologyService; + _settings.ResolveExternalReference = referenceResolver is not null ? resolve : null; ITypedElement? resolve(string reference, string location) { - var r = TaskHelper.Await(() => ResolveExternalReference.ResolveAsync(reference)); + var r = TaskHelper.Await(() => referenceResolver.ResolveAsync(reference)); return toTypedElement(r); } } + private readonly Predicate _fhirPathFilter = ass => ass is FhirPathValidator; + + private void updateContext() + { + if (SkipConstraintValidation && !_settings.ExcludeFilters.Contains(_fhirPathFilter)) + _settings.ExcludeFilters.Add(_fhirPathFilter); + else if (!SkipConstraintValidation && _settings.ExcludeFilters.Contains(_fhirPathFilter)) + _settings.ExcludeFilters.Remove(_fhirPathFilter); + } + private static ITypedElement? toTypedElement(object? o) => o switch { @@ -74,51 +67,7 @@ private ValidationContext buildContext() _ => throw new ArgumentException("Reference resolver must return either a Resource or ElementNode.") }; - private readonly IElementSchemaResolver _elementSchemaResolver; - - /// - /// An that is used when the validator must validate a code against a - /// terminology service. - /// - public ICodeValidationTerminologyService TerminologyService; - - - /// - /// A that resolves an url to an external instance, represented as a Model POCO. - /// - public IExternalReferenceResolver? ResolveExternalReference = null; - - /// - /// Optionally, a FhirPath compiler to use when evaluating constraints. - /// - /// Provide this if you have custom functions included in the symbol table. - public FhirPathCompiler? FhirPathCompiler = null; - - /// - /// Determines how to deal with failures of FhirPath constraints marked as "best practice". Default is . - /// - /// See , and - /// https://www.hl7.org/fhir/best-practices.html for more information. - public ValidateBestPracticesSeverity ConstraintBestPractices = ValidateBestPracticesSeverity.Warning; - - /// - /// The to invoke when a is encountered. If not set, the list of profiles - /// is used as encountered in the instance. - /// - public MetaProfileSelector? SelectMetaProfiles = null; - - /// - /// The to invoke when an is encountered in an instance. - /// If not set, then a validation of an Extension will warn if the extension cannot be resolved, or will return an error when - /// the extension cannot be resolved and is a modififier extension. - /// - public ExtensionUrlFollower? FollowExtensionUrl = null; - - /// - /// A function that maps a type name found in TypeRefComponent.Code to a resolvable canonical. - /// If not set, it will prefix the type with the standard http://hl7.org/fhir/StructureDefinition prefix. - /// - public TypeNameMapper? TypeNameMapper = null; + private readonly ValidationContext _settings; /// /// StructureDefinition may contain FhirPath constraints to enfore invariants in the data that cannot @@ -133,7 +82,7 @@ private ValidationContext buildContext() /// A report containing the issues found during validation. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters // Suppressing this issue since this will be a single method call when we introduce IScopedNode. - public ResultReport Validate(Resource instance, string? profile = null) => Validate(instance.ToTypedElement(ModelInfo.ModelInspector).AsScopedNode(), profile); + public OperationOutcome Validate(Resource instance, string? profile = null) => Validate(instance.ToTypedElement(ModelInfo.ModelInspector).AsScopedNode(), profile); #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters /// @@ -141,16 +90,16 @@ private ValidationContext buildContext() /// /// A report containing the issues found during validation. #pragma warning disable RS0026 // Do not add multiple public overloads with optional parameters - public ResultReport Validate(ElementNode instance, string? profile = null) => Validate(instance.AsScopedNode(), profile); + public OperationOutcome Validate(ElementNode instance, string? profile = null) => Validate(instance.AsScopedNode(), profile); #pragma warning restore RS0026 // Do not add multiple public overloads with optional parameters - internal ResultReport Validate(IScopedNode sn, string? profile = null) + internal OperationOutcome Validate(IScopedNode sn, string? profile = null) { profile ??= Canonical.ForCoreType(sn.InstanceType).ToString(); var validator = new SchemaReferenceValidator(profile); - var ctx = buildContext(); - return validator.Validate(sn, ctx); + updateContext(); + return validator.Validate(sn, _settings).ToOperationOutcome(); } } } diff --git a/test/Benchmarks/ValidatorBenchmarks.cs b/test/Benchmarks/ValidatorBenchmarks.cs index f743c68c..5b7c2050 100644 --- a/test/Benchmarks/ValidatorBenchmarks.cs +++ b/test/Benchmarks/ValidatorBenchmarks.cs @@ -40,7 +40,7 @@ public void GlobalSetup() // To avoid warnings about bi-model distributions, run the (slow) first-time run here in setup var cold = validateWip(TestResource!, InstanceTypeProfile!, TestResolver!); - Debug.Assert(cold.IsSuccessful); + Debug.Assert(cold.Success); var oldCold = validateCurrent(TestResource!, InstanceTypeProfile!, TestResolver!); Debug.Assert(oldCold.Success); @@ -58,7 +58,7 @@ public void WipValidator() _ = validateWip(TestResource!, InstanceTypeProfile!, TestResolver!); } - private static ResultReport validateWip(ElementNode typedElement, string schema, IResourceResolver rr) + private static Hl7.Fhir.Model.OperationOutcome validateWip(ElementNode typedElement, string schema, IResourceResolver rr) { var arr = rr.AsAsync(); var ts = new LocalTerminologyService(arr); diff --git a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs index 6a21244e..e74ef506 100644 --- a/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs +++ b/test/Firely.Fhir.Validation.Compilation.Tests.Shared/ValidatorHighLevelApiTests.cs @@ -8,6 +8,8 @@ using FluentAssertions; using Hl7.Fhir.Model; using Hl7.Fhir.Support; +using System.Collections.Generic; +using System.Linq; using Xunit; // Until we have Marco's IScopedNodeOnPoco adapter, I cannot write R5 tests using just the "shared" R4+ validator. @@ -25,6 +27,9 @@ public ValidatorHighLevelApiTests(SchemaBuilderFixture fixture) _fixture = fixture; } + private IEnumerable getErrorCodes(OperationOutcome oo) => oo.Issue.SelectMany(i => i.Details.Coding).Where(cd => cd.System == "http://hl7.org/fhir/dotnet-api-operation-outcome").Select(c => c.Code.ToString()); + + [Fact] public void ValidatesAndReportsError() { @@ -32,12 +37,12 @@ public void ValidatesAndReportsError() var validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, null); var result = validator.Validate(p, Canonical.ForCoreType("Patient").ToString()); - result.Errors.Should().ContainSingle().Which.IssueNumber.Should().Be(Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code); - result.IsSuccessful.Should().BeFalse(); + getErrorCodes(result).Should().ContainSingle(Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code.ToString()); + result.Success.Should().BeFalse(); result = validator.Validate(p); - result.Errors.Should().ContainSingle().Which.IssueNumber.Should().Be(Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code); - result.IsSuccessful.Should().BeFalse(); + getErrorCodes(result).Should().ContainSingle(Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE.Code.ToString()); + result.Success.Should().BeFalse(); } [Fact] @@ -46,7 +51,7 @@ public void NoReferenceResolution() // First, without a resolver, we do not follow references and thus do not validate the referenced resource. var p = new Patient() { ManagingOrganization = new ResourceReference("http://example.com/orgA") }; var validator = new Validator(_fixture.ResourceResolver, _fixture.ValidateCodeService, null); - validator.Validate(p, Canonical.ForCoreType("Patient").ToString()).IsSuccessful.Should().BeTrue(); + validator.Validate(p, Canonical.ForCoreType("Patient").ToString()).Success.Should().BeTrue(); // Now with reference resolution var or = new InMemoryExternalReferenceResolver() { ["http://example.com/orgA"] = new Organization() }; @@ -54,7 +59,7 @@ public void NoReferenceResolution() var result = validator.Validate(p, Canonical.ForCoreType("Patient").ToString()); // Organization should fail constraint org-1 - result.Errors.Should().ContainSingle().Which.IssueNumber.Should().Be(Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT.Code); + getErrorCodes(result).Should().ContainSingle(Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT.Code.ToString()); } [Fact] @@ -65,12 +70,12 @@ public void SkipConstraintValidation() // validate with constraint validation, it should fail on org-1 var result = validator.Validate(o, Canonical.ForCoreType("Organization").ToString()); - result.Errors.Should().ContainSingle().Which.IssueNumber.Should().Be(Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT.Code); + getErrorCodes(result).Should().ContainSingle(Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT.Code.ToString()); // now, skip constraint validation validator.SkipConstraintValidation = true; result = validator.Validate(o, Canonical.ForCoreType("Organization").ToString()); - result.IsSuccessful.Should().BeTrue(); + result.Success.Should().BeTrue(); } } } From e4bc64f368c52bc461567f0aef144e49f363654f Mon Sep 17 00:00:00 2001 From: Ewout Kramer Date: Thu, 7 Dec 2023 15:05:53 +0100 Subject: [PATCH 25/27] Renamed directory hl7.fhir.validator to firely.fhir.validator. --- Firely.Validator.API.sln | 11 +++++------ .../Firely.Fhir.Validation.R4.csproj | 2 +- .../Properties/AssemblyInfo.cs | 0 .../PublicAPI.Shipped.txt | 0 .../PublicAPI.Unshipped.txt | 0 .../Firely.Fhir.Validation.STU3.csproj | 2 +- .../Properties/AssemblyInfo.cs | 0 .../PublicAPI.Shipped.txt | 0 .../PublicAPI.Unshipped.txt | 0 .../Firely.Fhir.Validation.Shared.projitems} | 2 +- .../Firely.Fhir.Validation.Shared.shproj | 0 .../Validator.cs | 0 test/Benchmarks/Benchmarks.csproj | 2 +- ...Firely.Fhir.Validation.Compilation.R4.Tests.csproj | 2 +- ...rely.Fhir.Validation.Compilation.STU3.Tests.csproj | 2 +- 15 files changed, 11 insertions(+), 12 deletions(-) rename src/{Hl7.Fhir.Validation.R4 => Firely.Fhir.Validation.R4}/Firely.Fhir.Validation.R4.csproj (89%) rename src/{Hl7.Fhir.Validation.R4 => Firely.Fhir.Validation.R4}/Properties/AssemblyInfo.cs (100%) rename src/{Hl7.Fhir.Validation.R4 => Firely.Fhir.Validation.R4}/PublicAPI.Shipped.txt (100%) rename src/{Hl7.Fhir.Validation.R4 => Firely.Fhir.Validation.R4}/PublicAPI.Unshipped.txt (100%) rename src/{Hl7.Fhir.Validation.STU3 => Firely.Fhir.Validation.STU3}/Firely.Fhir.Validation.STU3.csproj (89%) rename src/{Hl7.Fhir.Validation.STU3 => Firely.Fhir.Validation.STU3}/Properties/AssemblyInfo.cs (100%) rename src/{Hl7.Fhir.Validation.STU3 => Firely.Fhir.Validation.STU3}/PublicAPI.Shipped.txt (100%) rename src/{Hl7.Fhir.Validation.STU3 => Firely.Fhir.Validation.STU3}/PublicAPI.Unshipped.txt (100%) rename src/{Hl7.Fhir.Validation.Shared/Hl7.Fhir.Validation.Shared.projitems => Firely.Fhir.Validation.Shared/Firely.Fhir.Validation.Shared.projitems} (88%) rename src/{Hl7.Fhir.Validation.Shared => Firely.Fhir.Validation.Shared}/Firely.Fhir.Validation.Shared.shproj (100%) rename src/{Hl7.Fhir.Validation.Shared => Firely.Fhir.Validation.Shared}/Validator.cs (100%) diff --git a/Firely.Validator.API.sln b/Firely.Validator.API.sln index d26b2270..92283642 100644 --- a/Firely.Validator.API.sln +++ b/Firely.Validator.API.sln @@ -27,7 +27,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Comp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "test\Benchmarks\Benchmarks.csproj", "{1B02120E-C942-448D-A444-6F7EB1816C95}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.R4", "src\Hl7.Fhir.Validation.R4\Firely.Fhir.Validation.R4.csproj", "{22520A59-7EBF-4740-A66C-3651742FAC0B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.R4", "src\Firely.Fhir.Validation.R4\Firely.Fhir.Validation.R4.csproj", "{22520A59-7EBF-4740-A66C-3651742FAC0B}" EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Firely.Fhir.Validation.Compilation.Shared", "src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.shproj", "{7794C7D2-EA50-405D-A61A-23F8ECF4228B}" EndProject @@ -41,9 +41,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Comp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.Compilation.R4.Tests", "test\Firely.Fhir.Validation.Compilation.Tests.R4\Firely.Fhir.Validation.Compilation.R4.Tests.csproj", "{B5968107-6E13-46C1-AB04-7FCEFFF179C7}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Firely.Fhir.Validation.Shared", "src\Hl7.Fhir.Validation.Shared\Firely.Fhir.Validation.Shared.shproj", "{21BF806C-BBE6-4A33-91F9-BF3AB5F1E70B}" +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Firely.Fhir.Validation.Shared", "src\Firely.Fhir.Validation.Shared\Firely.Fhir.Validation.Shared.shproj", "{21BF806C-BBE6-4A33-91F9-BF3AB5F1E70B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.STU3", "src\Hl7.Fhir.Validation.STU3\Firely.Fhir.Validation.STU3.csproj", "{4C9C5275-0C05-4CDE-A74C-1B43245252FB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Firely.Fhir.Validation.STU3", "src\Firely.Fhir.Validation.STU3\Firely.Fhir.Validation.STU3.csproj", "{4C9C5275-0C05-4CDE-A74C-1B43245252FB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -129,9 +129,8 @@ Global EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems*{052b9175-3a5b-46ca-b677-8d082c9a6063}*SharedItemsImports = 5 - src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.projitems*{21bf806c-bbe6-4a33-91f9-bf3ab5f1e70b}*SharedItemsImports = 13 - src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.projitems*{22520a59-7ebf-4740-a66c-3651742fac0b}*SharedItemsImports = 5 - src\Hl7.Fhir.Validation.Shared\Hl7.Fhir.Validation.Shared.projitems*{4c9c5275-0c05-4cde-a74c-1b43245252fb}*SharedItemsImports = 5 + src\Firely.Fhir.Validation.Shared\Firely.Fhir.Validation.Shared.projitems*{22520a59-7ebf-4740-a66c-3651742fac0b}*SharedItemsImports = 5 + src\Firely.Fhir.Validation.Shared\Firely.Fhir.Validation.Shared.projitems*{4c9c5275-0c05-4cde-a74c-1b43245252fb}*SharedItemsImports = 5 src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems*{7391862b-ac99-40c5-a616-dc83edd83a88}*SharedItemsImports = 5 src\Firely.Fhir.Validation.Compilation.Shared\Firely.Fhir.Validation.Compilation.Shared.projitems*{7794c7d2-ea50-405d-a61a-23f8ecf4228b}*SharedItemsImports = 13 test\Firely.Fhir.Validation.Compilation.Tests.Shared\Firely.Fhir.Validation.Compilation.Tests.Shared.projitems*{994a42c0-396b-48b6-81a0-4e622460ecab}*SharedItemsImports = 5 diff --git a/src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj b/src/Firely.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj similarity index 89% rename from src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj rename to src/Firely.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj index 9fcb8d84..fc21496e 100644 --- a/src/Hl7.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj +++ b/src/Firely.Fhir.Validation.R4/Firely.Fhir.Validation.R4.csproj @@ -1,5 +1,5 @@  - +