Пример #1
0
 private (bool Supported, bool IsPartiallySupported) CheckSearchParameterSupport(SearchParameterInfo parameterInfo)
 {
     try
     {
         return(_searchParameterSupportResolver.IsSearchParameterSupported(parameterInfo));
     }
     catch (Exception ex)
     {
         _logger.LogWarning("Unable to resolve search parameter {0}. Exception: {1}", parameterInfo?.Code, ex);
         return(false, false);
     }
 }
Пример #2
0
        public async Task <IReadOnlyCollection <ResourceSearchParameterStatus> > GetSearchParameterStatuses(CancellationToken cancellationToken)
        {
            // If the search parameter table in SQL does not yet contain status columns
            if (_schemaInformation.Current < SchemaVersionConstants.SearchParameterStatusSchemaVersion)
            {
                // Get status information from file.
                return(await _filebasedSearchParameterStatusDataStore.GetSearchParameterStatuses(cancellationToken));
            }

            using (IScoped <SqlConnectionWrapperFactory> scopedSqlConnectionWrapperFactory = _scopedSqlConnectionWrapperFactory())
                using (SqlConnectionWrapper sqlConnectionWrapper = await scopedSqlConnectionWrapperFactory.Value.ObtainSqlConnectionWrapperAsync(cancellationToken, true))
                    using (SqlCommandWrapper sqlCommandWrapper = sqlConnectionWrapper.CreateSqlCommand())
                    {
                        VLatest.GetSearchParamStatuses.PopulateCommand(sqlCommandWrapper);

                        var parameterStatuses = new List <ResourceSearchParameterStatus>();

                        using (SqlDataReader sqlDataReader = await sqlCommandWrapper.ExecuteReaderAsync(CommandBehavior.SequentialAccess, cancellationToken))
                        {
                            while (await sqlDataReader.ReadAsync(cancellationToken))
                            {
                                short          id;
                                string         uri;
                                string         stringStatus;
                                DateTimeOffset?lastUpdated;
                                bool?          isPartiallySupported;

                                ResourceSearchParameterStatus resourceSearchParameterStatus;

                                if (_schemaInformation.Current >= SchemaVersionConstants.SearchParameterSynchronizationVersion)
                                {
                                    (id, uri, stringStatus, lastUpdated, isPartiallySupported) = sqlDataReader.ReadRow(
                                        VLatest.SearchParam.SearchParamId,
                                        VLatest.SearchParam.Uri,
                                        VLatest.SearchParam.Status,
                                        VLatest.SearchParam.LastUpdated,
                                        VLatest.SearchParam.IsPartiallySupported);

                                    if (string.IsNullOrEmpty(stringStatus) || lastUpdated == null || isPartiallySupported == null)
                                    {
                                        // These columns are nullable because they are added to dbo.SearchParam in a later schema version.
                                        // They should be populated as soon as they are added to the table and should never be null.
                                        throw new SearchParameterNotSupportedException(Resources.SearchParameterStatusShouldNotBeNull);
                                    }

                                    var status = Enum.Parse <SearchParameterStatus>(stringStatus, true);

                                    resourceSearchParameterStatus = new SqlServerResourceSearchParameterStatus
                                    {
                                        Id     = id,
                                        Uri    = new Uri(uri),
                                        Status = status,
                                        IsPartiallySupported = (bool)isPartiallySupported,
                                        LastUpdated          = (DateTimeOffset)lastUpdated,
                                    };
                                }
                                else
                                {
                                    (uri, stringStatus, lastUpdated, isPartiallySupported) = sqlDataReader.ReadRow(
                                        VLatest.SearchParam.Uri,
                                        VLatest.SearchParam.Status,
                                        VLatest.SearchParam.LastUpdated,
                                        VLatest.SearchParam.IsPartiallySupported);

                                    if (string.IsNullOrEmpty(stringStatus) || lastUpdated == null || isPartiallySupported == null)
                                    {
                                        // These columns are nullable because they are added to dbo.SearchParam in a later schema version.
                                        // They should be populated as soon as they are added to the table and should never be null.
                                        throw new SearchParameterNotSupportedException(Resources.SearchParameterStatusShouldNotBeNull);
                                    }

                                    var status = Enum.Parse <SearchParameterStatus>(stringStatus, true);

                                    resourceSearchParameterStatus = new ResourceSearchParameterStatus
                                    {
                                        Uri    = new Uri(uri),
                                        Status = status,
                                        IsPartiallySupported = (bool)isPartiallySupported,
                                        LastUpdated          = (DateTimeOffset)lastUpdated,
                                    };
                                }

                                if (_schemaInformation.Current >= SchemaVersionConstants.AddMinMaxForDateAndStringSearchParamVersion)
                                {
                                    // For schema versions starting from AddMinMaxForDateAndStringSearchParamVersion we will check
                                    // whether the corresponding type of the search parameter is supported.
                                    SearchParameterInfo paramInfo = null;
                                    try
                                    {
                                        paramInfo = _searchParameterDefinitionManager.GetSearchParameter(resourceSearchParameterStatus.Uri.OriginalString);
                                    }
                                    catch (SearchParameterNotSupportedException)
                                    {
                                    }

                                    if (paramInfo != null && SqlServerSortingValidator.SupportedSortParamTypes.Contains(paramInfo.Type))
                                    {
                                        resourceSearchParameterStatus.SortStatus = SortParameterStatus.Enabled;
                                    }
                                    else
                                    {
                                        resourceSearchParameterStatus.SortStatus = SortParameterStatus.Disabled;
                                    }
                                }
                                else
                                {
                                    if (_sortingValidator.SupportedParameterUris.Contains(resourceSearchParameterStatus.Uri))
                                    {
                                        resourceSearchParameterStatus.SortStatus = SortParameterStatus.Enabled;
                                    }
                                    else
                                    {
                                        resourceSearchParameterStatus.SortStatus = SortParameterStatus.Disabled;
                                    }
                                }

                                parameterStatuses.Add(resourceSearchParameterStatus);
                            }
                        }

                        return(parameterStatuses);
                    }
        }
