Ejemplo n.º 1
0
        // 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 Validate(ScopedNavigator instance, IEnumerable <ElementDefinitionNavigator> definitions)
        {
            var outcome = new OperationOutcome();

            try
            {
                List <ElementDefinitionNavigator> allDefinitions = new List <ElementDefinitionNavigator>(definitions);

                if (allDefinitions.Count() == 1)
                {
                    outcome.Add(validateElement(allDefinitions.Single(), instance));
                }
                else
                {
                    var validators = allDefinitions.Select(nav => createValidator(nav, instance));
                    outcome.Add(this.Combine(BatchValidationMode.All, instance, validators));
                }
            }
            catch (Exception e)
            {
                outcome.AddIssue($"Internal logic failure: {e.Message}", Issue.PROCESSING_CATASTROPHIC_FAILURE, instance);
            }

            return(outcome);
        }
Ejemplo n.º 2
0
        public override OperationOutcome Validate(Validator validator, ITypedElement errorLocation)
        {
            OperationOutcome outcome = new OperationOutcome();

            foreach (var failure in _failures)
            {
                outcome.Add(failure);
            }

            outcome.Add(base.Validate(validator, errorLocation));

            return(outcome);
        }
        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);
        }
        internal static OperationOutcome ValidateMinMaxValue(this Validator validator, ElementDefinition definition, ITypedElement instance)
        {
            var outcome = new OperationOutcome();

            if (definition.MinValue != null)
            {
                outcome.Add(validateMinMaxValue(validator, definition.MinValue, instance, -1, "MinValue"));
            }

            if (definition.MaxValue != null)
            {
                outcome.Add(validateMinMaxValue(validator, definition.MaxValue, instance, 1, "MaxValue"));
            }

            return(outcome);
        }
Ejemplo n.º 5
0
        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);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Generate snapshots for all StructureDefinitions available to the preprocessor
        /// </summary>
        /// <returns></returns>
        public static OperationOutcome GenerateSnapshots(IEnumerable <StructureDefinition> sds, Func <StructureDefinition, OperationOutcome> snapshotGenerator, string path)
        {
            var outcome = new OperationOutcome();

            foreach (var sd in sds)
            {
                if (!sd.HasSnapshot)
                {
                    try
                    {
                        var snapshotOutcome = snapshotGenerator(sd);

                        if (!snapshotOutcome.Success)
                        {
                            outcome.AddIssue($"Snapshot generation failed for '{sd.Url}'. Details follow below.",
                                             Issue.UNAVAILABLE_SNAPSHOT_GENERATION_FAILED, path);
                            outcome.Add(snapshotOutcome);
                        }
                    }
                    catch (Exception e)
                    {
                        outcome.AddIssue($"Snapshot generation failed for '{sd.Url}'. Message: {e.Message}",
                                         Issue.UNAVAILABLE_SNAPSHOT_GENERATION_FAILED, path);
                    }
                }


                if (!sd.HasSnapshot)
                {
                    outcome.AddIssue($"Profile '{sd.Url}' does not include a snapshot.", Issue.UNAVAILABLE_NEED_SNAPSHOT, path);
                }
            }

            return(outcome);
        }
Ejemplo n.º 7
0
        public OperationOutcome Process()
        {
            var outcome = new OperationOutcome();

            // Start preprocessing by resolving the references to the profiles (if any)
            var resolveOutcome = _profiles.Resolve();

            outcome.Add(resolveOutcome);

            if (resolveOutcome.Success)
            {
                // Then, validate consistency of the profile assertions
                var validationOutcome = _profiles.Validate();
                outcome.Add(validationOutcome);

                if (validationOutcome.Success)
                {
                    if (_profiles.MinimalProfiles.Any())
                    {
                        // Then, generate snapshots for all sds that we have found
                        var genSnapshotOutcome = GenerateSnapshots(_profiles.MinimalProfiles, _snapshotGenerator, _path);
                        outcome.Add(genSnapshotOutcome);

                        if (genSnapshotOutcome.Success)
                        {
                            // Finally, return navigators to the definitions
                            Result = CreateNavigators(_profiles.MinimalProfiles);
                        }
                    }
                    else
                    {
                        outcome.AddIssue("There are no profile and type assertions at this point in the instance, so validation cannot succeed",
                                         Issue.PROFILE_NO_PROFILE_TO_VALIDATE_AGAINST, _path);
                    }
                }
            }

            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);
        }
        internal static OperationOutcome ValidatedParseXml(this Validator me, XmlReader instance, out Resource poco)
        {
            var result = new OperationOutcome();

            try
            {
                if (me.Settings.EnableXsdValidation)
                {
                    var doc = XDocument.Load(instance, LoadOptions.SetLineInfo);
                    result.Add(me.ValidateXml(doc));
                    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, (string)null);
                poco = null;
            }

            return(result);
        }
Ejemplo n.º 10
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);
        }
