// This is the one and only main entry point for all external validation calls (i.e. invoked by the user of the API) internal OperationOutcome Validate(IElementNavigator instance, string declaredTypeProfile, IEnumerable <string> statedCanonicals, IEnumerable <StructureDefinition> statedProfiles) { if (!(instance is ScopedNavigator)) { instance = new ScopedNavigator(instance); } var processor = new ProfilePreprocessor(profileResolutionNeeded, snapshotGenerationNeeded, instance, declaredTypeProfile, statedProfiles, statedCanonicals); var outcome = processor.Process(); // Note: only start validating if the profiles are complete and consistent if (outcome.Success) { outcome.Add(Validate((ScopedNavigator)instance, processor.Result)); } return(outcome); StructureDefinition profileResolutionNeeded(string canonical) { if (Settings.ResourceResolver != null) { return(Settings.ResourceResolver.FindStructureDefinition(canonical)); } else { return(null); } } }
// 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); }
internal static OperationOutcome ValidateChildConstraints(this Validator validator, ElementDefinitionNavigator definition, ScopedNavigator 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 override bool Add(ScopedNavigator instance) { // Membership of an "element bucket" should be determined by element name //var matches = ChildNameMatcher.NameMatches(Root.PathName, candidate); //if (!matches) // Validator.Trace(outcome, $"Element name {candidate.Name} does match definition {Root.Path}", Issue.CONTENT_ELEMENT_NAME_DOESNT_MATCH_DEFINITION, candidate); Members.Add(instance); return(true); }
internal static OperationOutcome ValidateTypeReferences(this Validator validator, IEnumerable<ElementDefinition.TypeRefComponent> typeRefs, ScopedNavigator instance) { //TODO: It's more efficient to do the non-reference types FIRST, since ANY match would be ok, //and validating non-references is cheaper //TODO: For each choice, we will currently try to resolve the reference. If it fails, you'll get multiple errors and probably //better separate the fetching of the instance from the validation, so we do not run the rest of the validation (multiple times!) //when a reference cannot be resolved. (this happens in a choice type where there are multiple references with multiple profiles) IEnumerable<Func<OperationOutcome>> validations = typeRefs.Select(tr => createValidatorForTypeRef(validator, instance,tr)); return validator.Combine(BatchValidationMode.Any, instance, validations); }
public void TestBucketAssignment() { var s = createSliceDefs() as SliceGroupBucket; var p = new Patient(); p.Telecom.Add(new ContactPoint { System = ContactPoint.ContactPointSystem.Phone, Use = ContactPoint.ContactPointUse.Home, Value = "+31-6-39015765" }); p.Telecom.Add(new ContactPoint { System = ContactPoint.ContactPointSystem.Email, Use = ContactPoint.ContactPointUse.Work, Value = "*****@*****.**" }); p.Telecom.Add(new ContactPoint { System = ContactPoint.ContactPointSystem.Other, Use = ContactPoint.ContactPointUse.Temp, Value = "skype://crap" }); p.Telecom.Add(new ContactPoint { System = ContactPoint.ContactPointSystem.Other, Use = ContactPoint.ContactPointUse.Home, Value = "http://nu.nl" }); p.Telecom.Add(new ContactPoint { System = ContactPoint.ContactPointSystem.Fax, Use = ContactPoint.ContactPointUse.Work, Value = "+31-20-6707070" }); var pnav = new ScopedNavigator(new PocoNavigator(p)); var telecoms = pnav.Children("telecom").Cast <ScopedNavigator>(); foreach (var telecom in telecoms) { Assert.True(s.Add(telecom)); } var outcome = s.Validate(_validator, pnav); Assert.True(outcome.Success); Assert.Equal(0, outcome.Warnings); Assert.Equal("+31-6-39015765", s.ChildSlices[0].Members.Single().Children("value").Single().Value); var emailBucket = s.ChildSlices[1] as SliceGroupBucket; Assert.Equal("*****@*****.**", emailBucket.Members.Single().Children("value").Single().Value); Assert.False(emailBucket.ChildSlices[0].Members.Any()); Assert.Equal("*****@*****.**", emailBucket.ChildSlices[1].Members.Single().Children("value").Single().Value); var otherBucket = s.ChildSlices[2] as SliceGroupBucket; Assert.Equal("http://nu.nl", otherBucket.ChildSlices[0].Members.Single().Children("value").Single().Value); Assert.False(otherBucket.ChildSlices[1].Members.Any()); Assert.Equal("skype://crap", otherBucket.Members.First().Children("value").Single().Value); // in the open slice - find it on other bucket, not child Assert.Equal("+31-20-6707070", s.Members.Last().Children("value").Single().Value); // in the open-at-end slice }
public override bool Add(ScopedNavigator candidate) { var report = Validator.Validate(candidate, Root); // If the instance matches everything in the slice, it's definitely a member if (report.Success) { Members.Add(candidate); _successes.Add(report); return(true); } // Now, it did not validate against the constraints... if (Discriminator?.Any() == true) { // Get the full path of the discriminator, which is rooted in the current instance path var baseInstancePath = candidate.Location; // remove all the [num] (from the instance path) and [x] (from the discriminator path) in one go, // so a path looking like this remains as a discriminator: Patient.deceased // (note won't work if deceasedBoolean is allowed as a discriminator vlaue) var discriminatorPaths = Discriminator.Select(d => strip(baseInstancePath + "." + d)).ToArray(); if (errorOnDiscriminator(discriminatorPaths, report)) { // Failed on a discriminator => this instance does not belong to this slice return(false); } else { // Validated against the discriminating elements, instance belongs to this slice although there // are validation errors on other elements Members.Add(candidate); _failures.Add(report); return(true); } } else { // No discriminator, and validation failed => not a member of this slice return(false); } }
public static MatchResult Match(ElementDefinitionNavigator definitionParent, ScopedNavigator instanceParent) { var definitionElements = harvestDefinitionNames(definitionParent); var elementsToMatch = instanceParent.Children().Cast <ScopedNavigator>().ToList(); List <Match> matches = new List <Match>(); foreach (var definitionElement in definitionElements) { var match = new Match() { Definition = definitionElement, InstanceElements = new List <ScopedNavigator>() }; // Special case is the .value of a primitive fhir type, this is represented // as the "Value" of the IValueProvider interface, not as a real child if (definitionElement.Current.IsPrimitiveValueConstraint()) { if (instanceParent.Value != null) { match.InstanceElements.Add(instanceParent); } } else { var definitionPath = ProfileNavigationExtensions.GetNameFromPath(definitionElement.Current?.Base?.Path ?? definitionElement.Path); var found = elementsToMatch.Where(ie => NameMatches(definitionPath, ie)).ToList(); match.InstanceElements.AddRange(found); elementsToMatch.RemoveAll(e => found.Contains(e)); } matches.Add(match); } MatchResult result = new MatchResult(); result.Matches = matches; result.UnmatchedInstanceElements = elementsToMatch; return(result); }
public bool Add(ScopedNavigator candidate) { return(Entry.Add(candidate)); }
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); }
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); } }
private Func <OperationOutcome> createValidator(ElementDefinitionNavigator nav, ScopedNavigator instance) { return(() => validateElement(nav, instance)); }
// 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); }
internal OperationOutcome Validate(ScopedNavigator instance, ElementDefinitionNavigator definition) { return(Validate(instance, new[] { definition })); }
private static OperationOutcome ValidateMatch(this Validator validator, Match match, ScopedNavigator 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 void ResolvePersonWithTwoNameSections() { FhirPathCompiler compiler = new FhirPathCompiler(CustomFhirPathFunctions.GetSymbolTable()); Person person = new Person { Id = "1", Identifier = new List <Identifier> { new Identifier { System = "2.16.578.1.12.4.1.4.1", Value = "16027512345" } }, Name = new List <HumanName> { new HumanName { Use = HumanName.NameUse.Official, Given = new[] { "Kenneth", }, Family = "Myhra" }, new HumanName { Use = HumanName.NameUse.Old, Given = new[] { "Kenneth" }, Family = "AnnetEtternavn" } }, Telecom = new List <ContactPoint> { new ContactPoint { Use = ContactPoint.ContactPointUse.Mobile, System = ContactPoint.ContactPointSystem.Phone, Value = "93228677" }, new ContactPoint { Use = ContactPoint.ContactPointUse.Home, System = ContactPoint.ContactPointSystem.Phone, Value = "93228677" }, new ContactPoint { Use = ContactPoint.ContactPointUse.Home, System = ContactPoint.ContactPointSystem.Email, Value = "*****@*****.**" } }, Address = new List <Address> { new Address { Use = Address.AddressUse.Home, Line = new [] { "Snipemyrveien 16" }, PostalCode = "1273", City = "Oslo", Country = "Norway" } } }; var personNavigator = new ScopedNavigator(new PocoNavigator(person)); // Retrieve official name instance, // then concatenate given and family separating them by a white space IEnumerable <IElementNavigator> result = personNavigator.Select("name.where(use = 'official').select(given & ' ' & family)", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Kenneth Myhra", result.Single().Value); // Retrieve the distinct values of name and given in a collection for all name instances result = personNavigator.Select("(name.given | name.family)", compiler: compiler); Assert.AreEqual(3, result.Count()); Trace.WriteLine(result.ToString()); Assert.AreEqual("Kenneth", result.First().Value); Assert.AreEqual("Myhra", result.Skip(1).First().Value); Assert.AreEqual("AnnetEtternavn", result.Skip(2).First().Value); // Find identifier that represent FNR result = personNavigator.Select("Person.identifier.where(system = '2.16.578.1.12.4.1.4.1').value", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("16027512345", result.First().Value); // Find patient with family equal to 'Myhra' result = personNavigator.Select("Person.name.where(family = 'Myhra').family", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Myhra", result.First().Value); // Retrieve all name instances result = personNavigator.Select("Person.name.select(given.join(' ') & ' ' & family)", compiler: compiler); Assert.AreEqual(2, result.Count()); var name = result.First().Value; Assert.AreEqual("Kenneth Myhra", name); name = result.Skip(1).First().Value; Assert.AreEqual("Kenneth AnnetEtternavn", name); // Retrieve first name instance result = personNavigator.Select("Person.name.select(given & ' ' & family).first()", compiler: compiler); Assert.AreEqual(1, result.Count()); name = result.SingleOrDefault().Value; Assert.IsNotNull(name); Assert.AreEqual("Kenneth Myhra", name); // Retrieve last name instance result = personNavigator.Select("Person.name.select(given & ' ' & family).last()", compiler: compiler); Assert.AreEqual(1, result.Count()); name = result.SingleOrDefault().Value; Assert.IsNotNull(name); Assert.AreEqual("Kenneth AnnetEtternavn", name); // Norwegian First name standard result = personNavigator.Select("Person.name.where(use = 'official').select(iif(given.count() > 1, given.take(count()-1), given).join(' '))", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Kenneth", result.Single().Value); // Norwegian middle name standard result = personNavigator.Select("Person.name.where(use = 'official').select(iif(given.count() > 1, given.last(), ''))", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("", result.Single().Value); // Family name / surname result = personNavigator.Select("Person.name.where(use = 'official').family", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Myhra", result.Single().Value); // Full name result = personNavigator.Select("Person.name.where(use = 'official').select(given.join(' ') & ' ' & family)", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Kenneth Myhra", result.Single().Value); // Phone number result = personNavigator.Select("Person.telecom.where(use = 'home' and system = 'phone').value"); Assert.AreEqual(1, result.Count()); Assert.AreEqual("93228677", result.Single().Value); // E-mail result = personNavigator.Select("Person.telecom.where(use = 'home' and system = 'email').value"); Assert.AreEqual(1, result.Count()); Assert.AreEqual("*****@*****.**", result.Single().Value); // Adresselinje 1 result = personNavigator.Select("Person.address.where(use = 'home').line.first()"); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Snipemyrveien 16", result.Single().Value); // Adresselinje 2 result = personNavigator.Select("Person.address.where(use = 'home').line.skip(1).first()"); Assert.AreEqual(0, result.Count()); // Adresselinje 3 result = personNavigator.Select("Person.address.where(use = 'home').line.skip(2).first()"); Assert.AreEqual(0, result.Count()); // Postnummer result = personNavigator.Select("Person.address.where(use = 'home').postalCode"); Assert.AreEqual(1, result.Count()); Assert.AreEqual("1273", result.Single().Value); // Poststed result = personNavigator.Select("Person.address.where(use = 'home').city"); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Oslo", result.Single().Value); // Land result = personNavigator.Select("Person.address.where(use = 'home').country"); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Norway", result.Single().Value); // -------------- // person2 has multiple given names. Person person2 = new Person { Id = "1", Identifier = new List <Identifier> { new Identifier { System = "2.16.578.1.12.4.1.4.1", Value = "16027512345" } }, Name = new List <HumanName> { new HumanName { Use = HumanName.NameUse.Official, Given = new[] { "Lars", "Kristoffer", "Ulstein" }, Family = "Jørgensen" } } }; var personNavigator2 = new ScopedNavigator(new PocoNavigator(person2)); // Norwegian First name standard result = personNavigator2.Select("Person.name.where(use = 'official').select(iif(given.count() > 1, given.take(count()-1), given).join(' '))", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Lars Kristoffer", result.Single().Value); // Norwegian middle name standard result = personNavigator2.Select("Person.name.where(use = 'official').select(iif(given.count() > 1, given.last(), ''))", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Ulstein", result.Single().Value); // Family name / surname result = personNavigator2.Select("Person.name.where(use = 'official').family", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Jørgensen", result.Single().Value); // Full name result = personNavigator2.Select("Person.name.where(use = 'official').select(given.join(' ') & ' ' & family)", compiler: compiler); Assert.AreEqual(1, result.Count()); Assert.AreEqual("Lars Kristoffer Ulstein Jørgensen", result.Single().Value); }
public abstract bool Add(ScopedNavigator instance);
private static Func <OperationOutcome> createValidatorForTypeRef(Validator validator, ScopedNavigator instance, ElementDefinition.TypeRefComponent tr) { // In STU3, we need to do BOTH // First, call Validate() against the profile (which is then a profile on Reference) THEN validate the referenced resource if (tr.Code == FHIRDefinedType.Reference) { return(() => validator.ValidateResourceReference(instance, tr)); } else { return(() => validator.Validate(instance, tr.GetDeclaredProfiles(), statedCanonicals: null, statedProfiles: 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); }
internal static OperationOutcome ValidateResourceReference(this Validator validator, ScopedNavigator 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); }