Пример #3
0
        private NormalizedSearchParameterQueryGenerator VisitSearchParameterExpressionBase(SearchParameterInfo searchParameterInfo, Expression childExpression, object context)
        {
            switch (searchParameterInfo.Name)
            {
            case SearchParameterNames.Id:
            case SearchParameterNames.LastUpdated:
            case SearchParameterNames.ResourceType:
            case SqlSearchParameters.ResourceSurrogateIdParameterName:
                // these are all denormalized
                return(null);
            }

            if (childExpression != null)
            {
                if (searchParameterInfo.Type == SearchParamType.Token)
                {
                    // could be Token or TokenText
                    return(childExpression.AcceptVisitor(this, context));
                }
            }

            if (!_cache.TryGetValue(searchParameterInfo.Url, out var generator))
            {
                generator = GetGenerator(searchParameterInfo);
                _cache.TryAdd(searchParameterInfo.Url, generator);
            }

            return(generator);

            NormalizedSearchParameterQueryGenerator GetGenerator(SearchParameterInfo param)
            {
                switch (param.Type)
                {
                case SearchParamType.Token:
                    return(TokenSearchParameterQueryGenerator.Instance);

                case SearchParamType.Date:
                    return(DateTimeSearchParameterQueryGenerator.Instance);

                case SearchParamType.Number:
                    return(NumberSearchParameterQueryGenerator.Instance);

                case SearchParamType.Quantity:
                    return(QuantitySearchParameterQueryGenerator.Instance);

                case SearchParamType.Reference:
                    return(ReferenceSearchParameterQueryGenerator.Instance);

                case SearchParamType.String:
                    return(StringSearchParameterQueryGenerator.Instance);

                case SearchParamType.Uri:
                    return(UriSearchParameterQueryGenerator.Instance);

                case SearchParamType.Composite:
                    Type searchValueType = _searchParameterToSearchValueTypeMap.GetSearchValueType(param);
                    if (searchValueType == typeof(ValueTuple <TokenSearchValue, QuantitySearchValue>))
                    {
                        return(TokenQuantityCompositeSearchParameterQueryGenerator.Instance);
                    }

                    if (searchValueType == typeof(ValueTuple <ReferenceSearchValue, TokenSearchValue>))
                    {
                        return(ReferenceTokenCompositeSearchParameterQueryGenerator.Instance);
                    }

                    if (searchValueType == typeof(ValueTuple <TokenSearchValue, TokenSearchValue>))
                    {
                        return(TokenTokenCompositeSearchParameterQueryGenerator.Instance);
                    }

                    if (searchValueType == typeof(ValueTuple <TokenSearchValue, DateTimeSearchValue>))
                    {
                        return(TokenDateTimeCompositeSearchParameterQueryGenerator.Instance);
                    }

                    if (searchValueType == typeof(ValueTuple <TokenSearchValue, StringSearchValue>))
                    {
                        return(TokenStringCompositeSearchParameterQueryGenerator.Instance);
                    }

                    if (searchValueType == typeof(ValueTuple <TokenSearchValue, NumberSearchValue, NumberSearchValue>))
                    {
                        return(TokenNumberNumberSearchParameterQueryGenerator.Instance);
                    }

                    throw new InvalidOperationException($"Unexpected composite search parameter {param.Url}");

                default:
                    throw new InvalidOperationException($"Unexpected search parameter type {param.Type}");
                }
            }
        }
        public SearchParameterStatusManagerTests()
        {
            _searchParameterRegistry          = Substitute.For <ISearchParameterRegistry>();
            _searchParameterDefinitionManager = Substitute.For <ISearchParameterDefinitionManager>();
            _searchParameterSupportResolver   = Substitute.For <ISearchParameterSupportResolver>();
            _mediator = Substitute.For <IMediator>();

            _manager = new SearchParameterStatusManager(
                _searchParameterRegistry,
                _searchParameterDefinitionManager,
                _searchParameterSupportResolver,
                _mediator);

            _resourceSearchParameterStatuses = new[]
            {
                new ResourceSearchParameterStatus
                {
                    Status      = SearchParameterStatus.Enabled,
                    Uri         = new Uri(ResourceId),
                    LastUpdated = Clock.UtcNow,
                },
                new ResourceSearchParameterStatus
                {
                    Status = SearchParameterStatus.Enabled,
                    Uri    = new Uri(ResourceLastupdated),
                    IsPartiallySupported = true,
                    LastUpdated          = Clock.UtcNow,
                },
                new ResourceSearchParameterStatus
                {
                    Status      = SearchParameterStatus.Disabled,
                    Uri         = new Uri(ResourceProfile),
                    LastUpdated = Clock.UtcNow,
                },
                new ResourceSearchParameterStatus
                {
                    Status      = SearchParameterStatus.Supported,
                    Uri         = new Uri(ResourceSecurity),
                    LastUpdated = Clock.UtcNow,
                },
            };

            _searchParameterRegistry.GetSearchParameterStatuses().Returns(_resourceSearchParameterStatuses);

            List <string> baseResourceTypes = new List <string>()
            {
                "Resource"
            };
            List <string> targetResourceTypes = new List <string>()
            {
                "Patient"
            };

            _queryParameter       = new SearchParameterInfo("_query", SearchParamType.Token, new Uri(ResourceQuery));
            _searchParameterInfos = new[]
            {
                new SearchParameterInfo("_id", SearchParamType.Token, new Uri(ResourceId), targetResourceTypes: targetResourceTypes, baseResourceTypes: baseResourceTypes),
                new SearchParameterInfo("_lastUpdated", SearchParamType.Token, new Uri(ResourceLastupdated), targetResourceTypes: targetResourceTypes, baseResourceTypes: baseResourceTypes),
                new SearchParameterInfo("_profile", SearchParamType.Token, new Uri(ResourceProfile), targetResourceTypes: targetResourceTypes),
                new SearchParameterInfo("_security", SearchParamType.Token, new Uri(ResourceSecurity), targetResourceTypes: targetResourceTypes),
                _queryParameter,
            };

            _searchParameterDefinitionManager.GetSearchParameters("Account")
            .Returns(_searchParameterInfos);

            _searchParameterDefinitionManager.AllSearchParameters
            .Returns(_searchParameterInfos);

            _searchParameterDefinitionManager.GetSearchParameter(new Uri(ResourceQuery))
            .Returns(_queryParameter);

            _searchParameterSupportResolver
            .IsSearchParameterSupported(Arg.Any <SearchParameterInfo>())
            .Returns((false, false));

            _searchParameterSupportResolver
            .IsSearchParameterSupported(Arg.Is(_searchParameterInfos[4]))
            .Returns((true, false));
        }
        public (bool Supported, bool IsPartiallySupported) IsSearchParameterSupported(SearchParameterInfo parameterInfo)
        {
            EnsureArg.IsNotNull(parameterInfo, nameof(parameterInfo));

            if (string.IsNullOrWhiteSpace(parameterInfo.Expression))
            {
                return(false, false);
            }

            Expression parsed = _compiler.Parse(parameterInfo.Expression);

            if (parameterInfo.Component != null && parameterInfo.Component.Any(x => x.ResolvedSearchParameter == null))
            {
                return(false, false);
            }

            (SearchParamType Type, Expression, Uri DefinitionUrl)[] componentExpressions = parameterInfo.Component
 protected SearchParameterExpressionBase(SearchParameterInfo searchParameter)
 {
     Parameter = searchParameter;
     EnsureArg.IsNotNull(searchParameter, nameof(searchParameter));
 }
Пример #7
0
        private Expression ParseChainedExpression(string resourceType, SearchParameterInfo searchParameter, string targetResourceType, ReadOnlySpan <char> remainingKey, string value, bool reversed)
        {
            // We have more paths after this so this is a chained expression.
            // Since this is chained expression, the expression must be a reference type.
            if (searchParameter.Type != ValueSets.SearchParamType.Reference)
            {
                // The search parameter is not a reference type, which is not allowed.
                throw new InvalidSearchOperationException(Core.Resources.ChainedParameterMustBeReferenceSearchParamType);
            }

            // Check to see if the client has specifically specified the target resource type to scope to.
            if (!string.IsNullOrEmpty(targetResourceType))
            {
                // A target resource type is specified.
                if (!ModelInfoProvider.IsKnownResource(targetResourceType))
                {
                    throw new InvalidSearchOperationException(string.Format(Core.Resources.ResourceNotSupported, targetResourceType));
                }
            }

            ChainedExpression chainedExpression = null;

            foreach (var possibleTargetResourceType in searchParameter.TargetResourceTypes)
            {
                if (!string.IsNullOrEmpty(targetResourceType) && targetResourceType != possibleTargetResourceType)
                {
                    continue;
                }

                var multipleChainType = reversed ? resourceType : possibleTargetResourceType;

                ChainedExpression expression;
                try
                {
                    expression = Expression.Chained(
                        resourceType,
                        searchParameter,
                        possibleTargetResourceType,
                        reversed,
                        ParseImpl(
                            multipleChainType,
                            remainingKey,
                            value));
                }
                catch (Exception ex) when(ex is ResourceNotSupportedException || ex is SearchParameterNotSupportedException)
                {
                    // The resource or search parameter is not supported for the resource.
                    // We will ignore these unsupported types.
                    continue;
                }

                if (chainedExpression == null)
                {
                    chainedExpression = expression;
                }
                else
                {
                    // If the target resource type is ambiguous, we throw an error.
                    // At the moment, this is not supported

                    throw new InvalidSearchOperationException(
                              string.Format(
                                  CultureInfo.CurrentCulture,
                                  Core.Resources.ChainedParameterSpecifyType,
                                  searchParameter.Name,
                                  string.Join(Core.Resources.OrDelimiter, searchParameter.TargetResourceTypes.Select(c => $"{searchParameter.Name}:{c}"))));
                }
            }

            if (chainedExpression == null)
            {
                // There was no reference that supports the search parameter.
                throw new InvalidSearchOperationException(Core.Resources.ChainedParameterNotSupported);
            }

            return(chainedExpression);
        }
        public Expression Parse(
            SearchParameterInfo searchParameter,
            SearchModifierCode?modifier,
            string value)
        {
            EnsureArg.IsNotNull(searchParameter, nameof(searchParameter));

            Debug.Assert(
                modifier == null || Enum.IsDefined(typeof(SearchModifierCode), modifier.Value),
                "Invalid modifier.");
            EnsureArg.IsNotNullOrWhiteSpace(value, nameof(value));

            Expression outputExpression;

            if (modifier == SearchModifierCode.Missing)
            {
                // We have to handle :missing modifier specially because if :missing modifier is specified,
                // then the value is a boolean string indicating whether the parameter is missing or not instead of
                // the search value type associated with the search parameter.
                if (!bool.TryParse(value, out bool isMissing))
                {
                    // An invalid value was specified.
                    throw new InvalidSearchOperationException(Core.Resources.InvalidValueTypeForMissingModifier);
                }

                return(Expression.MissingSearchParameter(searchParameter, isMissing));
            }

            if (modifier == SearchModifierCode.Text)
            {
                // We have to handle :text modifier specially because if :text modifier is supplied for token search param,
                // then we want to search the display text using the specified text, and therefore
                // we don't want to actually parse the specified text into token.
                if (searchParameter.Type != ValueSets.SearchParamType.Token)
                {
                    throw new InvalidSearchOperationException(
                              string.Format(CultureInfo.InvariantCulture, Core.Resources.ModifierNotSupported, modifier, searchParameter.Name));
                }

                outputExpression = Expression.StartsWith(FieldName.TokenText, null, value, true);
            }
            else
            {
                // Build the expression for based on the search value.
                if (searchParameter.Type == ValueSets.SearchParamType.Composite)
                {
                    if (modifier != null)
                    {
                        throw new InvalidSearchOperationException(
                                  string.Format(CultureInfo.InvariantCulture, Core.Resources.ModifierNotSupported, modifier, searchParameter.Name));
                    }

                    IReadOnlyList <string> compositeValueParts = value.SplitByCompositeSeparator();

                    if (compositeValueParts.Count > searchParameter.Component.Count)
                    {
                        throw new InvalidSearchOperationException(
                                  string.Format(CultureInfo.InvariantCulture, Core.Resources.NumberOfCompositeComponentsExceeded, searchParameter.Name));
                    }

                    var compositeExpressions = new Expression[compositeValueParts.Count];

                    var searchParameterComponentInfos = searchParameter.Component.ToList();

                    for (int i = 0; i < compositeValueParts.Count; i++)
                    {
                        var component = searchParameterComponentInfos[i];

                        // Find the corresponding search parameter info.
                        SearchParameterInfo componentSearchParameter = _searchParameterDefinitionManager.GetSearchParameter(component.DefinitionUrl);

                        string componentValue = compositeValueParts[i];

                        compositeExpressions[i] = Build(
                            componentSearchParameter,
                            modifier: null,
                            componentIndex: i,
                            value: componentValue);
                    }

                    outputExpression = Expression.And(compositeExpressions);
                }
                else
                {
                    outputExpression = Build(
                        searchParameter,
                        modifier,
                        componentIndex: null,
                        value: value);
                }
            }

            return(Expression.SearchParameter(searchParameter, outputExpression));
        }
 public bool TryGetSearchParameter(Uri definitionUri, out SearchParameterInfo value)
 {
     return(UrlLookup.TryGetValue(definitionUri, out value));
 }
        public void GivenACompositeExceedingNumberOfComponents_WhenBuilt_ThenInvalidSearchOperationExceptionShouldBeThrown()
        {
            SearchParameterInfo searchParameter = CreateCompositeSearchParameter(new SearchParameterComponentInfo());

            Assert.Throws <InvalidSearchOperationException>(() => _parser.Parse(searchParameter, null, "a$b$c"));
        }
        private Expression Build(
            SearchParameterInfo searchParameter,
            SearchModifierCode?modifier,
            int?componentIndex,
            string value)
        {
            ReadOnlySpan <char> valueSpan = value.AsSpan();

            // By default, the comparator is equal.
            SearchComparator comparator = SearchComparator.Eq;

            if (searchParameter.Type == ValueSets.SearchParamType.Date ||
                searchParameter.Type == ValueSets.SearchParamType.Number ||
                searchParameter.Type == ValueSets.SearchParamType.Quantity)
            {
                // If the search parameter type supports comparator, parse the comparator (if present).
                Tuple <string, SearchComparator> matchedComparator = SearchParamComparators.FirstOrDefault(
                    s => value.StartsWith(s.Item1, StringComparison.Ordinal));

                if (matchedComparator != null)
                {
                    comparator = matchedComparator.Item2;
                    valueSpan  = valueSpan.Slice(matchedComparator.Item1.Length);
                }
            }

            // Parse the value.
            Func <string, ISearchValue> parser = _parserDictionary[Enum.Parse <SearchParamType>(searchParameter.Type.ToString())];

            // Build the expression.
            var helper = new SearchValueExpressionBuilderHelper();

            // If the value contains comma, then we need to convert it into in expression.
            // But in this case, the user cannot specify prefix.
            IReadOnlyList <string> parts = value.SplitByOrSeparator();

            if (parts.Count == 1)
            {
                // This is a single value expression.
                ISearchValue searchValue = parser(valueSpan.ToString());

                return(helper.Build(
                           searchParameter.Name,
                           modifier,
                           comparator,
                           componentIndex,
                           searchValue));
            }
            else
            {
                if (comparator != SearchComparator.Eq)
                {
                    throw new InvalidSearchOperationException(Core.Resources.SearchComparatorNotSupported);
                }

                // This is a multiple value expression.
                Expression[] expressions = parts.Select(part =>
                {
                    ISearchValue searchValue = parser(part);

                    return(helper.Build(
                               searchParameter.Name,
                               modifier,
                               comparator,
                               componentIndex,
                               searchValue));
                }).ToArray();

                return(Expression.Or(expressions));
            }
        }
Пример #12
0
        private static List <(string ResourceType, SearchParameterInfo SearchParameter)> ValidateAndGetFlattenedList(
            IReadOnlyCollection <ITypedElement> searchParamCollection,
            IDictionary <Uri, SearchParameterInfo> uriDictionary,
            IModelInfoProvider modelInfoProvider)
        {
            var issues           = new List <OperationOutcomeIssue>();
            var searchParameters = searchParamCollection.Select((x, entryIndex) =>
            {
                try
                {
                    return(new SearchParameterWrapper(x));
                }
                catch (ArgumentException)
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionInvalidResource, entryIndex);
                    return(null);
                }
            }).ToList();

            // Do the first pass to make sure all resources are SearchParameter.
            for (int entryIndex = 0; entryIndex < searchParameters.Count; entryIndex++)
            {
                SearchParameterWrapper searchParameter = searchParameters[entryIndex];

                if (searchParameter == null)
                {
                    continue;
                }

                try
                {
                    SearchParameterInfo searchParameterInfo = GetOrCreateSearchParameterInfo(searchParameter, uriDictionary);
                    uriDictionary.Add(new Uri(searchParameter.Url), searchParameterInfo);
                }
                catch (FormatException)
                {
                    AddIssue(Resources.SearchParameterDefinitionInvalidDefinitionUri, entryIndex);
                    continue;
                }
                catch (ArgumentException)
                {
                    AddIssue(Resources.SearchParameterDefinitionDuplicatedEntry, searchParameter.Url);
                    continue;
                }
            }

            EnsureNoIssues();

            var validatedSearchParameters = new List <(string ResourceType, SearchParameterInfo SearchParameter)>
            {
                // _type is currently missing from the search params definition bundle, so we inject it in here.
                (KnownResourceTypes.Resource, new SearchParameterInfo(SearchParameterNames.ResourceType, SearchParameterNames.ResourceType, SearchParamType.Token, SearchParameterNames.ResourceTypeUri, null, "Resource.type().name", null)),
            };

            // Do the second pass to make sure the definition is valid.
            foreach (var searchParameter in searchParameters)
            {
                if (searchParameter == null)
                {
                    continue;
                }

                // If this is a composite search parameter, then make sure components are defined.
                if (string.Equals(searchParameter.Type, SearchParamType.Composite.GetLiteral(), StringComparison.OrdinalIgnoreCase))
                {
                    if (modelInfoProvider.Version == FhirSpecification.R5 && _knownBrokenR5.Contains(new Uri(searchParameter.Url)))
                    {
                        continue;
                    }

                    var composites = searchParameter.Component;
                    if (composites.Count == 0)
                    {
                        AddIssue(Core.Resources.SearchParameterDefinitionInvalidComponent, searchParameter.Url);
                        continue;
                    }

                    SearchParameterInfo compositeSearchParameter = GetOrCreateSearchParameterInfo(searchParameter, uriDictionary);

                    for (int componentIndex = 0; componentIndex < composites.Count; componentIndex++)
                    {
                        ITypedElement component     = composites[componentIndex];
                        var           definitionUrl = GetComponentDefinition(component);

                        if (definitionUrl == null ||
                            !uriDictionary.TryGetValue(new Uri(definitionUrl), out SearchParameterInfo componentSearchParameter))
                        {
                            AddIssue(
                                Core.Resources.SearchParameterDefinitionInvalidComponentReference,
                                searchParameter.Url,
                                componentIndex);
                            continue;
                        }

                        if (componentSearchParameter.Type == SearchParamType.Composite)
                        {
                            AddIssue(
                                Core.Resources.SearchParameterDefinitionComponentReferenceCannotBeComposite,
                                searchParameter.Url,
                                componentIndex);
                            continue;
                        }

                        if (string.IsNullOrWhiteSpace(component.Scalar("expression")?.ToString()))
                        {
                            AddIssue(
                                Core.Resources.SearchParameterDefinitionInvalidComponentExpression,
                                searchParameter.Url,
                                componentIndex);
                            continue;
                        }

                        compositeSearchParameter.Component[componentIndex].ResolvedSearchParameter = componentSearchParameter;
                    }
                }

                // Make sure the base is defined.
                IReadOnlyList <string> bases = searchParameter.Base;
                if (bases.Count == 0)
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionBaseNotDefined, searchParameter.Url);
                    continue;
                }

                for (int baseElementIndex = 0; baseElementIndex < bases.Count; baseElementIndex++)
                {
                    var code = bases[baseElementIndex];

                    string baseResourceType = code;

                    // Make sure the expression is not empty unless they are known to have empty expression.
                    // These are special search parameters that searches across all properties and needs to be handled specially.
                    if (ShouldExcludeEntry(baseResourceType, searchParameter.Name, modelInfoProvider) ||
                        (modelInfoProvider.Version == FhirSpecification.R5 && _knownBrokenR5.Contains(new Uri(searchParameter.Url))))
                    {
                        continue;
                    }
                    else
                    {
                        if (string.IsNullOrWhiteSpace(searchParameter.Expression))
                        {
                            AddIssue(Core.Resources.SearchParameterDefinitionInvalidExpression, searchParameter.Url);
                            continue;
                        }
                    }

                    validatedSearchParameters.Add((baseResourceType, GetOrCreateSearchParameterInfo(searchParameter, uriDictionary)));
                }
            }

            EnsureNoIssues();

            return(validatedSearchParameters);

            void AddIssue(string format, params object[] args)
            {
                issues.Add(new OperationOutcomeIssue(
                               OperationOutcomeConstants.IssueSeverity.Fatal,
                               OperationOutcomeConstants.IssueType.Invalid,
                               string.Format(CultureInfo.InvariantCulture, format, args)));
            }

            void EnsureNoIssues()
            {
                if (issues.Count != 0)
                {
                    throw new InvalidDefinitionException(
                              Core.Resources.SearchParameterDefinitionContainsInvalidEntry,
                              issues.ToArray());
                }
            }
        }