Ejemplo n.º 11
0
        /// <summary>
        /// Validates the instance, declared and stated profiles for consistenty.
        /// </summary>
        /// <returns></returns>
        public OperationOutcome Validate()
        {
            if (_lastValidationOutcome != null)
            {
                return(_lastValidationOutcome);
            }

            var outcome = new OperationOutcome();

            // Resolve input profiles first (note: this is cached)
            var resolutionOutcome = Resolve();

            if (!resolutionOutcome.Success)
            {
                return(resolutionOutcome);
            }
            else
            {
                outcome.Add(resolutionOutcome);
            }

            // If we have an instance type, it should be compatible with the declared type on the definition and the stated profiles
            if (InstanceType != null)
            {
                if (DeclaredType != null)
                {
                    if (!ModelInfo.IsInstanceTypeFor(DeclaredType.BaseType(), InstanceType.BaseType()))
                    {
                        outcome.AddIssue($"The declared type of the element ({DeclaredType.ReadableName()}) is incompatible with that of the instance ('{InstanceType.ReadableName()}')",
                                         Issue.CONTENT_ELEMENT_HAS_INCORRECT_TYPE, _path);
                    }
                }

                foreach (var type in StatedProfiles)
                {
                    if (!ModelInfo.IsInstanceTypeFor(type.BaseType(), InstanceType.BaseType()))
                    {
                        outcome.AddIssue($"Instance of type '{InstanceType.ReadableName()}' is incompatible with the stated profile '{type.Url}' which is constraining constrained type '{type.ReadableName()}'",
                                         Issue.CONTENT_ELEMENT_HAS_INCORRECT_TYPE, _path);
                    }
                }
            }

            // All stated profiles should be profiling the same core type
            if (StatedProfiles.Any())
            {
                var baseTypes = StatedProfiles.Select(p => p.BaseType()).Distinct().ToList();

                if (baseTypes.Count > 1)
                {
                    var combinedNames = String.Join(" and ", baseTypes.Select(bt => bt.GetLiteral()));
                    outcome.AddIssue($"The stated profiles are constraints on multiple different core types ({combinedNames}), which can never be satisfied.",
                                     Issue.CONTENT_MISMATCHING_PROFILES, _path);
                }
                else
                {
                    // The stated profiles should be compatible with the declared type of the element
                    if (DeclaredType != null)
                    {
                        if (!ModelInfo.IsInstanceTypeFor(DeclaredType.BaseType(), baseTypes.Single()))
                        {
                            outcome.AddIssue($"The stated profiles are all constraints on '{baseTypes.Single()}', which is incompatible with the declared type '{DeclaredType.ReadableName()}' of the element",
                                             Issue.CONTENT_MISMATCHING_PROFILES, _path);
                        }
                    }
                }
            }

            _lastValidationOutcome = outcome;
            return(outcome);
        }
Ejemplo n.º 12
0
        //   private OperationOutcome validateElement(ElementDefinitionNavigator definition, IElementNavigator instance)

        private OperationOutcome validateElement(ElementDefinitionNavigator definition, ScopedNavigator instance)
        {
            var outcome = new OperationOutcome();

            Trace(outcome, $"Start validation of ElementDefinition at path '{definition.QualifiedDefinitionPath()}'", Issue.PROCESSING_PROGRESS, instance);

            // If navigator cannot be moved to content, there's really nothing to validate against.
            if (definition.AtRoot && !definition.MoveToFirstChild())
            {
                outcome.AddIssue($"Snapshot component of profile '{definition.StructureDefinition?.Url}' has no content.", Issue.PROFILE_ELEMENTDEF_IS_EMPTY, instance);
                return(outcome);
            }

            // Any node must either have a value, or children, or both (e.g. extensions on primitives)
            if (instance.Value == null && !instance.HasChildren())
            {
                outcome.AddIssue("Element must not be empty", Issue.CONTENT_ELEMENT_MUST_HAVE_VALUE_OR_CHILDREN, instance);
                return(outcome);
            }

            var elementConstraints = definition.Current;

            if (elementConstraints.IsPrimitiveValueConstraint())
            {
                // The "value" property of a FHIR Primitive is the bottom of our recursion chain, it does not have a nameReference
                // nor a <type>, the only thing left to do to validate the content is to validate the string representation of the
                // primitive against the regex given in the core definition
                outcome.Add(VerifyPrimitiveContents(elementConstraints, instance));
            }
            else
            {
                bool isInlineChildren = !definition.Current.IsRootElement();

                // Now, validate the children
                if (definition.HasChildren)
                {
                    // If we are at the root of an abstract type (e.g. is this instance a Resource)?
                    // or we are at a nested resource, we may expect more children in the instance than
                    // we know about
                    bool allowAdditionalChildren = (isInlineChildren && elementConstraints.IsResourcePlaceholder()) ||
                                                   (!isInlineChildren && definition.StructureDefinition.Abstract == true);

                    // Handle in-lined constraints on children. In a snapshot, these children should be exhaustive,
                    // so there's no point in also validating the <type> or <nameReference>
                    // TODO: Check whether this is even true when the <type> has a profile?
                    // Note: the snapshot is *not* exhaustive if the declared type is a base FHIR type (like Resource),
                    // in which case there may be additional children (verified in the next step)
                    outcome.Add(this.ValidateChildConstraints(definition, instance, allowAdditionalChildren: allowAdditionalChildren));

                    // Special case: if we are located at a nested resource (i.e. contained or Bundle.entry.resource),
                    // we need to validate based on the actual type of the instance
                    if (isInlineChildren && elementConstraints.IsResourcePlaceholder())
                    {
                        outcome.Add(this.ValidateType(elementConstraints, instance));
                    }
                }

                if (!definition.HasChildren)
                {
                    // No inline-children, so validation depends on the presence of a <type> or <nameReference>
                    if (elementConstraints.Type != null || elementConstraints.NameReference != null)
                    {
                        outcome.Add(this.ValidateType(elementConstraints, instance));
                        outcome.Add(ValidateNameReference(elementConstraints, definition, instance));
                    }
                    else
                    {
                        Trace(outcome, "ElementDefinition has no child, nor does it specify a type or nameReference to validate the instance data against", Issue.PROFILE_ELEMENTDEF_CONTAINS_NO_TYPE_OR_NAMEREF, instance);
                    }
                }
            }

            outcome.Add(this.ValidateFixed(elementConstraints, instance));
            outcome.Add(this.ValidatePattern(elementConstraints, instance));
            outcome.Add(this.ValidateMinMaxValue(elementConstraints, instance));
            outcome.Add(ValidateMaxLength(elementConstraints, instance));
            outcome.Add(this.ValidateFp(elementConstraints, instance));
            outcome.Add(this.ValidateBinding(elementConstraints, instance));
            outcome.Add(this.ValidateExtension(elementConstraints, instance, "http://hl7.org/fhir/StructureDefinition/regex"));

            // If the report only has partial information, no use to show the hierarchy, so flatten it.
            if (Settings.Trace == false)
            {
                outcome.Flatten();
            }

            return(outcome);
        }
