public SearchOptions Create(string compartmentType, string compartmentId, string resourceType, IReadOnlyList <Tuple <string, string> > queryParameters) { var searchOptions = new SearchOptions(); string continuationToken = null; var searchParams = new SearchParams(); var unsupportedSearchParameters = new List <Tuple <string, string> >(); bool setDefaultBundleTotal = true; // Extract the continuation token, filter out the other known query parameters that's not search related. foreach (Tuple <string, string> query in queryParameters ?? Enumerable.Empty <Tuple <string, string> >()) { if (query.Item1 == KnownQueryParameterNames.ContinuationToken) { // This is an unreachable case. The mapping of the query parameters makes it so only one continuation token can exist. if (continuationToken != null) { throw new InvalidSearchOperationException( string.Format(Core.Resources.MultipleQueryParametersNotAllowed, KnownQueryParameterNames.ContinuationToken)); } try { continuationToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(query.Item2)); } catch (FormatException) { throw new BadRequestException(Core.Resources.InvalidContinuationToken); } setDefaultBundleTotal = false; } else if (query.Item1 == KnownQueryParameterNames.Format) { // TODO: We need to handle format parameter. } else if (string.Equals(query.Item1, KnownQueryParameterNames.Type, StringComparison.OrdinalIgnoreCase)) { var types = query.Item2.SplitByOrSeparator(); var badTypes = types.Where(type => !ModelInfoProvider.IsKnownResource(type)).ToHashSet(); if (badTypes.Count != 0) { _contextAccessor.FhirRequestContext.BundleIssues.Add( new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Warning, OperationOutcomeConstants.IssueType.NotSupported, string.Format(Core.Resources.InvalidTypeParameter, badTypes.OrderBy(x => x).Select(type => $"'{type}'").JoinByOrSeparator()))); if (badTypes.Count != types.Count) { // In case of we have acceptable types, we filter invalid types from search. searchParams.Add(KnownQueryParameterNames.Type, types.Except(badTypes).JoinByOrSeparator()); } else { // If all types are invalid, we add them to search params. If we remove them, we wouldn't filter by type, and return all types, // which is incorrect behaviour. Optimally we should indicate in search options what it would yield nothing, and skip search, // but there is no option for that right now. searchParams.Add(KnownQueryParameterNames.Type, query.Item2); } } else { searchParams.Add(KnownQueryParameterNames.Type, query.Item2); } } else if (string.IsNullOrWhiteSpace(query.Item1) || string.IsNullOrWhiteSpace(query.Item2)) { // Query parameter with empty value is not supported. unsupportedSearchParameters.Add(query); } else if (string.Compare(query.Item1, KnownQueryParameterNames.Total, StringComparison.OrdinalIgnoreCase) == 0) { if (Enum.TryParse <TotalType>(query.Item2, true, out var totalType)) { ValidateTotalType(totalType); searchOptions.IncludeTotal = totalType; setDefaultBundleTotal = false; } else { throw new BadRequestException(string.Format(Core.Resources.InvalidTotalParameter, query.Item2, SupportedTotalTypes)); } } else { // Parse the search parameters. try { // Basic format checking (e.g. integer value for _count key etc.). searchParams.Add(query.Item1, query.Item2); } catch (Exception ex) { throw new BadRequestException(ex.Message); } } } searchOptions.ContinuationToken = continuationToken; if (setDefaultBundleTotal) { ValidateTotalType(_featureConfiguration.IncludeTotalInBundle); searchOptions.IncludeTotal = _featureConfiguration.IncludeTotalInBundle; } // Check the item count. if (searchParams.Count != null) { searchOptions.MaxItemCountSpecifiedByClient = true; if (searchParams.Count > _featureConfiguration.MaxItemCountPerSearch) { searchOptions.MaxItemCount = _featureConfiguration.MaxItemCountPerSearch; _contextAccessor.FhirRequestContext.BundleIssues.Add( new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Information, OperationOutcomeConstants.IssueType.Informational, string.Format(Core.Resources.SearchParamaterCountExceedLimit, _featureConfiguration.MaxItemCountPerSearch, searchParams.Count))); } else { searchOptions.MaxItemCount = searchParams.Count.Value; } } else { searchOptions.MaxItemCount = _featureConfiguration.DefaultItemCountPerSearch; } searchOptions.IncludeCount = _featureConfiguration.DefaultIncludeCountPerSearch; // Check to see if only the count should be returned searchOptions.CountOnly = searchParams.Summary == SummaryType.Count; // If the resource type is not specified, then the common // search parameters should be used. ResourceType parsedResourceType = ResourceType.DomainResource; if (!string.IsNullOrWhiteSpace(resourceType) && !Enum.TryParse(resourceType, out parsedResourceType)) { throw new ResourceNotSupportedException(resourceType); } var searchExpressions = new List <Expression>(); if (!string.IsNullOrWhiteSpace(resourceType)) { searchExpressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, resourceType, false))); } searchExpressions.AddRange(searchParams.Parameters.Select( q => { try { return(_expressionParser.Parse(parsedResourceType.ToString(), q.Item1, q.Item2)); } catch (SearchParameterNotSupportedException) { unsupportedSearchParameters.Add(q); return(null); } }) .Where(item => item != null)); if (searchParams.Include?.Count > 0) { searchExpressions.AddRange(searchParams.Include.Select( q => _expressionParser.ParseInclude(parsedResourceType.ToString(), q, false /* not reversed */, false /* no iterate */)) .Where(item => item != null)); } if (searchParams.RevInclude?.Count > 0) { searchExpressions.AddRange(searchParams.RevInclude.Select( q => _expressionParser.ParseInclude(parsedResourceType.ToString(), q, true /* reversed */, false /* no iterate */)) .Where(item => item != null)); } // Parse _include:iterate (_include:recurse) parameters. // :iterate (:recurse) modifiers are not supported by Hl7.Fhir.Rest, hence not added to the Include collection and exist in the Parameters list. // See https://github.com/FirelyTeam/fhir-net-api/issues/222 // _include:iterate (_include:recurse) expression may appear without a preceding _include parameter // when applied on a circular reference searchExpressions.AddRange(ParseIncludeIterateExpressions(searchParams)); // remove _include:iterate and _revinclude:iterate parameters from unsupportedSearchParameters unsupportedSearchParameters.RemoveAll(p => AllIterateModifiers.Contains(p.Item1)); if (!string.IsNullOrWhiteSpace(compartmentType)) { if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType)) { if (string.IsNullOrWhiteSpace(compartmentId)) { throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid); } searchExpressions.Add(Expression.CompartmentSearch(compartmentType, compartmentId)); } else { throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType)); } } if (searchExpressions.Count == 1) { searchOptions.Expression = searchExpressions[0]; } else if (searchExpressions.Count > 1) { searchOptions.Expression = Expression.And(searchExpressions.ToArray()); } if (unsupportedSearchParameters.Any()) { // TODO: Client can specify whether exception should be raised or not when it encounters unknown search parameters. // For now, we will ignore any unknown search parameters. } searchOptions.UnsupportedSearchParams = unsupportedSearchParameters; if (searchParams.Sort?.Count > 0) { var sortings = new List <(SearchParameterInfo, SortOrder)>(searchParams.Sort.Count); bool sortingsValid = true; foreach (Tuple <string, Hl7.Fhir.Rest.SortOrder> sorting in searchParams.Sort) { try { SearchParameterInfo searchParameterInfo = _searchParameterDefinitionManager.GetSearchParameter(parsedResourceType.ToString(), sorting.Item1); sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder())); } catch (SearchParameterNotSupportedException) { sortingsValid = false; _contextAccessor.FhirRequestContext.BundleIssues.Add(new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Warning, OperationOutcomeConstants.IssueType.NotSupported, string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchParameterNotSupported, sorting.Item1, parsedResourceType.ToString()))); } } if (sortingsValid) { if (!_sortingValidator.ValidateSorting(sortings, out IReadOnlyList <string> errorMessages)) { if (errorMessages == null || errorMessages.Count == 0) { throw new InvalidOperationException($"Expected {_sortingValidator.GetType().Name} to return error messages when {nameof(_sortingValidator.ValidateSorting)} returns false"); } sortingsValid = false; foreach (var errorMessage in errorMessages) { _contextAccessor.FhirRequestContext.BundleIssues.Add(new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Warning, OperationOutcomeConstants.IssueType.NotSupported, errorMessage)); } } } if (sortingsValid) { searchOptions.Sort = sortings; } } if (searchOptions.Sort == null) { searchOptions.Sort = Array.Empty <(SearchParameterInfo searchParameterInfo, SortOrder sortOrder)>(); } return(searchOptions); IEnumerable <IncludeExpression> ParseIncludeIterateExpressions(SearchParams searchParams) { return(searchParams.Parameters .Where(p => p != null && AllIterateModifiers.Where(m => string.Equals(p.Item1, m, StringComparison.OrdinalIgnoreCase)).Any()) .Select(p => { var includeResourceType = p.Item2?.Split(':')[0]; if (!ModelInfoProvider.IsKnownResource(includeResourceType)) { throw new ResourceNotSupportedException(includeResourceType); } var reversed = RevIncludeIterateModifiers.Contains(p.Item1); var expression = _expressionParser.ParseInclude(includeResourceType, p.Item2, reversed, true); // Reversed Iterate expressions (not wildcard) must specify target type if there is more than one possible target type if (expression.Reversed && expression.Iterate && expression.TargetResourceType == null && expression.ReferenceSearchParameter?.TargetResourceTypes?.Count > 1) { throw new BadRequestException(string.Format(Core.Resources.RevIncludeIterateTargetTypeNotSpecified, p.Item2)); } // For circular include iterate expressions, add an informational issue indicating that a single iteration is supported. // See https://www.hl7.org/fhir/search.html#revinclude. if (expression.Iterate && expression.CircularReference) { _contextAccessor.FhirRequestContext.BundleIssues.Add( new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Information, OperationOutcomeConstants.IssueType.Informational, string.Format(Core.Resources.IncludeIterateCircularReferenceExecutedOnce, p.Item1, p.Item2))); } return expression; })); } void ValidateTotalType(TotalType totalType) { // Estimate is not yet supported. if (totalType == TotalType.Estimate) { throw new SearchOperationNotSupportedException(string.Format(Core.Resources.UnsupportedTotalParameter, totalType, SupportedTotalTypes)); } } }
public SearchOptions Create(string compartmentType, string compartmentId, string resourceType, IReadOnlyList <Tuple <string, string> > queryParameters) { var options = new SearchOptions(); string continuationToken = null; var searchParams = new SearchParams(); var unsupportedSearchParameters = new List <Tuple <string, string> >(); // Extract the continuation token, filter out the other known query parameters that's not search related. foreach (Tuple <string, string> query in queryParameters ?? Enumerable.Empty <Tuple <string, string> >()) { if (query.Item1 == KnownQueryParameterNames.ContinuationToken) { if (continuationToken != null) { throw new InvalidSearchOperationException( string.Format(Core.Resources.MultipleQueryParametersNotAllowed, KnownQueryParameterNames.ContinuationToken)); } continuationToken = query.Item2; } else if (query.Item1 == KnownQueryParameterNames.Format) { // TODO: We need to handle format parameter. } else if (string.IsNullOrWhiteSpace(query.Item2)) { // Query parameter with empty value is not supported. unsupportedSearchParameters.Add(query); } else { // Parse the search parameters. try { searchParams.Add(query.Item1, query.Item2); } catch (Exception ex) { _logger.LogInformation(ex, "Failed to parse the query parameter. Skipping."); // There was a problem parsing the parameter. Add it to list of unsupported parameters. unsupportedSearchParameters.Add(query); } } } options.ContinuationToken = continuationToken; // Check the item count. if (searchParams.Count != null) { options.MaxItemCount = searchParams.Count.Value; } // Check to see if only the count should be returned options.CountOnly = searchParams.Summary == SummaryType.Count; // If the resource type is not specified, then the common // search parameters should be used. ResourceType parsedResourceType = ResourceType.DomainResource; if (!string.IsNullOrWhiteSpace(resourceType) && !Enum.TryParse(resourceType, out parsedResourceType)) { throw new ResourceNotSupportedException(resourceType); } var searchExpressions = new List <Expression>(); if (!string.IsNullOrWhiteSpace(resourceType)) { searchExpressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter.ToInfo(), Expression.Equals(FieldName.TokenCode, null, resourceType))); } searchExpressions.AddRange(searchParams.Parameters.Select( q => { try { return(_expressionParser.Parse(parsedResourceType.ToString(), q.Item1, q.Item2)); } catch (SearchParameterNotSupportedException) { unsupportedSearchParameters.Add(q); return(null); } }) .Where(item => item != null)); if (!string.IsNullOrWhiteSpace(compartmentType)) { if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType)) { if (string.IsNullOrWhiteSpace(compartmentId)) { throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid); } searchExpressions.Add(Expression.CompartmentSearch(compartmentType, compartmentId)); } else { throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType)); } } if (searchExpressions.Count == 1) { options.Expression = searchExpressions[0]; } else if (searchExpressions.Count > 1) { options.Expression = Expression.And(searchExpressions.ToArray()); } if (unsupportedSearchParameters.Any()) { // TODO: Client can specify whether exception should be raised or not when it encounters unknown search parameters. // For now, we will ignore any unknown search parameters. } options.UnsupportedSearchParams = unsupportedSearchParameters; return(options); }
public SearchOptions Create(string compartmentType, string compartmentId, string resourceType, IReadOnlyList <Tuple <string, string> > queryParameters) { var searchOptions = new SearchOptions(); string continuationToken = null; var searchParams = new SearchParams(); var unsupportedSearchParameters = new List <Tuple <string, string> >(); // Extract the continuation token, filter out the other known query parameters that's not search related. foreach (Tuple <string, string> query in queryParameters ?? Enumerable.Empty <Tuple <string, string> >()) { if (query.Item1 == KnownQueryParameterNames.ContinuationToken) { // This is an unreachable case. The mapping of the query parameters makes it so only one continuation token can exist. if (continuationToken != null) { throw new InvalidSearchOperationException( string.Format(Core.Resources.MultipleQueryParametersNotAllowed, KnownQueryParameterNames.ContinuationToken)); } // Checks if the continuation token is base 64 bit encoded. Needed for systems that have cached continuation tokens from before they were encoded. if (Base64FormatRegex.IsMatch(query.Item2)) { continuationToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(query.Item2)); } else { continuationToken = query.Item2; } } else if (query.Item1 == KnownQueryParameterNames.Format) { // TODO: We need to handle format parameter. } else if (string.IsNullOrWhiteSpace(query.Item1) || string.IsNullOrWhiteSpace(query.Item2)) { // Query parameter with empty value is not supported. unsupportedSearchParameters.Add(query); } else if (string.Compare(query.Item1, KnownQueryParameterNames.Total, StringComparison.OrdinalIgnoreCase) == 0) { // Estimate is not yet supported. var supportedTotalTypes = new string($"'{TotalType.Accurate}', '{TotalType.None}'").ToLower(CultureInfo.CurrentCulture); if (Enum.TryParse <TotalType>(query.Item2, true, out var totalType)) { if (totalType == TotalType.Estimate) { throw new SearchOperationNotSupportedException(string.Format(Core.Resources.UnsupportedTotalParameter, query.Item2, supportedTotalTypes)); } searchOptions.IncludeTotal = totalType; } else { throw new BadRequestException(string.Format(Core.Resources.InvalidTotalParameter, query.Item2, supportedTotalTypes)); } } else { // Parse the search parameters. try { // Basic format checking (e.g. integer value for _count key etc.). searchParams.Add(query.Item1, query.Item2); } catch (Exception ex) { throw new BadRequestException(ex.Message); } } } searchOptions.ContinuationToken = continuationToken; // Check the item count. if (searchParams.Count != null) { searchOptions.MaxItemCount = searchParams.Count.Value; } // Check to see if only the count should be returned searchOptions.CountOnly = searchParams.Summary == SummaryType.Count; // If the resource type is not specified, then the common // search parameters should be used. ResourceType parsedResourceType = ResourceType.DomainResource; if (!string.IsNullOrWhiteSpace(resourceType) && !Enum.TryParse(resourceType, out parsedResourceType)) { throw new ResourceNotSupportedException(resourceType); } var searchExpressions = new List <Expression>(); if (!string.IsNullOrWhiteSpace(resourceType)) { searchExpressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, resourceType, false))); } searchExpressions.AddRange(searchParams.Parameters.Select( q => { try { return(_expressionParser.Parse(parsedResourceType.ToString(), q.Item1, q.Item2)); } catch (SearchParameterNotSupportedException) { unsupportedSearchParameters.Add(q); return(null); } }) .Where(item => item != null)); if (searchParams.Include?.Count > 0) { searchExpressions.AddRange(searchParams.Include.Select( q => _expressionParser.ParseInclude(parsedResourceType.ToString(), q)) .Where(item => item != null)); } if (!string.IsNullOrWhiteSpace(compartmentType)) { if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType)) { if (string.IsNullOrWhiteSpace(compartmentId)) { throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid); } searchExpressions.Add(Expression.CompartmentSearch(compartmentType, compartmentId)); } else { throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType)); } } if (searchExpressions.Count == 1) { searchOptions.Expression = searchExpressions[0]; } else if (searchExpressions.Count > 1) { searchOptions.Expression = Expression.And(searchExpressions.ToArray()); } if (unsupportedSearchParameters.Any()) { // TODO: Client can specify whether exception should be raised or not when it encounters unknown search parameters. // For now, we will ignore any unknown search parameters. } searchOptions.UnsupportedSearchParams = unsupportedSearchParameters; if (searchParams.Sort?.Count > 0) { var sortings = new List <(SearchParameterInfo, SortOrder)>(); List <(string parameterName, string reason)> unsupportedSortings = null; foreach (Tuple <string, Hl7.Fhir.Rest.SortOrder> sorting in searchParams.Sort) { try { SearchParameterInfo searchParameterInfo = _searchParameterDefinitionManager.GetSearchParameter(parsedResourceType.ToString(), sorting.Item1); sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder())); } catch (SearchParameterNotSupportedException) { (unsupportedSortings ??= new List <(string parameterName, string reason)>()).Add((sorting.Item1, string.Format(Core.Resources.SearchParameterNotSupported, sorting.Item1, resourceType))); } } searchOptions.Sort = sortings; searchOptions.UnsupportedSortingParams = (IReadOnlyList <(string parameterName, string reason)>)unsupportedSortings ?? Array.Empty <(string parameterName, string reason)>(); } else { searchOptions.Sort = Array.Empty <(SearchParameterInfo searchParameterInfo, SortOrder sortOrder)>(); searchOptions.UnsupportedSortingParams = Array.Empty <(string parameterName, string reason)>(); } return(searchOptions); }
public void Test_TokenCaseCorrect() { CleanUpByIdentifier(ResourceType.Observation); Hl7.Fhir.Rest.FhirClient clientFhir = new Hl7.Fhir.Rest.FhirClient(StaticTestData.FhirEndpoint(), false); clientFhir.Timeout = 1000 * 720; // give the call a while to execute (particularly while debugging). //Add an Observation one with an Effective Period 10:00 AM to 11:00 AM FhirDateTime ObsOneEffectiveStart = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 10, 00, 00, new TimeSpan(8, 0, 0))); FhirDateTime ObsOneEffectiveEnd = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 11, 00, 00, new TimeSpan(8, 0, 0))); string ObsOneResourceId = string.Empty; Observation ObsOne = new Observation(); ObsOne.Identifier = new List<Identifier>(){ new Identifier() { System = StaticTestData.TestIdentiferSystem, Value = Common.Tools.FhirGuid.FhirGuid.NewFhirGuid() } }; ObsOne.Code = new CodeableConcept(); ObsOne.Code.Coding = new List<Coding>(); ObsOne.Code.Coding.Add(new Coding() { System = "http:/mytestcodesystem.com/system", Code = "ObsOne" }); var EffectiveObsOnePeriod = new Period(); EffectiveObsOnePeriod.StartElement = ObsOneEffectiveStart; EffectiveObsOnePeriod.EndElement = ObsOneEffectiveEnd; ObsOne.Effective = EffectiveObsOnePeriod; Observation ObsOneResult = null; try { ObsOneResult = clientFhir.Create(ObsOne); } catch (Exception Exec) { Assert.True(false, "Exception thrown on resource Create: " + Exec.Message); } Assert.NotNull(ObsOneResult, "Resource create by Updated returned resource of null"); ObsOneResourceId = ObsOneResult.Id; ObsOneResult = null; //Add an Observation one with an Effective Period 10:30 AM to 11:30 AM FhirDateTime ObsTwoEffectiveStart = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 10, 30, 00, new TimeSpan(8, 0, 0))); FhirDateTime ObsTwoEffectiveEnd = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 11, 30, 00, new TimeSpan(8, 0, 0))); string ObsTwoResourceId = string.Empty; Observation ObsTwo = new Observation(); ObsTwo.Identifier = new List<Identifier>(){ new Identifier() { System = StaticTestData.TestIdentiferSystem, Value = Common.Tools.FhirGuid.FhirGuid.NewFhirGuid() } }; ObsTwo.Code = new CodeableConcept(); ObsTwo.Code.Coding = new List<Coding>(); ObsTwo.Code.Coding.Add(new Coding() { System = "http:/mytestcodesystem.com/system", Code = "ObsTwo" }); var EffectiveObsTwoPeriod = new Period(); EffectiveObsTwoPeriod.StartElement = ObsTwoEffectiveStart; EffectiveObsTwoPeriod.EndElement = ObsTwoEffectiveEnd; ObsTwo.Effective = EffectiveObsTwoPeriod; Observation ObsTwoResult = null; try { ObsTwoResult = clientFhir.Create(ObsTwo); } catch (Exception Exec) { Assert.True(false, "Exception thrown on resource Create: " + Exec.Message); } Assert.NotNull(ObsTwoResult, "Resource create by Updated returned resource of null"); ObsTwoResourceId = ObsTwoResult.Id; ObsTwoResult = null; //Assert var SearchParam = new SearchParams(); try { //ObsOne = 10:00 to 11:00 //ObsTwo = 10:30 to 11:30 SearchParam.Add("identifier", $"{StaticTestData.TestIdentiferSystem}|"); SearchParam.Add("code", "http:/mytestcodesystem.com/system|ObsOne"); clientFhir.PreferredParameterHandling = SearchParameterHandling.Strict; Bundle BundleResult = clientFhir.Search<Observation>(SearchParam); Assert.IsNotNull(BundleResult); Assert.IsNotNull(BundleResult.Entry); Assert.AreEqual(1, BundleResult.Entry.Count); Assert.AreEqual(ObsOneResourceId, BundleResult.Entry[0].Resource.Id); } catch (Exception Exec) { Assert.True(false, "Exception thrown on resource Search: " + Exec.Message); } CleanUpByIdentifier(ResourceType.Observation); }
public void Test_TokenCaseInCorrect() { CleanUpByIdentifier(ResourceType.Observation); Hl7.Fhir.Rest.FhirClient clientFhir = new Hl7.Fhir.Rest.FhirClient(StaticTestData.FhirEndpoint(), false); clientFhir.Timeout = 1000 * 720; // give the call a while to execute (particularly while debugging). //Add an Observation one with an Effective Period 10:00 AM to 11:00 AM FhirDateTime ObsOneEffectiveStart = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 10, 00, 00, new TimeSpan(8, 0, 0))); FhirDateTime ObsOneEffectiveEnd = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 11, 00, 00, new TimeSpan(8, 0, 0))); string ObsOneResourceId = string.Empty; Observation ObsOne = new Observation(); ObsOne.Identifier = new List<Identifier>(){ new Identifier() { System = StaticTestData.TestIdentiferSystem, Value = Common.Tools.FhirGuid.FhirGuid.NewFhirGuid() } }; ObsOne.Code = new CodeableConcept(); ObsOne.Code.Coding = new List<Coding>(); ObsOne.Code.Coding.Add(new Coding() { System = "http:/mytestcodesystem.com/system", Code = "ObsOne" }); var EffectiveObsOnePeriod = new Period(); EffectiveObsOnePeriod.StartElement = ObsOneEffectiveStart; EffectiveObsOnePeriod.EndElement = ObsOneEffectiveEnd; ObsOne.Effective = EffectiveObsOnePeriod; Observation ObsOneResult = null; try { ObsOneResult = clientFhir.Create(ObsOne); } catch (Exception Exec) { Assert.True(false, "Exception thrown on resource Create: " + Exec.Message); } Assert.NotNull(ObsOneResult, "Resource create by Updated returned resource of null"); ObsOneResourceId = ObsOneResult.Id; ObsOneResult = null; //Add an Observation one with an Effective Period 10:30 AM to 11:30 AM FhirDateTime ObsTwoEffectiveStart = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 10, 30, 00, new TimeSpan(8, 0, 0))); FhirDateTime ObsTwoEffectiveEnd = new FhirDateTime(new DateTimeOffset(2018, 08, 5, 11, 30, 00, new TimeSpan(8, 0, 0))); string ObsTwoResourceId = string.Empty; Observation ObsTwo = new Observation(); ObsTwo.Identifier = new List<Identifier>(){ new Identifier() { System = StaticTestData.TestIdentiferSystem, Value = Common.Tools.FhirGuid.FhirGuid.NewFhirGuid() } }; ObsTwo.Code = new CodeableConcept(); ObsTwo.Code.Coding = new List<Coding>(); ObsTwo.Code.Coding.Add(new Coding() { System = "http:/mytestcodesystem.com/system", Code = "ObsTwo" }); var EffectiveObsTwoPeriod = new Period(); EffectiveObsTwoPeriod.StartElement = ObsTwoEffectiveStart; EffectiveObsTwoPeriod.EndElement = ObsTwoEffectiveEnd; ObsTwo.Effective = EffectiveObsTwoPeriod; Observation ObsTwoResult = null; try { ObsTwoResult = clientFhir.Create(ObsTwo); } catch (Exception Exec) { Assert.True(false, "Exception thrown on resource Create: " + Exec.Message); } Assert.NotNull(ObsTwoResult, "Resource create by Updated returned resource of null"); ObsTwoResourceId = ObsTwoResult.Id; ObsTwoResult = null; //Assert var SearchParam = new SearchParams(); try { //ObsOne = 10:00 to 11:00 //ObsTwo = 10:30 to 11:30 SearchParam.Add("identifier", $"{StaticTestData.TestIdentiferSystem}|"); SearchParam.Add("code", "http:/mytestcodesystem.com/system|obsone"); clientFhir.PreferredParameterHandling = SearchParameterHandling.Strict; Bundle BundleResult = clientFhir.Search<Observation>(SearchParam); //From thr R4 FHIR Spec //Note: There are many challenging issues around case senstivity and token searches. //Some code systems are case sensitive(e.g.UCUM) while others are known not to be.For many //code systems, it's ambiguous. Other kinds of values are also ambiguous. When in doubt, servers //SHOULD treat tokens in a case-insensitive manner, on the grounds that including undesired data //has less safety implications than excluding desired behavior. Clients SHOULD always use the //correct case when possible, and allow for the server to perform case-insensitive matching. //STU3 was a little vage on this point. Trying to conclude case sesativity based on CodeSystem is //a more difficult goal and would slow down searches, do not plan on doing that any time soon. Assert.IsNotNull(BundleResult); Assert.IsNotNull(BundleResult.Entry); Assert.AreEqual(1, BundleResult.Entry.Count); Assert.AreEqual(ObsOneResourceId, BundleResult.Entry[0].Resource.Id); } catch (Exception Exec) { Assert.True(false, "Exception thrown on resource Search: " + Exec.Message); } CleanUpByIdentifier(ResourceType.Observation); }
public SearchOptions Create(string compartmentType, string compartmentId, string resourceType, IReadOnlyList <Tuple <string, string> > queryParameters) { var searchOptions = new SearchOptions(); string continuationToken = null; var searchParams = new SearchParams(); var unsupportedSearchParameters = new List <Tuple <string, string> >(); bool setDefaultBundleTotal = true; // Extract the continuation token, filter out the other known query parameters that's not search related. foreach (Tuple <string, string> query in queryParameters ?? Enumerable.Empty <Tuple <string, string> >()) { if (query.Item1 == KnownQueryParameterNames.ContinuationToken) { // This is an unreachable case. The mapping of the query parameters makes it so only one continuation token can exist. if (continuationToken != null) { throw new InvalidSearchOperationException( string.Format(Core.Resources.MultipleQueryParametersNotAllowed, KnownQueryParameterNames.ContinuationToken)); } try { continuationToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(query.Item2)); } catch (FormatException) { throw new BadRequestException(Core.Resources.InvalidContinuationToken); } setDefaultBundleTotal = false; } else if (query.Item1 == KnownQueryParameterNames.Format || query.Item1 == KnownQueryParameterNames.Pretty) { // _format and _pretty are not search parameters, so we can ignore them. } else if (string.Equals(query.Item1, KnownQueryParameterNames.Type, StringComparison.OrdinalIgnoreCase)) { if (string.IsNullOrWhiteSpace(query.Item2)) { throw new BadRequestException(string.Format(Core.Resources.InvalidTypeParameter, query.Item2)); } var types = query.Item2.SplitByOrSeparator(); var badTypes = types.Where(type => !ModelInfoProvider.IsKnownResource(type)).ToHashSet(); if (badTypes.Count != 0) { _contextAccessor.RequestContext?.BundleIssues.Add( new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Warning, OperationOutcomeConstants.IssueType.NotSupported, string.Format(Core.Resources.InvalidTypeParameter, badTypes.OrderBy(x => x).Select(type => $"'{type}'").JoinByOrSeparator()))); if (badTypes.Count != types.Count) { // In case of we have acceptable types, we filter invalid types from search. searchParams.Add(KnownQueryParameterNames.Type, types.Except(badTypes).JoinByOrSeparator()); } else { // If all types are invalid, we add them to search params. If we remove them, we wouldn't filter by type, and return all types, // which is incorrect behaviour. Optimally we should indicate in search options what it would yield nothing, and skip search, // but there is no option for that right now. searchParams.Add(KnownQueryParameterNames.Type, query.Item2); } } else { searchParams.Add(KnownQueryParameterNames.Type, query.Item2); } } else if (string.IsNullOrWhiteSpace(query.Item1) || string.IsNullOrWhiteSpace(query.Item2)) { // Query parameter with empty value is not supported. unsupportedSearchParameters.Add(query); } else if (string.Compare(query.Item1, KnownQueryParameterNames.Total, StringComparison.OrdinalIgnoreCase) == 0) { if (Enum.TryParse <TotalType>(query.Item2, true, out var totalType)) { ValidateTotalType(totalType); searchOptions.IncludeTotal = totalType; setDefaultBundleTotal = false; } else { throw new BadRequestException(string.Format(Core.Resources.InvalidTotalParameter, query.Item2, SupportedTotalTypes)); } } else { // Parse the search parameters. try { // Basic format checking (e.g. integer value for _count key etc.). searchParams.Add(query.Item1, query.Item2); } catch (Exception ex) { throw new BadRequestException(ex.Message); } } } searchOptions.ContinuationToken = continuationToken; if (setDefaultBundleTotal) { ValidateTotalType(_featureConfiguration.IncludeTotalInBundle); searchOptions.IncludeTotal = _featureConfiguration.IncludeTotalInBundle; } // Check the item count. if (searchParams.Count != null) { searchOptions.MaxItemCountSpecifiedByClient = true; if (searchParams.Count > _featureConfiguration.MaxItemCountPerSearch) { searchOptions.MaxItemCount = _featureConfiguration.MaxItemCountPerSearch; _contextAccessor.RequestContext?.BundleIssues.Add( new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Information, OperationOutcomeConstants.IssueType.Informational, string.Format(Core.Resources.SearchParamaterCountExceedLimit, _featureConfiguration.MaxItemCountPerSearch, searchParams.Count))); } else { searchOptions.MaxItemCount = searchParams.Count.Value; } } else { searchOptions.MaxItemCount = _featureConfiguration.DefaultItemCountPerSearch; } searchOptions.IncludeCount = _featureConfiguration.DefaultIncludeCountPerSearch; if (searchParams.Elements?.Any() == true && searchParams.Summary != null && searchParams.Summary != SummaryType.False) { // The search parameters _elements and _summarize cannot be specified for the same request. throw new BadRequestException(string.Format(Core.Resources.ElementsAndSummaryParametersAreIncompatible, KnownQueryParameterNames.Summary, KnownQueryParameterNames.Elements)); } // Check to see if only the count should be returned searchOptions.CountOnly = searchParams.Summary == SummaryType.Count; // If the resource type is not specified, then the common // search parameters should be used. ResourceType[] parsedResourceTypes = new[] { ResourceType.DomainResource }; var searchExpressions = new List <Expression>(); if (string.IsNullOrWhiteSpace(resourceType)) { // Try to parse resource types from _type Search Parameter // This will result in empty array if _type has any modifiers // Which is good, since :not modifier changes the meaning of the // search parameter and we can no longer use it to deduce types // (and should proceed with ResourceType.DomainResource in that case) var resourceTypes = searchParams.Parameters .Where(q => q.Item1 == KnownQueryParameterNames.Type) // <-- Equality comparison to avoid modifiers .SelectMany(q => q.Item2.SplitByOrSeparator()) .Where(type => ModelInfoProvider.IsKnownResource(type)) .Select(x => { if (!Enum.TryParse(x, out ResourceType parsedType)) { // Should never get here throw new ResourceNotSupportedException(x); } return(parsedType); }) .Distinct(); if (resourceTypes.Any()) { parsedResourceTypes = resourceTypes.ToArray(); } } else { if (!Enum.TryParse(resourceType, out parsedResourceTypes[0])) { throw new ResourceNotSupportedException(resourceType); } searchExpressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, resourceType, false))); } var resourceTypesString = parsedResourceTypes.Select(x => x.ToString()).ToArray(); searchExpressions.AddRange(searchParams.Parameters.Select( q => { try { return(_expressionParser.Parse(resourceTypesString, q.Item1, q.Item2)); } catch (SearchParameterNotSupportedException) { unsupportedSearchParameters.Add(q); return(null); } }) .Where(item => item != null)); // Parse _include:iterate (_include:recurse) parameters. // _include:iterate (_include:recurse) expression may appear without a preceding _include parameter // when applied on a circular reference searchExpressions.AddRange(ParseIncludeIterateExpressions(searchParams.Include, resourceTypesString, false)); searchExpressions.AddRange(ParseIncludeIterateExpressions(searchParams.RevInclude, resourceTypesString, true)); if (!string.IsNullOrWhiteSpace(compartmentType)) { if (Enum.TryParse(compartmentType, out CompartmentType parsedCompartmentType)) { if (string.IsNullOrWhiteSpace(compartmentId)) { throw new InvalidSearchOperationException(Core.Resources.CompartmentIdIsInvalid); } searchExpressions.Add(Expression.CompartmentSearch(compartmentType, compartmentId)); } else { throw new InvalidSearchOperationException(string.Format(Core.Resources.CompartmentTypeIsInvalid, compartmentType)); } } if (searchExpressions.Count == 1) { searchOptions.Expression = searchExpressions[0]; } else if (searchExpressions.Count > 1) { searchOptions.Expression = Expression.And(searchExpressions.ToArray()); } if (unsupportedSearchParameters.Any()) { bool throwForUnsupported = false; if (_contextAccessor.RequestContext?.RequestHeaders != null && _contextAccessor.RequestContext.RequestHeaders.TryGetValue(KnownHeaders.Prefer, out var values)) { var handlingValue = values.FirstOrDefault(x => x.StartsWith("handling=", StringComparison.OrdinalIgnoreCase)); if (handlingValue != default) { handlingValue = handlingValue.Substring("handling=".Length); if (string.IsNullOrWhiteSpace(handlingValue) || !Enum.TryParse <SearchParameterHandling>(handlingValue, true, out var handling)) { throw new BadRequestException(string.Format( Core.Resources.InvalidHandlingValue, handlingValue, string.Join(",", Enum.GetNames <SearchParameterHandling>()))); } if (handling == SearchParameterHandling.Strict) { throwForUnsupported = true; } } } if (throwForUnsupported) { throw new BadRequestException(string.Format( Core.Resources.SearchParameterNotSupported, string.Join(",", unsupportedSearchParameters.Select(x => x.Item1)), string.Join(",", resourceTypesString))); } else { foreach (var unsupported in unsupportedSearchParameters) { _contextAccessor.RequestContext?.BundleIssues.Add(new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Warning, OperationOutcomeConstants.IssueType.NotSupported, string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchParameterNotSupported, unsupported.Item1, string.Join(",", resourceTypesString)))); } } } searchOptions.UnsupportedSearchParams = unsupportedSearchParameters; if (searchParams.Sort?.Count > 0) { var sortings = new List <(SearchParameterInfo, SortOrder)>(searchParams.Sort.Count); bool sortingsValid = true; foreach ((string, Hl7.Fhir.Rest.SortOrder)sorting in searchParams.Sort) { try { SearchParameterInfo searchParameterInfo = resourceTypesString.Select(t => _searchParameterDefinitionManager.GetSearchParameter(t, sorting.Item1)).Distinct().First(); sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder())); } catch (SearchParameterNotSupportedException) { sortingsValid = false; _contextAccessor.RequestContext?.BundleIssues.Add(new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Warning, OperationOutcomeConstants.IssueType.NotSupported, string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchParameterNotSupported, sorting.Item1, string.Join(", ", resourceTypesString)))); } } if (sortingsValid) { if (!_sortingValidator.ValidateSorting(sortings, out IReadOnlyList <string> errorMessages)) { if (errorMessages == null || errorMessages.Count == 0) { throw new InvalidOperationException($"Expected {_sortingValidator.GetType().Name} to return error messages when {nameof(_sortingValidator.ValidateSorting)} returns false"); } sortingsValid = false; foreach (var errorMessage in errorMessages) { _contextAccessor.RequestContext?.BundleIssues.Add(new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Warning, OperationOutcomeConstants.IssueType.NotSupported, errorMessage)); } } } if (sortingsValid) { searchOptions.Sort = sortings; } } if (searchOptions.Sort == null) { searchOptions.Sort = Array.Empty <(SearchParameterInfo searchParameterInfo, SortOrder sortOrder)>(); } return(searchOptions); IEnumerable <IncludeExpression> ParseIncludeIterateExpressions( IList <(string query, IncludeModifier modifier)> includes, string[] typesString, bool isReversed) { return(includes.Select(p => { var includeResourceTypeList = typesString; var iterate = p.modifier == IncludeModifier.Iterate || p.modifier == IncludeModifier.Recurse; if (iterate) { var includeResourceType = p.query?.Split(':')[0]; if (!ModelInfoProvider.IsKnownResource(includeResourceType)) { throw new ResourceNotSupportedException(includeResourceType); } includeResourceTypeList = new[] { includeResourceType }; } var expression = _expressionParser.ParseInclude(includeResourceTypeList, p.query, isReversed, iterate); // Reversed Iterate expressions (not wildcard) must specify target type if there is more than one possible target type if (expression.Reversed && expression.Iterate && expression.TargetResourceType == null && expression.ReferenceSearchParameter?.TargetResourceTypes?.Count > 1) { throw new BadRequestException( string.Format(Core.Resources.RevIncludeIterateTargetTypeNotSpecified, p.query)); } // For circular include iterate expressions, add an informational issue indicating that a single iteration is supported. // See https://www.hl7.org/fhir/search.html#revinclude. if (expression.Iterate && expression.CircularReference) { var issueProperty = string.Concat(isReversed ? "_revinclude" : "_include", ":", p.modifier.ToString().ToLowerInvariant()); _contextAccessor.RequestContext?.BundleIssues.Add( new OperationOutcomeIssue( OperationOutcomeConstants.IssueSeverity.Information, OperationOutcomeConstants.IssueType.Informational, string.Format(Core.Resources.IncludeIterateCircularReferenceExecutedOnce, issueProperty, p.query))); } return expression; })); } void ValidateTotalType(TotalType totalType) { // Estimate is not yet supported. if (totalType == TotalType.Estimate) { throw new SearchOperationNotSupportedException(string.Format(Core.Resources.UnsupportedTotalParameter, totalType, SupportedTotalTypes)); } } }
public Hl7.Fhir.Model.Bundle GetPatientByIdentifier(string system, string value) { if (string.IsNullOrWhiteSpace(system)) { throw new ArgumentException("message", nameof(system)); } if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("message", nameof(value)); } var activity = new ActivityLog { ActivityTypeDescriptorId = (int)Entities.Activity.ActivityType.GET_PACIENTE_EN_BUS_BY_IDENTIFIER }; //var baseUrl = "https://testapp.hospitalitaliano.org.ar/masterfile-federacion-service/fhir"; var baseUrl = _integrationServicesConfiguration.Services .First(x => x.ServiceName == "BUS").BaseURL; var client = new FhirClient(baseUrl); client.PreferredFormat = ResourceFormat.Json; client.OnBeforeRequest += (object sender, BeforeRequestEventArgs e) => { var requestAdderss = e.RawRequest.Address.ToString(); activity.RequestIsURL = true; activity.ActivityRequestUI = requestAdderss; _logger.LogInformation($"Send Request Address:{requestAdderss}"); }; client.OnAfterResponse += (object sender, AfterResponseEventArgs e) => { if (e.Body != null) { var responseBody = Encoding.UTF8.GetString(e.Body, 0, e.Body.Length); //Prettify !!! responseBody = JToken.Parse(responseBody).ToString(); activity.ResponseIsJson = true; activity.ActivityResponse = $"Status: {e.RawResponse.StatusCode}"; activity.ActivityResponseBody = responseBody; _logger.LogInformation($"Received response with status: {e.RawResponse.StatusCode}"); _logger.LogInformation($"Received response with body: {responseBody}"); } }; Hl7.Fhir.Model.Bundle ret = null; try { var qp = new SearchParams(); qp.Add("identifier", $"{system}|{value}"); ret = client.Search <Hl7.Fhir.Model.Patient>(qp); _currentContext.RegisterActivityLog(activity); } catch (Exception ex) { _logger.LogError(ex.ToString()); throw ex; } return(ret); }
/// <summary> /// Check for outgoing messages /// If new appts are found, they will be added, missing ones will be removed /// (not a flush and add in again - as this will lose data) /// </summary> /// <param name="model"></param> public static async System.Threading.Tasks.Task CheckAppointments(ArrivalsModel model) { var oldexptecting = model.Expecting.ToList(); var oldwaiting = model.Waiting.ToList(); var server = GetServerConnection(); var criteria = new SearchParams(); criteria.Add("date", "2020-03-19"); // TODO: Change this to today's date criteria.Include.Add("Appointment:actor"); var bundle = await server.SearchAsync <Appointment>(criteria); // Debugging var doc = System.Xml.Linq.XDocument.Parse(new Hl7.Fhir.Serialization.FhirXmlSerializer().SerializeToString(bundle)); Console.WriteLine(doc.ToString(System.Xml.Linq.SaveOptions.None)); Func <ResourceReference, System.Threading.Tasks.Task <Resource> > resolveReference = async(reference) => { if (string.IsNullOrEmpty(reference.Reference)) { return(null); } ResourceIdentity ri = new ResourceIdentity(reference.Reference); var resource = bundle.Entry.FirstOrDefault(e => e.Resource.ResourceType.GetLiteral() == ri.ResourceType && e.Resource.Id == ri.Id)?.Resource; if (resource == null) { // wasn't returned in the bundle, so go searching for it resource = await server.ReadAsync <Resource>(reference.Reference); } return(resource); }; foreach (var entry in bundle.Entry.Select(e => e.Resource as Appointment).Where(e => e != null)) { if (entry.Status == Appointment.AppointmentStatus.Booked) { PmsAppointment app = await ToPmsAppointment(entry, resolveReference); if (app != null && !model.Expecting.Contains(app)) { model.Expecting.Add(app); } } if (entry.Status == Appointment.AppointmentStatus.Arrived) { PmsAppointment app = await ToPmsAppointment(entry, resolveReference); if (app != null && !model.Waiting.Contains(app)) { model.Waiting.Add(app); } } } // finished processing all the items, so remove any that are left foreach (var item in oldexptecting) { model.Expecting.Remove(item); } foreach (var item in oldwaiting) { model.Waiting.Remove(item); } }
public SearchResourceViewModel GetResourceDetails(string patientId, string resouceInfo) { this.patientId = patientId; this.resouceInfo = resouceInfo; SearchResourceViewModel vm = new SearchResourceViewModel(); try { switch (resouceInfo) { //Patient Details case "PatientInfo": //Creating the endpoint url with patient patient parameter var ResourceUrl = ("/Patient/" + patientId); //Reading the Patient details using the FHIR Client var patientDetailsRead = FhirClient.Read <Patient>(ResourceUrl); //Serializing the data to json string string patientDetails = fhirJsonSerializer.SerializeToString(patientDetailsRead); //deserializing the json string to patient model vm.patient = (Patient)fhirJsonParser.Parse(patientDetails, typeof(Patient)); //Displaying the Div element of the text section vm.operationOutcomeError = vm.patient.Text == null ? string.Empty : vm.patient.Text.Div; //Displaying the RAW json data in the view vm.ResourceRawJsonData = JValue.Parse(patientDetails).ToString(); break; //Patient Encounter Details case "EncounterInfo": //Adding the Parameter to the URL to get the Encounter related to specific patient using the SearchParams parms.Add("patient", patientId); //Searching the Encounter for the patient var encounterDetailsRead = FhirClient.Search <Encounter>(parms); //Serializing the data to json string string encounterDetails = fhirJsonSerializer.SerializeToString(encounterDetailsRead); //Parsing the json string Bundle encounters = (Bundle)fhirJsonParser.Parse(encounterDetails, typeof(Bundle)); //Parsing the data to the encounter model vm.encounters = encounters.Entry.Select(Resource => (Encounter)Resource.Resource).ToList(); //Displaying the Div element of the text section foreach (var enc in vm.encounters) { vm.operationOutcomeError = enc.Text == null ? string.Empty : enc.Text.Div; } //Displaying the RAW json data in the view vm.ResourceRawJsonData = JValue.Parse(encounterDetails).ToString(); break; //Patient Medication Details case "MedicationRequest": //Adding the Parameter to the URL to get the Medication related to specific patient using the SearchParams var Status = "active"; parms.Add("patient", patientId); parms.Add("status", Status); //Searching the Medication Request for the patient var medicationDetailsRead = FhirClient.Search <MedicationRequest>(parms); //Serializing the data to json string string medicationDetails = fhirJsonSerializer.SerializeToString(medicationDetailsRead); //Parsing the json string Bundle medications = (Bundle)fhirJsonParser.Parse(medicationDetails, typeof(Bundle)); //Parsing the data to the medication model vm.medicationRequest = medications.Entry.Select(Resource => (MedicationRequest)Resource.Resource).ToList(); //Displaying the Div element of the text section foreach (var medreq in vm.medicationRequest) { vm.operationOutcomeError = medreq.Text == null ? string.Empty : medreq.Text.Div; } //Displaying the RAW json data in the view vm.ResourceRawJsonData = JValue.Parse(medicationDetails).ToString(); break; //Patient condition Details case "ConditionInfo": //Adding the Parameter to the URL to get the Condition related to specific patient using the SearchParams parms.Add("patient", patientId); //Searching the Conditons for the patient var conditionDetailsRead = FhirClient.Search <Condition>(parms); //Serializing the data to json string string conditionDetails = fhirJsonSerializer.SerializeToString(conditionDetailsRead); //Parsing the json string Bundle conditions = (Bundle)fhirJsonParser.Parse(conditionDetails, typeof(Bundle)); //Parsing the data to the condition model vm.conditions = conditions.Entry.Select(Resource => (Condition)Resource.Resource).ToList(); //Displaying the Div element of the text section foreach (var con in vm.conditions) { vm.operationOutcomeError = con.Text == null ? string.Empty : con.Text.Div; } //Displaying the RAW json data in the view vm.ResourceRawJsonData = JValue.Parse(conditionDetails).ToString(); break; //Patient observation Details case "ObservationInfo": //Adding the Parameter to the URL to get the Observation related to specific patient using the SearchParams parms.Add("patient", patientId); //Searching the Observation Request for the patient var observationDetailsRead = FhirClient.Search <Observation>(parms); //Serializing the data to json string string observationDetails = fhirJsonSerializer.SerializeToString(observationDetailsRead); //Parsing the json string Bundle observations = (Bundle)fhirJsonParser.Parse(observationDetails, typeof(Bundle)); //Parsing the data to the Observation model vm.observations = observations.Entry.Select(Resource => (Observation)Resource.Resource).ToList(); //Displaying the Div element of the text section foreach (var obs in vm.observations) { vm.operationOutcomeError = obs.Text == null ? string.Empty : obs.Text.Div; } //Displaying the RAW json data in the view vm.ResourceRawJsonData = JValue.Parse(observationDetails).ToString(); break; } } catch (FhirOperationException FhirOpExec) { var response = FhirOpExec.Outcome; var errorDetails = fhirJsonSerializer.SerializeToString(response); OperationOutcome error = (OperationOutcome)fhirJsonParser.Parse(errorDetails, typeof(OperationOutcome)); vm.operationOutcomeError = error.Text == null ? string.Empty : error.Text.Div; vm.ResourceRawJsonData = JValue.Parse(errorDetails).ToString(); } catch (WebException ex) { var response = new StreamReader(ex.Response.GetResponseStream()).ReadToEnd(); var error = JsonConvert.DeserializeObject(response); vm.ResourceRawJsonData = error.ToString(); } return(vm); }
public System.Threading.Tasks.Task <Bundle> Search(IEnumerable <KeyValuePair <string, string> > parameters, int?Count, SummaryType summary) { Bundle result = new Bundle(); result.Meta = new Meta(); result.Id = new Uri("urn:uuid:" + Guid.NewGuid().ToString("n")).OriginalString; result.Type = Bundle.BundleType.Searchset; result.ResourceBase = RequestDetails.BaseUri; result.Total = 0; // If there was no count provided, we'll just default in a value if (!Count.HasValue) { Count = 40; } // TODO: Thread the requests... // System.Threading.Tasks.Parallel.ForEach(_members, async (member) => foreach (var member in _members) { try { // create a connection with the supported format type FhirClient server = new FhirClient(member.Url); member.PrepareFhirClientSecurity(server); System.Diagnostics.Trace.WriteLine($"Searching {member.Url} {member.Name}"); server.PreferCompressedResponses = true; server.PreferredFormat = member.Format; SearchParams sp = new SearchParams(); foreach (var item in parameters) { if (item.Key == "_include") { sp.Include.Add(item.Value); } else { sp.Add(item.Key, item.Value); } } sp.Count = Count; sp.Summary = summary; // Bundle partialResult = server.Search(sp, ResourceName); Bundle partialResult = server.Search(sp, ResourceName); lock (result) { if (partialResult.Total.HasValue) { result.Total += partialResult.Total; } foreach (var entry in partialResult.Entry) { result.Entry.Add(entry); entry.Resource.ResourceBase = server.Endpoint; if (entry.Resource.Meta == null) { entry.Resource.Meta = new Meta(); } if (!string.IsNullOrEmpty(entry.Resource.Id)) { entry.Resource.Meta.AddExtension("http://hl7.org/fhir/StructureDefinition/extension-Meta.source|3.2", new FhirUri(entry.Resource.ResourceIdentity(entry.Resource.ResourceBase).OriginalString)); } var prov = member.CreateProvenance(); member.WithProvenance(prov, entry.Resource, entry.FullUrl); result.Entry.Add(new Bundle.EntryComponent() { FullUrl = $"urn-uuid{Guid.NewGuid().ToString("D")}", Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Include }, Resource = prov }); } OperationOutcome oe = new OperationOutcome(); oe.Issue.Add(new OperationOutcome.IssueComponent() { Severity = OperationOutcome.IssueSeverity.Information, Code = OperationOutcome.IssueType.Informational, Details = new CodeableConcept(null, null, $"Searching {member.Name} found {partialResult.Total} results"), Diagnostics = partialResult.SelfLink?.OriginalString }); result.Entry.Add(new Bundle.EntryComponent() { Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Outcome }, Resource = oe }); } } catch (FhirOperationException ex) { if (ex.Outcome != null) { ex.Outcome.Issue.Insert(0, new OperationOutcome.IssueComponent() { Severity = OperationOutcome.IssueSeverity.Information, Code = OperationOutcome.IssueType.Exception, Details = new CodeableConcept(null, null, $"Exception Searching {member.Name}"), Diagnostics = member.Url }); result.Entry.Add(new Bundle.EntryComponent() { Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Outcome }, Resource = ex.Outcome }); } } catch (Exception ex) { // some other weirdness went on OperationOutcome oe = new OperationOutcome(); oe.Issue.Add(new OperationOutcome.IssueComponent() { Severity = OperationOutcome.IssueSeverity.Information, Code = OperationOutcome.IssueType.Exception, Details = new CodeableConcept(null, null, $"Exception Searching {member.Name}"), Diagnostics = member.Url }); oe.Issue.Add(new OperationOutcome.IssueComponent() { Severity = OperationOutcome.IssueSeverity.Error, Code = OperationOutcome.IssueType.Exception, Diagnostics = ex.Message }); result.Entry.Add(new Bundle.EntryComponent() { Search = new Bundle.SearchComponent() { Mode = Bundle.SearchEntryMode.Outcome }, Resource = oe }); } } // TODO:Merge Sort the results? // TODO:Mess with the back/next links return(System.Threading.Tasks.Task.FromResult(result)); }