Пример #13
0
 private bool UsePartialSearchParams(SearchParameterInfo parameter)
 {
     return(_fhirReqeustContextAccessor.RequestContext != null &&
            _fhirReqeustContextAccessor.RequestContext.IncludePartiallyIndexedSearchParams &&
            parameter.IsSupported);
 }
 public SearchParamTableExpressionQueryGenerator GetSearchParamTableExpressionQueryGenerator(SearchParameterInfo searchParameterInfo)
 {
     return(VisitSearchParameterExpressionBase(searchParameterInfo, null, null));
 }
Пример #15
0
        private List <(string ResourceType, SearchParameterInfo SearchParameter)> ValidateAndGetFlattenedList()
        {
            var issues = new List <OperationOutcomeIssue>();

            BundleWrapper bundle = null;

            using (Stream stream = _modelInfoProvider.OpenVersionedFileStream(_embeddedResourceName, _embeddedResourceNamespace, _assembly))
            {
                using TextReader reader     = new StreamReader(stream);
                using JsonReader jsonReader = new JsonTextReader(reader);
                try
                {
                    bundle = new BundleWrapper(FhirJsonNode.Read(jsonReader).ToTypedElement(_modelInfoProvider.StructureDefinitionSummaryProvider));
                }
                catch (FormatException ex)
                {
                    AddIssue(ex.Message);
                }
            }

            EnsureNoIssues();

            IReadOnlyList <BundleEntryWrapper> entries = bundle.Entries;

            // Do the first pass to make sure all resources are SearchParameter.
            for (int entryIndex = 0; entryIndex < entries.Count; entryIndex++)
            {
                // Make sure resources are not null and they are SearchParameter.
                BundleEntryWrapper entry = entries[entryIndex];

                ITypedElement searchParameterElement = entry.Resource;

                if (searchParameterElement == null || !string.Equals(searchParameterElement.InstanceType, KnownResourceTypes.SearchParameter, StringComparison.OrdinalIgnoreCase))
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionInvalidResource, entryIndex);
                    continue;
                }

                var searchParameter = new SearchParameterWrapper(searchParameterElement);

                try
                {
                    SearchParameterInfo searchParameterInfo = CreateSearchParameterInfo(searchParameter);
                    _uriDictionary.Add(new Uri(searchParameter.Url), searchParameterInfo);
                }
                catch (FormatException)
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionInvalidDefinitionUri, entryIndex);
                    continue;
                }
                catch (ArgumentException)
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionDuplicatedEntry, searchParameter.Url);
                    continue;
                }
            }

            EnsureNoIssues();

            var validatedSearchParameters = new List <(string ResourceType, SearchParameterInfo SearchParameter)>
            {
                // _type is currently missing from the search params definition bundle, so we inject it in here.
                (KnownResourceTypes.Resource, new SearchParameterInfo(SearchParameterNames.ResourceType, SearchParamType.Token, SearchParameterNames.ResourceTypeUri, null, "Resource.type().name", null)),
            };

            // Do the second pass to make sure the definition is valid.
            for (int entryIndex = 0; entryIndex < entries.Count; entryIndex++)
            {
                BundleEntryWrapper entry = entries[entryIndex];

                ITypedElement searchParameterElement = entry.Resource;
                var           searchParameter        = new SearchParameterWrapper(searchParameterElement);

                // If this is a composite search parameter, then make sure components are defined.
                if (string.Equals(searchParameter.Type, SearchParamType.Composite.GetLiteral(), StringComparison.OrdinalIgnoreCase))
                {
                    var composites = searchParameter.Component;
                    if (composites.Count == 0)
                    {
                        AddIssue(Core.Resources.SearchParameterDefinitionInvalidComponent, searchParameter.Url);
                        continue;
                    }

                    for (int componentIndex = 0; componentIndex < composites.Count; componentIndex++)
                    {
                        ITypedElement component     = composites[componentIndex];
                        var           definitionUrl = GetComponentDefinition(component);

                        if (definitionUrl == null ||
                            !_uriDictionary.TryGetValue(new Uri(definitionUrl), out SearchParameterInfo componentSearchParameter))
                        {
                            AddIssue(
                                Core.Resources.SearchParameterDefinitionInvalidComponentReference,
                                searchParameter.Url,
                                componentIndex);
                            continue;
                        }

                        if (componentSearchParameter.Type == SearchParamType.Composite)
                        {
                            AddIssue(
                                Core.Resources.SearchParameterDefinitionComponentReferenceCannotBeComposite,
                                searchParameter.Url,
                                componentIndex);
                            continue;
                        }

                        if (string.IsNullOrWhiteSpace(component.Scalar("expression")?.ToString()))
                        {
                            AddIssue(
                                Core.Resources.SearchParameterDefinitionInvalidComponentExpression,
                                searchParameter.Url,
                                componentIndex);
                            continue;
                        }
                    }
                }

                // Make sure the base is defined.
                var bases = searchParameter.Base;
                if (bases.Count == 0)
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionBaseNotDefined, searchParameter.Url);
                    continue;
                }

                for (int baseElementIndex = 0; baseElementIndex < bases.Count; baseElementIndex++)
                {
                    var code = bases[baseElementIndex];

                    string baseResourceType = code;

                    // Make sure the expression is not empty unless they are known to have empty expression.
                    // These are special search parameters that searches across all properties and needs to be handled specially.
                    if (ShouldExcludeEntry(baseResourceType, searchParameter.Name) ||
                        (_modelInfoProvider.Version == FhirSpecification.R5 && _knownBrokenR5.Contains(new Uri(searchParameter.Url))))
                    {
                        continue;
                    }
                    else
                    {
                        if (string.IsNullOrWhiteSpace(searchParameter.Expression))
                        {
                            AddIssue(Core.Resources.SearchParameterDefinitionInvalidExpression, searchParameter.Url);
                            continue;
                        }
                    }

                    validatedSearchParameters.Add((baseResourceType, CreateSearchParameterInfo(searchParameter)));
                }
            }

            EnsureNoIssues();

            return(validatedSearchParameters);

            void AddIssue(string format, params object[] args)
            {
                issues.Add(new OperationOutcomeIssue(
                               OperationOutcomeConstants.IssueSeverity.Fatal,
                               OperationOutcomeConstants.IssueType.Invalid,
                               string.Format(CultureInfo.InvariantCulture, format, args)));
            }

            void EnsureNoIssues()
            {
                if (issues.Count != 0)
                {
                    throw new InvalidDefinitionException(
                              Core.Resources.SearchParameterDefinitionContainsInvalidEntry,
                              issues.ToArray());
                }
            }
        }
 public SearchParamType GetSearchParameterType(SearchParameterInfo searchParameter, int?componentIndex)
 {
     return(_inner.GetSearchParameterType(searchParameter, componentIndex));
 }
