private static OperationOutcome ValidateMatch(this Validator validator, Match match, IElementNavigator parent) { var outcome = new OperationOutcome(); var definition = match.Definition.Current; if (definition.Min == null) { validator.Trace(outcome, $"Element definition does not specify a 'min' value, which is required. Cardinality has not been validated", Issue.PROFILE_ELEMENTDEF_CARDINALITY_MISSING, parent); } else if (definition.Max == null) { validator.Trace(outcome, $"Element definition does not specify a 'max' value, which is required. Cardinality has not been validated", Issue.PROFILE_ELEMENTDEF_CARDINALITY_MISSING, parent); } var cardinality = Cardinality.FromElementDefinition(definition); var bucket = BucketFactory.CreateRoot(match.Definition, validator); foreach (var element in match.InstanceElements) { var success = bucket.Add(element); // For the "root" slice group (=the original core element that was sliced, not resliced) // any element that does not match is an error // Since the ChildNameMatcher currently does the matching, this will never go wrong } outcome.Add(bucket.Validate(validator, parent)); return(outcome); }
private static CompiledExpression getExecutableConstraint(Validator v, OperationOutcome outcome, IElementNavigator instance, ElementDefinition.ConstraintComponent constraintElement) { var compiledExpression = constraintElement.Annotation <CompiledConstraintAnnotation>()?.Expression; if (compiledExpression == null) { var fpExpressionText = constraintElement.GetFhirPathConstraint(); if (fpExpressionText != null) { try { compiledExpression = v.FpCompiler.Compile(fpExpressionText); constraintElement.AddAnnotation(new CompiledConstraintAnnotation { Expression = compiledExpression }); } catch (Exception e) { v.Trace(outcome, $"Compilation of FhirPath for constraint '{constraintElement.Key}' failed: {e.Message}", Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, instance); } } else { v.Trace(outcome, $"Encountered an invariant ({constraintElement.Key}) that has no FhirPath expression, skipping validation of this constraint", Issue.UNSUPPORTED_CONSTRAINT_WITHOUT_FHIRPATH, instance); } } return(compiledExpression); }
internal static OperationOutcome ValidateChildConstraints(this Validator validator, ElementDefinitionNavigator definition, ScopedNode instance, bool allowAdditionalChildren) { var outcome = new OperationOutcome(); if (!definition.HasChildren) { return(outcome); } validator.Trace(outcome, "Start validation of inlined child constraints for '{0}'".FormatWith(definition.Path), Issue.PROCESSING_PROGRESS, instance); var matchResult = ChildNameMatcher.Match(definition, instance); if (matchResult.UnmatchedInstanceElements.Any() && !allowAdditionalChildren) { var elementList = String.Join(",", matchResult.UnmatchedInstanceElements.Select(e => "'" + e.Name + "'")); validator.Trace(outcome, $"Encountered unknown child elements {elementList} for definition '{definition.Path}'", Issue.CONTENT_ELEMENT_HAS_UNKNOWN_CHILDREN, instance); } //TODO: Give warnings for out-of order children. Really? That's an xml artifact, no such thing in Json! //(with the serializationrepresentationnav we could determine the source is xml and make order matter....) // Recursively validate my children foreach (var match in matchResult.Matches) { outcome.Add(validator.ValidateMatch(match, instance)); } return(outcome); }
private static IElementNavigator resolveReference(this Validator validator, ScopedNavigator instance, string reference, out ElementDefinition.AggregationMode? referenceKind, OperationOutcome outcome) { var identity = new ResourceIdentity(reference); if (identity.Form == ResourceIdentityForm.Undetermined) { if (!Uri.IsWellFormedUriString(reference, UriKind.RelativeOrAbsolute)) { validator.Trace(outcome, $"Encountered an unparseable reference ({reference})", Issue.CONTENT_UNPARSEABLE_REFERENCE, instance); referenceKind = null; return null; } } var result = instance.Resolve(reference); if (identity.Form == ResourceIdentityForm.Local) { referenceKind = ElementDefinition.AggregationMode.Contained; if(result == null) validator.Trace(outcome, $"Contained reference ({reference}) is not resolvable", Issue.CONTENT_CONTAINED_REFERENCE_NOT_RESOLVABLE, instance); } else { if (result != null) referenceKind = ElementDefinition.AggregationMode.Bundled; else referenceKind = ElementDefinition.AggregationMode.Referenced; } return result; }
public static OperationOutcome ValidateFp(this Validator v, ElementDefinition definition, ScopedNavigator instance) { var outcome = new OperationOutcome(); if (!definition.Constraint.Any()) { return(outcome); } if (v.Settings.SkipConstraintValidation) { return(outcome); } var context = instance.AtResource ? instance : instance.Parent; foreach (var constraintElement in definition.Constraint) { bool success = false; try { var compiled = getExecutableConstraint(v, outcome, instance, constraintElement); success = compiled.Predicate(instance, new FhirEvaluationContext(context) { Resolver = callExternalResolver }); } catch (Exception e) { v.Trace(outcome, $"Evaluation of FhirPath for constraint '{constraintElement.Key}' failed: {e.Message}", Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, instance); } if (!success) { var text = "Instance failed constraint " + constraintElement.ConstraintDescription(); var issue = constraintElement.Severity == ElementDefinition.ConstraintSeverity.Error ? Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT : Issue.CONTENT_ELEMENT_FAILS_WARNING_CONSTRAINT; v.Trace(outcome, text, issue, instance); } } return(outcome); IElementNavigator callExternalResolver(string url) { OperationOutcome o = new OperationOutcome(); var result = v.ExternalReferenceResolutionNeeded(url, o, "dummy"); if (o.Success && result != null) { return(result); } return(null); } }
internal static OperationOutcome ValidateType(this Validator validator, ElementDefinition definition, ScopedNavigator instance) { var outcome = new OperationOutcome(); validator.Trace(outcome, "Validating against constraints specified by the element's defined type", Issue.PROCESSING_PROGRESS, instance); if(definition.Type.Any(tr => tr.Code == null)) validator.Trace(outcome, "ElementDefinition contains a type with an empty type code", Issue.PROFILE_ELEMENTDEF_CONTAINS_NULL_TYPE, instance); // Check if this is a choice: there are multiple distinct Codes to choose from var typeRefs = definition.Type.Where(tr => tr.Code != null); var choices = typeRefs.Select(tr => tr.Code.Value).Distinct(); if (choices.Count() > 1) { if (instance.Type != null) { // This is a choice type, find out what type is present in the instance data // (e.g. deceased[Boolean], or _resourceType in json). This is exposed by IElementNavigator.TypeName. var instanceType = ModelInfo.FhirTypeNameToFhirType(instance.Type); if (instanceType != null) { // In fact, the next statements are just an optimalization, without them, we would do an ANY validation // against *all* choices, what we do here is pre-filtering for sensible choices, and report if there isn't // any. var applicableChoices = typeRefs.Where(tr => ModelInfo.IsInstanceTypeFor(tr.Code.Value, instanceType.Value)); // Instance typename must be one of the applicable types in the choice if (applicableChoices.Any()) { outcome.Include(validator.ValidateTypeReferences(applicableChoices, instance)); } else { var choiceList = String.Join(",", choices.Select(t => "'" + t.GetLiteral() + "'")); validator.Trace(outcome, $"Type specified in the instance ('{instance.Type}') is not one of the allowed choices ({choiceList})", Issue.CONTENT_ELEMENT_HAS_INCORRECT_TYPE, instance); } } else validator.Trace(outcome, $"Instance indicates the element is of type '{instance.Type}', which is not a known FHIR core type.", Issue.CONTENT_ELEMENT_CHOICE_INVALID_INSTANCE_TYPE, instance); } else validator.Trace(outcome, "ElementDefinition is a choice or contains a polymorphic type constraint, but the instance does not indicate its actual type", Issue.CONTENT_ELEMENT_CANNOT_DETERMINE_TYPE, instance); } else if (choices.Count() == 1) { // Only one type present in list of typerefs, all of the typerefs are candidates outcome.Include(validator.ValidateTypeReferences(typeRefs, instance)); } return outcome; }
private static OperationOutcome ValidateMatch(this Validator validator, Match match, ScopedNode parent) { var outcome = new OperationOutcome(); var definition = match.Definition.Current; if (definition.Min == null) { validator.Trace(outcome, $"Element definition does not specify a 'min' value, which is required. Cardinality has not been validated", Issue.PROFILE_ELEMENTDEF_CARDINALITY_MISSING, parent); } else if (definition.Max == null) { validator.Trace(outcome, $"Element definition does not specify a 'max' value, which is required. Cardinality has not been validated", Issue.PROFILE_ELEMENTDEF_CARDINALITY_MISSING, parent); } var cardinality = Cardinality.FromElementDefinition(definition); IBucket bucket; try { bucket = BucketFactory.CreateRoot(match.Definition, validator); } catch (NotImplementedException ni) { // Will throw if a non-supported slice type is encountered validator.Trace(outcome, ni.Message, Issue.UNSUPPORTED_SLICING_NOT_SUPPORTED, parent); return(outcome); } foreach (var element in match.InstanceElements) { var success = bucket.Add(element); // For the "root" slice group (=the original core element that was sliced, not resliced) // any element that does not match is an error // Since the ChildNameMatcher currently does the matching, this will never go wrong } outcome.Add(bucket.Validate(validator, parent)); return(outcome); }
public virtual OperationOutcome Validate(Validator validator, IElementNavigator errorLocation) { var outcome = new OperationOutcome(); if (!Cardinality.InRange(Members.Count)) { validator.Trace(outcome, $"Instance count for '{Name}' is {Members.Count}, which is not within the specified cardinality of {Cardinality.ToString()}", Issue.CONTENT_INCORRECT_OCCURRENCE, errorLocation); } return(outcome); }
public virtual OperationOutcome Validate(Validator validator, IElementNavigator errorLocation) { var outcome = new OperationOutcome(); if (!Cardinality.InRange(Members.Count)) { OperationOutcome.IssueComponent issue = validator.Trace(outcome, $"Instance count for '{Name}' is {Members.Count}, which is not within the specified cardinality of {Cardinality.ToString()}", Issue.CONTENT_INCORRECT_OCCURRENCE, errorLocation); if (issue != null) { // the location in the structure definition (this will match to the discriminator when checking slicing) // issue.LocationElement.Add(new FhirString(Path)); issue.SetAnnotation(new SlicePathAnnotation(Path)); } } return(outcome); }
public static OperationOutcome ValidateFixed(this Validator v, ElementDefinition definition, IElementNavigator instance) { var outcome = new OperationOutcome(); if (definition.Fixed != null) { IElementNavigator fixedValueNav = new PocoNavigator(definition.Fixed); if (!instance.IsExactlyEqualTo(fixedValueNav)) { v.Trace(outcome, $"Value is not exactly equal to fixed value '{toReadable(definition.Fixed)}'", Issue.CONTENT_DOES_NOT_MATCH_FIXED_VALUE, instance); } } return(outcome); }
public static OperationOutcome ValidatePattern(this Validator v, ElementDefinition definition, IElementNavigator instance) { var outcome = new OperationOutcome(); if (definition.Pattern != null) { IElementNavigator patternValueNav = new PocoNavigator(definition.Pattern); if (!instance.Matches(patternValueNav)) { v.Trace(outcome, $"Value does not match pattern '{toReadable(definition.Pattern)}'", Issue.CONTENT_DOES_NOT_MATCH_PATTERN_VALUE, instance); } } return(outcome); }
private static OperationOutcome validateMinMaxValue(Validator me, Element definition, ITypedElement instance, int comparisonOutcome, string elementName) { var outcome = new OperationOutcome(); if (definition != null) { // Min/max are only defined for ordered types if (definition.GetType().IsOrderedFhirType()) { try { var instanceValue = instance.GetComparableValue(definition.GetType()); if (instanceValue != null) { if (Compare(instanceValue, definition) == comparisonOutcome) { var label = comparisonOutcome == -1 ? "smaller than" : comparisonOutcome == 0 ? "equal to" : "larger than"; var issue = comparisonOutcome == -1 ? Issue.CONTENT_ELEMENT_PRIMITIVE_VALUE_TOO_SMALL : Issue.CONTENT_ELEMENT_PRIMITIVE_VALUE_TOO_LARGE; outcome.AddIssue($"Instance value '{instanceValue}' is {label} {elementName} '{definition}'", issue, instance); } } } catch (NotSupportedException ns) { outcome.AddIssue($"Comparing the instance against the {elementName} failed: {ns.Message}", Issue.UNSUPPORTED_MIN_MAX_QUANTITY, instance); } } else { me.Trace(outcome, $"{elementName} was given in ElementDefinition, but type '{definition.TypeName}' is not an ordered type", Issue.PROFILE_ELEMENTDEF_MIN_MAX_USES_UNORDERED_TYPE, instance); } } return(outcome); }
public static OperationOutcome Combine(this Validator validator, BatchValidationMode mode, ITypedElement instance, IEnumerable <Func <OperationOutcome> > validations) { if (validations.Count() == 0) { return(new OperationOutcome()); } if (validations.Count() == 1) { // To not pollute the output if there's just a single input, just add it to the output return(validations.First()()); } OperationOutcome combinedResult = new OperationOutcome(); var modeLabel = mode == BatchValidationMode.All ? "ALL" : "ANY"; validator.Trace(combinedResult, $"Combination of {validations.Count()} child validation runs, {modeLabel} must succeed", Issue.PROCESSING_PROGRESS, instance); int failures = 0; int successes = 0; List <OperationOutcome> results = new List <OperationOutcome>(); // Run the given validations one by one, short circuiting when ANY success is enough foreach (var validation in validations) { var result = validation(); results.Add(result); if (result.Success) { successes += 1; } else { failures += 1; } if (mode == BatchValidationMode.Any && successes > 0) { break; // shortcut evaluation } } // Did we have success overall? bool success = mode == BatchValidationMode.Any && successes > 0 || mode == BatchValidationMode.All && failures == 0 || mode == BatchValidationMode.Once && successes == 1; // Now, build final report for (var index = 0; index < results.Count; index++) { var result = results[index]; validator.Trace(combinedResult, $"Report {index}: {(result.Success ? "SUCCESS" : "FAILURE")}", Issue.PROCESSING_PROGRESS, instance); if (success) { // We'd like to include all results of the combined reports, but if the total result is a success, // any errors in failing runs should just be informational if (!result.Success) { result.MakeInformational(); } } combinedResult.Include(result); } if (success) { validator.Trace(combinedResult, "Combined validation succeeded", Issue.PROCESSING_PROGRESS, instance); } else { combinedResult.AddIssue($"Combined {modeLabel} validation failed, {failures} child validation runs failed, {successes} succeeded", Issue.PROCESSING_PROGRESS, instance); } return(combinedResult); }
internal static OperationOutcome ValidateResourceReference(this Validator validator, IElementNavigator instance, ElementDefinition.TypeRefComponent typeRef) { var outcome = new OperationOutcome(); var references = instance.GetChildrenByName("reference"); var reference = references.FirstOrDefault()?.Value as string; if (reference == null) // No reference found -> this is always valid { return(outcome); } if (references.Count() > 1) { validator.Trace(outcome, $"Encountered multiple references, just using first ({reference})", Issue.CONTENT_REFERENCE_HAS_MULTIPLE_REFERENCES, instance); } // Try to resolve the reference *within* the current instance (Bundle, resource with contained resources) first ElementDefinition.AggregationMode?encounteredKind; var referencedResource = validator.ResolveReference(instance, reference, out encounteredKind, outcome); // Validate the kind of aggregation. // If no aggregation is given, all kinds of aggregation are allowed, otherwise only allow // those aggregation types that are given in the Aggregation element bool hasAggregation = typeRef.Aggregation != null && typeRef.Aggregation.Count() != 0; if (hasAggregation && !typeRef.Aggregation.Any(a => a == encounteredKind)) { validator.Trace(outcome, $"Encountered a reference ({reference}) of kind '{encounteredKind}' which is not allowed", Issue.CONTENT_REFERENCE_OF_INVALID_KIND, instance); } // If we failed to find a referenced resource within the current instance, try to resolve it using an external method if (referencedResource == null && encounteredKind == ElementDefinition.AggregationMode.Referenced) { try { referencedResource = validator.ExternalReferenceResolutionNeeded(reference, outcome, instance); } catch (Exception e) { validator.Trace(outcome, $"Resolution of external reference {reference} failed. Message: {e.Message}", Issue.UNAVAILABLE_REFERENCED_RESOURCE, instance); } } // If the reference was resolved (either internally or externally, validate it if (referencedResource != null) { validator.Trace(outcome, $"Starting validation of referenced resource {reference} ({encounteredKind})", Issue.PROCESSING_START_NESTED_VALIDATION, instance); // References within the instance are dealt with within the same validator, // 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 (encounteredKind != ElementDefinition.AggregationMode.Referenced) { outcome.Include(validator.Validate(referencedResource, typeRef.GetDeclaredProfiles(), statedProfiles: null, statedCanonicals: null)); } else { var newValidator = validator.NewInstance(); outcome.Include(newValidator.Validate(referencedResource, typeRef.GetDeclaredProfiles(), statedProfiles: null, statedCanonicals: null)); } } else { validator.Trace(outcome, $"Cannot resolve reference {reference}", Issue.UNAVAILABLE_REFERENCED_RESOURCE, instance); } return(outcome); }
public OperationOutcome Validate(Validator validator, IElementNavigator errorLocation) { var outcome = Entry.Validate(validator, errorLocation); // Validate against entry slice, e.g. cardinality var lastMatchingSlice = -1; var openTailInUse = false; // Go over the elements in the instance, in order foreach (var candidate in Entry.Members) { bool hasSucceeded = false; // Try to find the child slice that this element matches for (var sliceNumber = 0; sliceNumber < ChildSlices.Count; sliceNumber++) { var sliceName = ChildSlices[sliceNumber].Name; var success = ChildSlices[sliceNumber].Add(candidate); if (success) { // The instance matched a slice that we have already passed, if order matters, // this is not allowed if (sliceNumber < lastMatchingSlice && Ordered) { validator.Trace(outcome, $"Element matches slice '{sliceName}', but this " + $"is out of order for group '{Name}', since a previous element already matched slice '{ChildSlices[lastMatchingSlice].Name}'", Issue.CONTENT_ELEMENT_SLICING_OUT_OF_ORDER, candidate); } else { lastMatchingSlice = sliceNumber; } if (openTailInUse) { // We found a match while we already added a non-match to a "open at end" slicegroup, that's not allowed validator.Trace(outcome, $"Element matched slice '{sliceName}', but it appears after a non-match, which is not allowed for an open-at-end group", Issue.CONTENT_ELEMENT_FAILS_SLICING_RULE, candidate); } hasSucceeded = true; } } // So we found no slice that can take this candidate, let's take a look at the rules if (!hasSucceeded) { if (Rules == ElementDefinition.SlicingRules.Open) { validator.Trace(outcome, $"Element was determined to be in the open slice for group '{Name}'", Issue.PROCESSING_PROGRESS, candidate); } else if (Rules == ElementDefinition.SlicingRules.OpenAtEnd) { openTailInUse = true; } else { // Sorry, won't fly validator.Trace(outcome, $"Element does not match any slice, but the group at '{Name}' is closed.", Issue.CONTENT_ELEMENT_FAILS_SLICING_RULE, candidate); } } } // Finally, add any validation items on the elements that made it into the child slices foreach (var slice in ChildSlices) { var sliceOutcome = slice.Validate(validator, errorLocation); foreach (var issue in sliceOutcome.Issue) { // Only add the issue from the slice outcome if the entry validation did not already catch // the same issue, otherwise the users will see it twice. if (!outcome.Issue.Exists(i => i.Location.First() == issue.Location.First() && i.Details.Text == issue.Details.Text)) { outcome.AddIssue(issue); } } } return(outcome); }
public static OperationOutcome ValidateFp(this Validator v, ElementDefinition definition, ScopedNode instance) { var outcome = new OperationOutcome(); if (!definition.Constraint.Any()) { return(outcome); } if (v.Settings.SkipConstraintValidation) { return(outcome); } var context = instance.ResourceContext; foreach (var constraintElement in definition.Constraint) { // 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. if (constraintElement.Key == "rng-2") { continue; } bool success = false; try { var compiled = getExecutableConstraint(v, outcome, instance, constraintElement); success = compiled.Predicate(instance, new FhirEvaluationContext(context) { ElementResolver = callExternalResolver }); } catch (Exception e) { v.Trace(outcome, $"Evaluation of FhirPath for constraint '{constraintElement.Key}' failed: {e.Message}", Issue.PROFILE_ELEMENTDEF_INVALID_FHIRPATH_EXPRESSION, instance); } if (!success) { var text = "Instance failed constraint " + constraintElement.ConstraintDescription(); var issue = constraintElement.Severity == ElementDefinition.ConstraintSeverity.Error ? Issue.CONTENT_ELEMENT_FAILS_ERROR_CONSTRAINT : Issue.CONTENT_ELEMENT_FAILS_WARNING_CONSTRAINT; v.Trace(outcome, text, issue, instance); } } return(outcome); ITypedElement callExternalResolver(string url) { OperationOutcome o = new OperationOutcome(); var result = v.ExternalReferenceResolutionNeeded(url, o, "dummy"); if (o.Success && result != null) { return(result); } return(null); } }
internal static OperationOutcome ValidateResourceReference(this Validator validator, ScopedNode instance, ElementDefinition.TypeRefComponent typeRef) { var outcome = new OperationOutcome(); var reference = instance.ParseResourceReference()?.Reference; if (reference == null) // No reference found -> this is always valid { return(outcome); } // Try to resolve the reference *within* the current instance (Bundle, resource with contained resources) first var referencedResource = validator.resolveReference(instance, reference, out ElementDefinition.AggregationMode? encounteredKind, outcome); // Validate the kind of aggregation. // If no aggregation is given, all kinds of aggregation are allowed, otherwise only allow // those aggregation types that are given in the Aggregation element bool hasAggregation = typeRef.Aggregation != null && typeRef.Aggregation.Count() != 0; if (hasAggregation && !typeRef.Aggregation.Any(a => a == encounteredKind)) { validator.Trace(outcome, $"Encountered a reference ({reference}) of kind '{encounteredKind}' which is not allowed", Issue.CONTENT_REFERENCE_OF_INVALID_KIND, instance); } // Bail out if we are asked to follow an *external reference* when this is disabled in the settings if (validator.Settings.ResolveExteralReferences == false && encounteredKind == ElementDefinition.AggregationMode.Referenced) { return(outcome); } // If we failed to find a referenced resource within the current instance, try to resolve it using an external method if (referencedResource == null && encounteredKind == ElementDefinition.AggregationMode.Referenced) { try { referencedResource = validator.ExternalReferenceResolutionNeeded(reference, outcome, instance.Location); } catch (Exception e) { validator.Trace(outcome, $"Resolution of external reference {reference} failed. Message: {e.Message}", Issue.UNAVAILABLE_REFERENCED_RESOURCE, instance); } } // If the reference was resolved (either internally or externally, validate it if (referencedResource != null) { validator.Trace(outcome, $"Starting validation of referenced resource {reference} ({encounteredKind})", Issue.PROCESSING_START_NESTED_VALIDATION, instance); // References within the instance are dealt with within the same validator, // 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. OperationOutcome childResult; if (encounteredKind != ElementDefinition.AggregationMode.Referenced) { childResult = validator.Validate(referencedResource, typeRef.GetDeclaredProfiles(), statedProfiles: null, statedCanonicals: null); } else { var newValidator = validator.NewInstance(); childResult = newValidator.Validate(referencedResource, typeRef.GetDeclaredProfiles(), statedProfiles: null, statedCanonicals: null); } // Prefix each path with the referring resource's path to keep the locations // interpretable foreach (var issue in childResult.Issue) { issue.Location = issue.Location.Concat(new string[] { instance.Location }); } outcome.Include(childResult); } else { validator.Trace(outcome, $"Cannot resolve reference {reference}", Issue.UNAVAILABLE_REFERENCED_RESOURCE, instance); } return(outcome); }