private async Task <Bug.Logic.DomainModel.SearchParameter?> GetSearchParameter(string parameterName) { Bug.Logic.DomainModel.SearchParameter SearchParameter; List <Bug.Logic.DomainModel.SearchParameter> SearchParameterList = await ISearchParameterCache.GetForIndexingAsync(this.FhirVersion, this.ResourceContext); //Here we go through a series of ways to locate the SearchParameter for each segment of the chain query if (PreviousChainSearchParameter is null) { //If there is no previous then we look through the search parameter for the root resource type stored in this.ResourceContext SearchParameter = SearchParameterList.SingleOrDefault(x => x.Name == parameterName); if (SearchParameter is null) { ErrorInSearchParameterProcessing = true; UnsupportedSearchQueryParameterList.Add(new InvalidSearchQueryParameter(this.RawParameter, $"The resource search parameter name of: {parameterName} within the chained search query of: {this.RawParameter} is not a known search parameter for the resource: {this.ResourceContext} for this server in FHIR version: {this.FhirVersion.GetCode()}")); return(null); } else { return(SearchParameter); } } else { //Here we are using the PreviousChainSearchParameter's TypeModifierResource as the context to find the search parameter if (!PreviousChainSearchParameter.TypeModifierResource.HasValue) { //If there is no TypeModifierResource on the previous then we look at how many it supports and if only one we can use that. if (PreviousChainSearchParameter.TargetResourceTypeList.Count == 1) { PreviousChainSearchParameter.TypeModifierResource = PreviousChainSearchParameter.TargetResourceTypeList.ToArray()[0].ResourceTypeId; List <Bug.Logic.DomainModel.SearchParameter> SearchParametersListForTarget = await ISearchParameterCache.GetForIndexingAsync(this.FhirVersion, PreviousChainSearchParameter.TargetResourceTypeList.ToArray()[0].ResourceTypeId); Bug.Logic.DomainModel.SearchParameter SearchParameterForTarget = SearchParametersListForTarget.SingleOrDefault(x => x.Name == parameterName); if (SearchParameterForTarget is null) { string Message = $"Unable to locate the search parameter named: {parameterName} for the resource type: {PreviousChainSearchParameter.TypeModifierResource} for FHIR version: {this.FhirVersion.GetCode()} within the chain search query of: {this.RawParameter}"; ErrorInSearchParameterProcessing = true; InvalidSearchQueryParameterList.Add(new InvalidSearchQueryParameter(this.RawParameter, Message)); return(null); } else { SearchParameter = SearchParameterForTarget; return(SearchParameter); } } else { //If more than one then we search for the given search parameter name among all resource types supported for the PreviousChainSearchParameter Dictionary <ResourceType, Bug.Logic.DomainModel.SearchParameter> MultiChainedSearchParameter = new Dictionary <ResourceType, DomainModel.SearchParameter>(); foreach (var TargetResourceType in PreviousChainSearchParameter.TargetResourceTypeList) { List <Bug.Logic.DomainModel.SearchParameter> SearchParametersListForTarget = await ISearchParameterCache.GetForIndexingAsync(this.FhirVersion, TargetResourceType.ResourceTypeId); Bug.Logic.DomainModel.SearchParameter SearchParameterForTarget = SearchParametersListForTarget.SingleOrDefault(x => x.Name == parameterName); if (SearchParameterForTarget != null) { MultiChainedSearchParameter.Add(TargetResourceType.ResourceTypeId, SearchParameterForTarget); } } if (MultiChainedSearchParameter.Count() == 1) { //If this resolves to only one found then we use it PreviousChainSearchParameter.TypeModifierResource = MultiChainedSearchParameter.First().Key; SearchParameter = MultiChainedSearchParameter.First().Value; return(SearchParameter); } else { if (MultiChainedSearchParameter.Count > 1) { //We still have many to choose from so it cannot be resolved. The user need to specify the ResourceType with the Type modifier on the search parameter query e.g subject:Patient.family string RefResources = string.Empty; foreach (var DicItem in MultiChainedSearchParameter) { RefResources += ", " + DicItem.Key.GetCode(); } string ResourceName = this.ResourceContext.GetCode(); string Message = string.Empty; Message = $"The chained search parameter '{this.RawParameter}' is ambiguous. "; Message += $"Additional information: "; Message += $"The search parameter '{parameterName}' could be a search parameter for any of the following resource types ({RefResources.TrimStart(',').Trim()}). "; Message += $"To correct this you must prefix the search parameter with a Type modifier, for example: '{PreviousChainSearchParameter.Name}:{MultiChainedSearchParameter.First().Key.GetCode()}.{MultiChainedSearchParameter.First().Value.Name}' "; Message += $"If the '{MultiChainedSearchParameter.First().Key.GetCode()}' resource was the intended reference for the search parameter '{MultiChainedSearchParameter.First().Value.Name}'."; ErrorInSearchParameterProcessing = true; InvalidSearchQueryParameterList.Add(new InvalidSearchQueryParameter(this.RawParameter, Message)); return(null); } else { //We have found zero matches for this search parameter name from the previous allowed resource types, so the search parameter name is possibly wrong. string TargetResourceTypes = string.Empty; foreach (var TargetResourceType in PreviousChainSearchParameter.TargetResourceTypeList) { TargetResourceTypes += ", " + TargetResourceType.ResourceTypeId.GetCode(); } string ResourceName = this.ResourceContext.GetCode(); string Message = string.Empty; Message = $"The chained search parameter '{this.RawParameter}' is unresolvable. "; Message += $"Additional information: "; Message += $"The search parameter: {parameterName} should be a search parameter for any of the following resource types ({TargetResourceTypes.TrimStart(',').Trim()}) as resolved from the previous link in the chain: {PreviousChainSearchParameter.Name}. "; Message += $"To correct this you must specify a search parameter here that is supported by those resource types. "; Message += $"Please review your chained search query and specifically the use of the search parameter: {parameterName}'"; ErrorInSearchParameterProcessing = true; InvalidSearchQueryParameterList.Add(new InvalidSearchQueryParameter(this.RawParameter, Message)); return(null); } } } } else if (CheckModifierTypeResourceValidForSearchParameter(PreviousChainSearchParameter.TypeModifierResource.Value, PreviousChainSearchParameter.TargetResourceTypeList)) { //PreviousChainSearchParameter.TypeModifierResource = PreviousChainSearchParameter.TypeModifierResource; //Double check the final Type modifier resource resolved is valid for the previous search parameter, the user could have got it wrong in the query. ResourceType ResourceTypeTest = PreviousChainSearchParameter.TypeModifierResource.Value; FhirVersion FhirVersionTest = this.FhirVersion; var TempSearchParameterList = await this.ISearchParameterCache.GetForIndexingAsync(FhirVersionTest, ResourceTypeTest); SearchParameter = TempSearchParameterList.SingleOrDefault(x => x.Name == parameterName); if (SearchParameter is object) { return(SearchParameter); } else { string ResourceName = ResourceTypeTest.GetCode(); string Message = $"The chained search query part: {parameterName} is not a supported search parameter name for the resource type: {ResourceName} for this server in FHIR version {this.FhirVersion.GetCode()}. "; Message += $"Additional information: "; Message += $"This search parameter was a chained search parameter. The part that was not recognized was: {parameterName}."; ErrorInSearchParameterProcessing = true; InvalidSearchQueryParameterList.Add(new InvalidSearchQueryParameter(this.RawParameter, Message)); return(null); } } else { //The modifier target resource provided is not valid for the previous reference, e.g subject:DiagnosticReport.family=millar string ResourceName = this.ResourceContext.GetCode(); string Message = $"The search parameter '{parameterName}' is not supported by this server for the resource type '{ResourceName}'. "; Message += $"Additional information: "; Message += $"This search parameter was a chained search parameter. The part that was not recognized was '{PreviousChainSearchParameter.Name}.{parameterName}', The search parameter modifier given '{PreviousChainSearchParameter.TypeModifierResource}' is not valid for the search parameter {PreviousChainSearchParameter.Name}. "; ErrorInSearchParameterProcessing = true; InvalidSearchQueryParameterList.Add(new InvalidSearchQueryParameter(this.RawParameter, Message)); return(null); } } }
public void IncludeQueryPositive(FhirVersion fhirVersion, ResourceType resourceContext, IncludeType parameterInclude, string isRecurseIterate, ResourceType sourceResource, string searchParameterName, ResourceType?modifierResource) { //Prepare SearchQueryService SearchQueryService = SetupSearchQueryService(); Dictionary <string, StringValues> QueryDictonary; if (!string.IsNullOrWhiteSpace(isRecurseIterate)) { if (modifierResource.HasValue) { QueryDictonary = new Dictionary <string, StringValues> { { $"{parameterInclude.GetCode()}:{isRecurseIterate}", new StringValues($"{sourceResource.GetCode()}:{searchParameterName}:{modifierResource.GetCode()}") }, }; } else { QueryDictonary = new Dictionary <string, StringValues> { { $"{parameterInclude.GetCode()}:{isRecurseIterate}", new StringValues($"{sourceResource.GetCode()}:{searchParameterName}") }, }; } } else { if (modifierResource.HasValue) { QueryDictonary = new Dictionary <string, StringValues> { { $"{parameterInclude.GetCode()}", new StringValues($"{sourceResource.GetCode()}:{searchParameterName}:{modifierResource.GetCode()}") }, }; } else { QueryDictonary = new Dictionary <string, StringValues> { { $"{parameterInclude.GetCode()}", new StringValues($"{sourceResource.GetCode()}:{searchParameterName}") }, }; } } FhirSearchQuery FhirSearchQuery = new FhirSearchQuery(); FhirSearchQuery.Parse(QueryDictonary); //Act ISerachQueryServiceOutcome Outcome = SearchQueryService.Process(fhirVersion, resourceContext, FhirSearchQuery).Result; //Assert Assert.NotNull(Outcome); Assert.Equal(0, Outcome.InvalidSearchQueryList.Count); Assert.Null(Outcome.CountRequested); Assert.Equal(0, Outcome.SearchQueryList.Count); Assert.Equal(Common.Enums.ResourceType.Observation, Outcome.ResourceContext); Assert.Equal(1, Outcome.IncludeList.Count); var IncludeItem = Outcome.IncludeList[0]; if (isRecurseIterate == "recurse") { Assert.True(IncludeItem.IsRecurse); Assert.True(IncludeItem.IsRecurseIterate); } else { Assert.False(IncludeItem.IsRecurse); } if (isRecurseIterate == "iterate") { Assert.True(IncludeItem.IsIterate); Assert.True(IncludeItem.IsRecurseIterate); } else { Assert.False(IncludeItem.IsIterate); } Assert.Equal(parameterInclude, IncludeItem.Type); Assert.Equal(sourceResource, IncludeItem.SourceResourceType); if (modifierResource.HasValue) { Assert.Equal(modifierResource, IncludeItem.SearchParameterTargetResourceType); } else { Assert.Null(IncludeItem.SearchParameterTargetResourceType); } Assert.Single(IncludeItem.SearchParameterList); var SearchParameter = IncludeItem.SearchParameterList[0]; Assert.Equal(searchParameterName, SearchParameter.Name); }