Пример #17
0
        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
                {
                    // 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);
                    }
                }
            }

            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 (!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 ?? (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);
        }
Пример #18
0
        private Expression ParseImpl(string[] resourceTypes, ReadOnlySpan <char> key, string value)
        {
            if (TryConsume(ReverseChainParameter.AsSpan(), ref key))
            {
                if (!TrySplit(SearchSplitChar, ref key, out ReadOnlySpan <char> type))
                {
                    throw new InvalidSearchOperationException(Core.Resources.ReverseChainMissingType);
                }

                if (!TrySplit(SearchSplitChar, ref key, out ReadOnlySpan <char> refParam))
                {
                    throw new InvalidSearchOperationException(Core.Resources.ReverseChainMissingReference);
                }

                string typeString = type.ToString();
                SearchParameterInfo refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter(typeString, refParam.ToString());

                return(ParseChainedExpression(new[] { typeString }, refSearchParameter, resourceTypes, key, value, true));
            }

            if (TrySplit(ChainParameter, ref key, out ReadOnlySpan <char> chainedInput))
            {
                string[] targetType = Array.Empty <string>();

                if (TrySplit(SearchSplitChar, ref chainedInput, out ReadOnlySpan <char> refParamName))
                {
                    targetType = new[] { chainedInput.ToString() };
                }
                else
                {
                    refParamName = chainedInput;
                }

                if (refParamName.IsEmpty)
                {
                    throw new SearchParameterNotSupportedException(resourceTypes[0], key.ToString());
                }

                SearchParameterInfo refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter(resourceTypes[0], refParamName.ToString());
                foreach (var resourceType in resourceTypes)
                {
                    if (refSearchParameter != _searchParameterDefinitionManager.GetSearchParameter(resourceType, refParamName.ToString()))
                    {
                        throw new BadRequestException(string.Format(Core.Resources.SearchParameterMustBeCommon, refParamName.ToString(), resourceTypes[0], resourceType));
                    }
                }

                return(ParseChainedExpression(resourceTypes, refSearchParameter, targetType, key, value, false));
            }

            ReadOnlySpan <char> modifier;

            if (TrySplit(SearchSplitChar, ref key, out ReadOnlySpan <char> paramName))
            {
                modifier = key;
            }
            else
            {
                paramName = key;
                modifier  = ReadOnlySpan <char> .Empty;
            }

            // Check to see if the search parameter is supported for this type or not.
            SearchParameterInfo searchParameter = _searchParameterDefinitionManager.GetSearchParameter(resourceTypes[0], paramName.ToString());

            foreach (var resourceType in resourceTypes)
            {
                if (searchParameter != _searchParameterDefinitionManager.GetSearchParameter(resourceType, paramName.ToString()))
                {
                    throw new BadRequestException(string.Format(Core.Resources.SearchParameterMustBeCommon, paramName.ToString(), resourceTypes[0], resourceType));
                }
            }

            return(ParseSearchValueExpression(searchParameter, modifier.ToString(), value));
        }
