// 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); }
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); }
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); }
/// <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); }
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); }
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); }
/// <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); }
// 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); }
// 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); } }