Ejemplo n.º 13
0
        //   private OperationOutcome validateElement(ElementDefinitionNavigator definition, IElementNavigator instance)

        private OperationOutcome validateElement(ElementDefinitionNavigator definition, IElementNavigator instance)
        {
            var outcome = new OperationOutcome();

            try
            {
                Trace(outcome, $"Start validation of ElementDefinition at path '{definition.QualifiedDefinitionPath()}'", Issue.PROCESSING_PROGRESS, instance);

                ScopeTracker.Enter(instance);

                // If navigator cannot be moved to content, there's really nothing to validate against.
                if (definition.AtRoot && !definition.MoveToFirstChild())
                {
                    outcome.AddIssue($"Snapshot component of profile '{definition.StructureDefinition?.Url}' has no content.", Issue.PROFILE_ELEMENTDEF_IS_EMPTY, instance);
                    return(outcome);
                }

                // Any node must either have a value, or children, or both (e.g. extensions on primitives)
                if (instance.Value == null && !instance.HasChildren())
                {
                    outcome.AddIssue("Element must not be empty", Issue.CONTENT_ELEMENT_MUST_HAVE_VALUE_OR_CHILDREN, instance);
                    return(outcome);
                }

                var elementConstraints = definition.Current;

                if (elementConstraints.IsPrimitiveValueConstraint())
                {
                    // The "value" property of a FHIR Primitive is the bottom of our recursion chain, it does not have a nameReference
                    // nor a <type>, the only thing left to do to validate the content is to validate the string representation of the
                    // primitive against the regex given in the core definition
                    outcome.Add(VerifyPrimitiveContents(elementConstraints, instance));
                }
                else if (definition.HasChildren)
                {
                    // Handle in-lined constraints on children. In a snapshot, these children should be exhaustive,
                    // so there's no point in also validating the <type> or <nameReference>
                    // TODO: Check whether this is even true when the <type> has a profile?
                    outcome.Add(this.ValidateChildConstraints(definition, instance));
                }
                else
                {
                    // No inline-children, so validation depends on the presence of a <type> or <nameReference>
                    if (elementConstraints.Type != null || elementConstraints.NameReference != null)

                    {
                        outcome.Add(this.ValidateType(elementConstraints, instance));
                        outcome.Add(ValidateNameReference(elementConstraints, definition, instance));
                    }
                    else
                    {
                        Trace(outcome, "ElementDefinition has no child, nor does it specify a type or nameReference to validate the instance data against", Issue.PROFILE_ELEMENTDEF_CONTAINS_NO_TYPE_OR_NAMEREF, instance);
                    }
                }

                outcome.Add(this.ValidateFixed(elementConstraints, instance));
                outcome.Add(this.ValidatePattern(elementConstraints, instance));
                outcome.Add(this.ValidateMinMaxValue(elementConstraints, instance));
                outcome.Add(ValidateMaxLength(elementConstraints, instance));
                outcome.Add(ValidateConstraints(elementConstraints, instance));
                outcome.Add(this.ValidateBinding(elementConstraints, instance));

                // If the report only has partial information, no use to show the hierarchy, so flatten it.
                if (Settings.Trace == false)
                {
                    outcome.Flatten();
                }

                return(outcome);
            }
            finally
            {
                ScopeTracker.Leave(instance);
            }
        }