Пример #19
0
        public async Task GivenNewSearchParam_WhenReindexJobCompleted_ThenParamIsSearchable()
        {
            var searchParamName = "foo";
            var searchParam     = new SearchParameterInfo(
                name: searchParamName,
                searchParamType: ValueSets.SearchParamType.String,
                url: new Uri("http://hl7.org/fhir/SearchParameter/Patient-foo"),
                components: null,
                expression: "Patient.name",
                targetResourceTypes: null,
                baseResourceTypes: new List <string>()
            {
                "Patient"
            })
            {
                IsSupported  = true,
                IsSearchable = false,
            };

            _searchParameterDefinitionManager.UrlLookup.Add(searchParam.Url, searchParam);
            _searchParameterDefinitionManager.TypeLookup["Patient"].Add(searchParamName, searchParam);

            await UpsertPatientData("searchIndicesPatient1");
            await UpsertPatientData("searchIndicesPatient2");

            var queryParams = new List <Tuple <string, string> >()
            {
                new Tuple <string, string>("foo", "searchIndicesPatient1")
            };
            var searchResults = await _searchService.Value.SearchAsync("Patient", queryParams, CancellationToken.None);

            Assert.Equal(searchParamName, searchResults.UnsupportedSearchParameters.FirstOrDefault().Item1);
            Assert.Equal(2, searchResults.Results.Count());

            var searchIndexValues1 = new List <SearchIndexEntry>();

            searchIndexValues1.Add(new SearchIndexEntry(searchParam, new StringSearchValue("searchIndicesPatient1")));
            _searchIndexer.Extract(Arg.Is <ResourceElement>(r => r.Id.Equals("searchIndicesPatient1"))).Returns(searchIndexValues1);

            var searchIndexValues2 = new List <SearchIndexEntry>();

            searchIndexValues2.Add(new SearchIndexEntry(searchParam, new StringSearchValue("searchIndicesPatient2")));
            _searchIndexer.Extract(Arg.Is <ResourceElement>(r => r.Id.Equals("searchIndicesPatient2"))).Returns(searchIndexValues2);

            var request = new CreateReindexRequest();

            CreateReindexResponse response = await _createReindexRequestHandler.Handle(request, CancellationToken.None);

            Assert.NotNull(response);
            Assert.False(string.IsNullOrWhiteSpace(response.Job.JobRecord.Id));

            _reindexJobWorker = new ReindexJobWorker(
                () => _scopedOperationDataStore,
                Options.Create(_jobConfiguration),
                InitializeReindexJobTask,
                NullLogger <ReindexJobWorker> .Instance);

            var cancellationTokenSource = new CancellationTokenSource();

            try
            {
                var reindexWorkerTask = _reindexJobWorker.ExecuteAsync(cancellationTokenSource.Token);
                var reindexJobWrapper = await _fhirOperationDataStore.GetReindexJobByIdAsync(response.Job.JobRecord.Id, cancellationTokenSource.Token);

                int delayCount = 0;
                while (reindexJobWrapper.JobRecord.Status != OperationStatus.Completed &&
                       delayCount < 10)
                {
                    await Task.Delay(1000);

                    delayCount++;
                    reindexJobWrapper = await _fhirOperationDataStore.GetReindexJobByIdAsync(response.Job.JobRecord.Id, cancellationTokenSource.Token);
                }

                Assert.True(delayCount <= 9);

                searchResults = await _searchService.Value.SearchAsync("Patient", queryParams, CancellationToken.None);

                Assert.Single(searchResults.Results);

                var patient = searchResults.Results.FirstOrDefault().Resource;
                Assert.Contains("searchIndicesPatient1", patient.RawResource.Data);
            }
            finally
            {
                cancellationTokenSource.Cancel();
            }
        }
