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;
        }
Beispiel #3
0
        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);
        }
Beispiel #4
0
        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);
        }
Beispiel #5
0
        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);
        }
Beispiel #7
0
        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);
        }