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; }
public void Setup() { OperationOutcome level0 = new OperationOutcome(); level0.AddIssue(Issue.CONTENT_ELEMENT_HAS_INCORRECT_TYPE.ToIssueComponent("A test error at level 0", "Patient")); OperationOutcome level1 = new OperationOutcome(); Patient p = new Patient(); p.Active = true; _location = new PocoNavigator(p); _location.MoveToFirstChild(); level1.AddIssue(Issue.PROFILE_ELEMENTDEF_CARDINALITY_MISSING.ToIssueComponent("A test warning at level 1", _location)); OperationOutcome level2 = new OperationOutcome(); level2.AddIssue(Issue.UNSUPPORTED_CONSTRAINT_WITHOUT_FHIRPATH.ToIssueComponent("A test warning at level 2", "Patient.active[0].id[0]")); level2.AddIssue(Issue.CONTENT_ELEMENT_MUST_MATCH_TYPE.ToIssueComponent("Another test error at level 2", "Patient.active[0].id[0]")); level1.Include(level2); level0.Include(level1); level0.AddIssue(Issue.PROFILE_ELEMENTDEF_IS_EMPTY.ToIssueComponent("A test warning at level 0", "Patient")); _report = level0; }
private OperationOutcome callService(string code, string system, string display, string uri, BindingStrength?strength, string path) { var outcome = new OperationOutcome(); OperationOutcome validateResult = _service.ValidateCode(uri, code, system, display, abstractAllowed: false); var codeLabel = $"Code '{code}' from system '{system}'"; if (display != null) { codeLabel += $" with display '{display}'"; } if (validateResult.Where(type: OperationOutcome.IssueType.NotSupported).Any()) { if (strength != BindingStrength.Example) { outcome.AddIssue($"The terminology service is incapable of validating {codeLabel} (valueset '{uri}').", Issue.UNSUPPORTED_BINDING_NOT_SUPPORTED_BY_SERVICE, path); validateResult.MakeInformational(); outcome.Include(validateResult); } return(outcome); } if (!validateResult.Success) { if (strength == BindingStrength.Required) { outcome.AddIssue($"{codeLabel} is not valid for required binding to valueset '{uri}'", Issue.CONTENT_INVALID_FOR_REQUIRED_BINDING, path); } else if (strength != BindingStrength.Example) { outcome.AddIssue($"{codeLabel} is not valid for non-required binding to valueset '{uri}'", Issue.CONTENT_INVALID_FOR_NON_REQUIRED_BINDING, path); } validateResult.MakeInformational(); } outcome.Include(validateResult); return(outcome); }
internal OperationOutcome ValidateNameReference(ElementDefinition definition, ElementDefinitionNavigator allDefinitions, ScopedNavigator instance) { var outcome = new OperationOutcome(); if (definition.NameReference != null) { Trace(outcome, $"Start validation of constraints referred to by nameReference '{definition.NameReference}'", Issue.PROCESSING_PROGRESS, instance); var referencedPositionNav = allDefinitions.ShallowCopy(); if (referencedPositionNav.JumpToNameReference(definition.NameReference)) { outcome.Include(Validate(instance, referencedPositionNav)); } else { Trace(outcome, $"ElementDefinition uses a non-existing nameReference '{definition.NameReference}'", Issue.PROFILE_ELEMENTDEF_INVALID_NAMEREFERENCE, instance); } } return(outcome); }
private OperationOutcome validateCodeVS(ValueSet vs, CodeableConcept cc, bool?abstractAllowed) { var outcome = new OperationOutcome(); // Maybe just a text, but if there are no codings, that's a positive result if (!cc.Coding.Any()) { return(outcome); } // If we have just 1 coding, we better handle this using the simpler version of ValidateBinding if (cc.Coding.Count == 1) { return(validateCodeVS(vs, cc.Coding.Single(), abstractAllowed)); } // Else, look for one succesful match in any of the codes in the CodeableConcept var callResults = cc.Coding.Select(coding => validateCodeVS(vs, coding, abstractAllowed)); var successOutcome = callResults.Where(r => r.Success).OrderBy(oo => oo.Warnings).FirstOrDefault(); if (successOutcome == null) { outcome.AddIssue("None of the Codings in the CodeableConcept were valid for the binding. Details follow.", Issue.CONTENT_INVALID_FOR_REQUIRED_BINDING); foreach (var cr in callResults) { cr.MakeInformational(); outcome.Include(cr); } } else { outcome.Add(successOutcome); } 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, 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); }
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); }