Пример #20
0
 /// <summary>
 /// Creates a <see cref="SearchParameterExpression"/> that represents a set of ANDed expressions over a search parameter.
 /// </summary>
 /// <param name="searchParameter">The search parameter this expression is bound to.</param>
 /// <param name="expression">The expression over the parameter's values.</param>
 /// <returns>A <see cref="SearchParameterExpression"/>.</returns>
 public static SearchParameterExpression SearchParameter(SearchParameterInfo searchParameter, Expression expression)
 {
     return(new SearchParameterExpression(searchParameter, expression));
 }
Пример #21
0
        private Expression ParseImpl(string resourceType, ReadOnlySpan <char> key, string value)
        {
            if (TryConsume(ReverseChainParameter.AsSpan(), ref key))
            {
                if (!TrySplit(ChainSplitChar, ref key, out ReadOnlySpan <char> type))
                {
                    throw new InvalidSearchOperationException(Core.Resources.ReverseChainMissingType);
                }

                if (!TrySplit(ChainSplitChar, ref key, out ReadOnlySpan <char> refParam))
                {
                    throw new InvalidSearchOperationException(Core.Resources.ReverseChainMissingReference);
                }

                string typeString = type.ToString();
                SearchParameterInfo refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter(typeString, refParam.ToString());

                return(ParseChainedExpression(typeString, refSearchParameter, resourceType, key, value, true));
            }

            if (TrySplit(ChainParameter, ref key, out ReadOnlySpan <char> chainedInput))
            {
                ReadOnlySpan <char> targetTypeName;

                if (TrySplit(ChainSplitChar, ref chainedInput, out ReadOnlySpan <char> refParamName))
                {
                    targetTypeName = chainedInput;
                }
                else
                {
                    refParamName   = chainedInput;
                    targetTypeName = ReadOnlySpan <char> .Empty;
                }

                if (refParamName.IsEmpty)
                {
                    throw new SearchParameterNotSupportedException(resourceType, key.ToString());
                }

                SearchParameterInfo refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter(resourceType, refParamName.ToString());

                return(ParseChainedExpression(resourceType, refSearchParameter, targetTypeName.ToString(), key, value, false));
            }

            ReadOnlySpan <char> modifier;

            if (TrySplit(ChainSplitChar, ref key, out ReadOnlySpan <char> paramName))
            {
                modifier = key;
            }
            else
            {
                paramName = key;
                modifier  = ReadOnlySpan <char> .Empty;
            }

            // Check to see if the search parameter is supported for this type or not.
            SearchParameterInfo searchParameter = _searchParameterDefinitionManager.GetSearchParameter(resourceType, paramName.ToString());

            return(ParseSearchValueExpression(searchParameter, modifier.ToString(), value));
        }
Пример #22
0
 /// <summary>
 /// Creates a <see cref="MissingSearchParameterExpression"/> that represents a search parameter being present or not in a resource.
 /// </summary>
 /// <param name="searchParameter">The search parameter this expression is bound to.</param>
 /// <param name="isMissing">A flag indicating whether the parameter should be missing or not.</param>
 /// <returns>A <see cref="SearchParameterExpression"/>.</returns>
 public static MissingSearchParameterExpression MissingSearchParameter(SearchParameterInfo searchParameter, bool isMissing)
 {
     return(new MissingSearchParameterExpression(searchParameter, isMissing));
 }
Пример #23
0
        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.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)
            {
                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)>();
                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);

                        if (searchParameterInfo.IsSortSupported())
                        {
                            sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder()));
                        }
                        else
                        {
                            throw new SearchParameterNotSupportedException(string.Format(Core.Resources.SearchSortParameterNotSupported, searchParameterInfo.Name));
                        }
                    }
                    catch (SearchParameterNotSupportedException)
                    {
                        (unsupportedSortings ??= new List <(string parameterName, string reason)>()).Add((sorting.Item1, string.Format(Core.Resources.SearchSortParameterNotSupported, sorting.Item1)));
                    }
                }

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

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

                    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));
                }
            }
        }
Пример #24
0
 /// <summary>
 /// Creates a <see cref="ChainedExpression"/> that represents chained operation.
 /// </summary>
 /// <param name="resourceType">The resource type.</param>
 /// <param name="referenceSearchParameter">The search parameter that establishes the reference between resources</param>
 /// <param name="targetResourceType">The target resource type.</param>
 /// <param name="reversed">If this is a reversed chained expression.</param>
 /// <param name="expression">The expression.</param>
 /// <returns>A <see cref="ChainedExpression"/> that represents chained operation on <paramref name="targetResourceType"/> through <paramref name="referenceSearchParameter"/>.</returns>
 public static ChainedExpression Chained(string resourceType, SearchParameterInfo referenceSearchParameter, string targetResourceType, bool reversed, Expression expression)
 {
     return(new ChainedExpression(resourceType, referenceSearchParameter, targetResourceType, reversed, expression));
 }
Пример #25
0
        public SearchParameterDefinitionManagerTests()
        {
            _searchParameterSupportResolver = Substitute.For <ISearchParameterSupportResolver>();
            _mediator = Substitute.For <IMediator>();
            _searchParameterRegistry          = Substitute.For <ISearchParameterRegistry>();
            _searchParameterDefinitionManager = new SearchParameterDefinitionManager(ModelInfoProvider.Instance);
            _fhirRequestContextAccessor       = Substitute.For <IFhirRequestContextAccessor>();
            _fhirRequestContextAccessor.FhirRequestContext.Returns(_fhirRequestContext);

            _manager = new SearchParameterStatusManager(
                _searchParameterRegistry,
                _searchParameterDefinitionManager,
                _searchParameterSupportResolver,
                _mediator);

            _searchParameterRegistry.GetSearchParameterStatuses()
            .Returns(new[]
            {
                new ResourceSearchParameterStatus
                {
                    Status = SearchParameterStatus.Enabled,
                    Uri    = new Uri(ResourceId),
                },
                new ResourceSearchParameterStatus
                {
                    Status = SearchParameterStatus.Enabled,
                    Uri    = new Uri(ResourceLastUpdated),
                    IsPartiallySupported = true,
                },
                new ResourceSearchParameterStatus
                {
                    Status = SearchParameterStatus.Disabled,
                    Uri    = new Uri(ResourceProfile),
                },
                new ResourceSearchParameterStatus
                {
                    Status = SearchParameterStatus.Supported,
                    Uri    = new Uri(ResourceSecurity),
                },
            });

            _queryParameter       = new SearchParameterInfo("_query", SearchParamType.Token, new Uri(ResourceQuery));
            _searchParameterInfos = new[]
            {
                new SearchParameterInfo("_id", SearchParamType.Token, new Uri(ResourceId)),
                new SearchParameterInfo("_lastUpdated", SearchParamType.Token, new Uri(ResourceLastUpdated)),
                new SearchParameterInfo("_profile", SearchParamType.Token, new Uri(ResourceProfile)),
                new SearchParameterInfo("_security", SearchParamType.Token, new Uri(ResourceSecurity)),
                _queryParameter,
            };

            _testSearchParamInfo = new SearchParameterInfo("_test", SearchParamType.Special, new Uri(ResourceTest));

            _searchParameterSupportResolver
            .IsSearchParameterSupported(Arg.Any <SearchParameterInfo>())
            .Returns((false, false));

            _searchParameterSupportResolver
            .IsSearchParameterSupported(Arg.Is(_searchParameterInfos[4]))
            .Returns((true, false));

            _searchParameterDefinitionManager.Start();
        }
Пример #26
0
 /// <summary>
 /// Creates a <see cref="IncludeExpression"/> that represents an include operation.
 /// </summary>
 /// <param name="resourceType">The source resource type.</param>
 /// <param name="referenceSearchParameter">The search parameter that establishes the reference between resources</param>
 /// <param name="targetResourceType">The target resource type.</param>
 /// <param name="wildCard">If this is a wildcard include.</param>
 /// <param name="reversed">If this is a reversed include (revinclude) expression.</param>
 /// <returns>A <see cref="IncludeExpression"/> that represents an include on <param name="targetResourceType"> through <paramref name="referenceSearchParameter"/>.</param></returns>
 public static IncludeExpression Include(string resourceType, SearchParameterInfo referenceSearchParameter, string targetResourceType, bool wildCard, bool reversed)
 {
     return(new IncludeExpression(resourceType, referenceSearchParameter, targetResourceType, wildCard, reversed));
 }
Пример #27
0
        private List <(string ResourceType, SearchParameterInfo SearchParameter)> ValidateAndGetFlattenedList()
        {
            var issues = new List <OperationOutcomeIssue>();

            Bundle bundle = null;

            using (Stream stream = _assembly.GetManifestResourceStream(_embeddedResourceName))
            {
                using TextReader reader     = new StreamReader(stream);
                using JsonReader jsonReader = new JsonTextReader(reader);
                try
                {
                    // The parser does some basic validation such as making sure entry is not null, enum has the right value, and etc.
                    bundle = _fhirJsonParser.Parse <Bundle>(jsonReader);
                }
                catch (FormatException ex)
                {
                    AddIssue(ex.Message);
                }
            }

            EnsureNoIssues();

            // Do the first pass to make sure all resources are SearchParameter.
            for (int entryIndex = 0; entryIndex < bundle.Entry.Count; entryIndex++)
            {
                // Make sure resources are not null and they are SearchParameter.
                EntryComponent entry = bundle.Entry[entryIndex];

                var searchParameter = entry.Resource as SearchParameter;

                if (searchParameter == null)
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionInvalidResource, entryIndex);
                    continue;
                }

                try
                {
                    SearchParameterInfo searchParameterInfo = CreateSearchParameterInfo(searchParameter);
                    _uriDictionary.Add(new Uri(searchParameter.Url), searchParameterInfo);
                }
                catch (FormatException)
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionInvalidDefinitionUri, entryIndex);
                    continue;
                }
                catch (ArgumentException)
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionDuplicatedEntry, searchParameter.Url);
                    continue;
                }
            }

            EnsureNoIssues();

            var validatedSearchParameters = new List <(string ResourceType, SearchParameterInfo SearchParameter)>
            {
                // _type is currently missing from the search params definition bundle, so we inject it in here.
                (ResourceType.Resource.ToString(), new SearchParameterInfo(SearchParameterNames.ResourceType, SearchParamType.Token.ToString(), SearchParameterNames.ResourceTypeUri, null, "Resource.type().name", null)),
            };

            // Do the second pass to make sure the definition is valid.
            for (int entryIndex = 0; entryIndex < bundle.Entry.Count; entryIndex++)
            {
                var searchParameter = (SearchParameter)bundle.Entry[entryIndex].Resource;

                // If this is a composite search parameter, then make sure components are defined.
                if (searchParameter.Type == SearchParamType.Composite)
                {
                    if (searchParameter.Component?.Count == 0)
                    {
                        AddIssue(Core.Resources.SearchParameterDefinitionInvalidComponent, searchParameter.Url);
                        continue;
                    }

                    for (int componentIndex = 0; componentIndex < searchParameter.Component.Count; componentIndex++)
                    {
                        ComponentComponent component = searchParameter.Component[componentIndex];

                        if (component.GetComponentDefinitionUri() == null ||
                            !_uriDictionary.TryGetValue(component.GetComponentDefinitionUri(), out SearchParameterInfo componentSearchParameter))
                        {
                            AddIssue(
                                Core.Resources.SearchParameterDefinitionInvalidComponentReference,
                                searchParameter.Url,
                                componentIndex);
                            continue;
                        }

                        if (componentSearchParameter.Type == SearchParamType.Composite.ToValueSet())
                        {
                            AddIssue(
                                Core.Resources.SearchParameterDefinitionComponentReferenceCannotBeComposite,
                                searchParameter.Url,
                                componentIndex);
                            continue;
                        }

                        if (string.IsNullOrWhiteSpace(component.Expression))
                        {
                            AddIssue(
                                Core.Resources.SearchParameterDefinitionInvalidComponentExpression,
                                searchParameter.Url,
                                componentIndex);
                            continue;
                        }
                    }
                }

                // Make sure the base is defined.
                if (searchParameter.BaseElement?.Count == 0)
                {
                    AddIssue(Core.Resources.SearchParameterDefinitionBaseNotDefined, searchParameter.Url);
                    continue;
                }

                for (int baseElementIndex = 0; baseElementIndex < searchParameter.BaseElement.Count; baseElementIndex++)
                {
                    Code <ResourceType> code = searchParameter.BaseElement[baseElementIndex];

                    string baseResourceType = code.Value.Value.ToString();

                    // Make sure the expression is not empty unless they are known to have empty expression.
                    // These are special search parameters that searches across all properties and needs to be handled specially.
                    if (ShouldExcludeEntry(baseResourceType, searchParameter.Name))
                    {
                        continue;
                    }
                    else
                    {
                        if (string.IsNullOrWhiteSpace(searchParameter.Expression))
                        {
                            AddIssue(Core.Resources.SearchParameterDefinitionInvalidExpression, searchParameter.Url);
                            continue;
                        }
                    }

                    validatedSearchParameters.Add((baseResourceType, CreateSearchParameterInfo(searchParameter)));
                }
            }

            EnsureNoIssues();

            return(validatedSearchParameters);

            void AddIssue(string format, params object[] args)
            {
                issues.Add(new OperationOutcomeIssue(
                               OperationOutcomeConstants.IssueSeverity.Fatal,
                               OperationOutcomeConstants.IssueType.Invalid,
                               string.Format(CultureInfo.InvariantCulture, format, args)));
            }

            void EnsureNoIssues()
            {
                if (issues.Count != 0)
                {
                    throw new InvalidDefinitionException(
                              Core.Resources.SearchParameterDefinitionContainsInvalidEntry,
                              issues.ToArray());
                }
            }
        }
Пример #28
0
        public SearchParameterDefinitionManagerTests()
        {
            _searchParameterSupportResolver = Substitute.For <ISearchParameterSupportResolver>();
            _mediator = Substitute.For <IMediator>();
            _searchParameterStatusDataStore   = Substitute.For <ISearchParameterStatusDataStore>();
            _searchParameterDefinitionManager = new SearchParameterDefinitionManager(ModelInfoProvider.Instance, _mediator);
            _fhirRequestContextAccessor       = Substitute.For <IFhirRequestContextAccessor>();
            _fhirRequestContextAccessor.FhirRequestContext.Returns(_fhirRequestContext);

            _manager = new SearchParameterStatusManager(
                _searchParameterStatusDataStore,
                _searchParameterDefinitionManager,
                _searchParameterSupportResolver,
                _mediator);

            _searchParameterStatusDataStore.GetSearchParameterStatuses()
            .Returns(new[]
            {
                new ResourceSearchParameterStatus
                {
                    Status = SearchParameterStatus.Enabled,
                    Uri    = new Uri(ResourceId),
                },
                new ResourceSearchParameterStatus
                {
                    Status = SearchParameterStatus.Enabled,
                    Uri    = new Uri(ResourceLastUpdated),
                    IsPartiallySupported = true,
                },
                new ResourceSearchParameterStatus
                {
                    Status = SearchParameterStatus.Disabled,
                    Uri    = new Uri(ResourceProfile),
                },
                new ResourceSearchParameterStatus
                {
                    Status = SearchParameterStatus.Supported,
                    Uri    = new Uri(ResourceSecurity),
                },
            });

            _queryParameter = new SearchParameterInfo("_query", "_query", SearchParamType.Token, new Uri(ResourceQuery), baseResourceTypes: new List <string>()
            {
                "Patient"
            });
            _searchParameterInfos = new[]
            {
                new SearchParameterInfo("_id", "_id", SearchParamType.Token, new Uri(ResourceId)),
                new SearchParameterInfo("_lastUpdated", "_lastUpdated", SearchParamType.Token, new Uri(ResourceLastUpdated)),
                new SearchParameterInfo("_profile", "_profile", SearchParamType.Token, new Uri(ResourceProfile)),
                new SearchParameterInfo("_security", "_security", SearchParamType.Token, new Uri(ResourceSecurity)),
                _queryParameter,
            };

            _testSearchParamInfo = new SearchParameterInfo("_test", "_test", SearchParamType.Special, new Uri(ResourceTest));

            _searchParameterSupportResolver
            .IsSearchParameterSupported(Arg.Any <SearchParameterInfo>())
            .Returns((false, false));

            _searchParameterSupportResolver
            .IsSearchParameterSupported(Arg.Is(_searchParameterInfos[4]))
            .Returns((true, false));

            var searchParameterDataStoreValidator = Substitute.For <IDataStoreSearchParameterValidator>();

            searchParameterDataStoreValidator.ValidateSearchParameter(Arg.Any <SearchParameterInfo>(), out Arg.Any <string>()).Returns(true, null);

            _searchParameterOperations = new SearchParameterOperations(_manager, _searchParameterDefinitionManager, ModelInfoProvider.Instance, _searchParameterSupportResolver, searchParameterDataStoreValidator);
        }
 private static void ValidateSearchParam(SearchParameterInfo expectedSearchParam, SearchParameterInfo actualSearchParam)
 {
     Assert.Equal(expectedSearchParam.Name, actualSearchParam.Name);
     Assert.Equal(expectedSearchParam.Type, actualSearchParam.Type);
     Assert.Equal(expectedSearchParam.Url, actualSearchParam.Url);
 }
        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));
                    }

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

                    setDefaultBundleTotal = false;
                }
                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)
                {
                    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.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, false /* not reversed */))
                                           .Where(item => item != null));
            }

            if (searchParams.RevInclude?.Count > 0)
            {
                searchExpressions.AddRange(searchParams.RevInclude.Select(
                                               q => _expressionParser.ParseInclude(parsedResourceType.ToString(), q, true /* reversed */))
                                           .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);

                        if (searchParameterInfo.IsSortSupported())
                        {
                            sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder()));
                        }
                        else
                        {
                            throw new SearchParameterNotSupportedException(string.Format(Core.Resources.SearchSortParameterNotSupported, searchParameterInfo.Name));
                        }
                    }
                    catch (SearchParameterNotSupportedException)
                    {
                        (unsupportedSortings ??= new List <(string parameterName, string reason)>()).Add((sorting.Item1, string.Format(Core.Resources.SearchSortParameterNotSupported, sorting.Item1)));
                    }
                }

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

            void ValidateTotalType(TotalType totalType)
            {
                // Estimate is not yet supported.
                if (totalType == TotalType.Estimate)
                {
                    throw new SearchOperationNotSupportedException(string.Format(Core.Resources.UnsupportedTotalParameter, totalType, SupportedTotalTypes));
                }
            }
        }