Exemplo n.º 1
0
        public void GivenACompositeWithVariousTypes_WhenBuilt_ThenCorrectExpressionShouldBeCreated()
        {
            const string  system         = "system";
            const string  code           = "code";
            const decimal quantity       = 10;
            const string  quantitySystem = "quantity-system";
            const string  quantityCode   = "quantity-code";

            var codeUri     = new Uri("http://code");
            var quantityUri = new Uri("http://quantity");

            SearchParameter searchParameter = CreateCompositeSearchParameter(
                new ComponentComponent()
            {
                Definition = new ResourceReference(codeUri.ToString()),
            },
                new ComponentComponent()
            {
                Definition = new ResourceReference(quantityUri.ToString()),
            });

            _searchParameterDefinitionManager.GetSearchParameter(codeUri).Returns(
                new SearchParameter()
            {
                Name = "code",
                Type = SearchParamType.Token,
            });

            _searchParameterDefinitionManager.GetSearchParameter(quantityUri).Returns(
                new SearchParameter()
            {
                Name = "quantity",
                Type = SearchParamType.Quantity,
            });

            Validate(
                searchParameter,
                null,
                $"{system}|{code}${quantity}|{quantitySystem}|{quantityCode}",
                outer => ValidateMultiaryExpression(
                    outer,
                    MultiaryOperator.And,
                    e => ValidateMultiaryExpression(
                        e,
                        MultiaryOperator.And,
                        se => ValidateStringExpression(se, FieldName.TokenSystem, StringOperator.Equals, system, false),
                        se => ValidateStringExpression(se, FieldName.TokenCode, StringOperator.Equals, code, false)),
                    e => ValidateMultiaryExpression(
                        e,
                        MultiaryOperator.And,
                        e1 => ValidateStringExpression(e1, FieldName.QuantitySystem, StringOperator.Equals, quantitySystem, false),
                        e1 => ValidateStringExpression(e1, FieldName.QuantityCode, StringOperator.Equals, quantityCode, false),
                        e1 => ValidateMultiaryExpression(
                            e1,
                            MultiaryOperator.And,
                            e2 => ValidateBinaryExpression(e2, FieldName.Quantity, BinaryOperator.GreaterThanOrEqual, 9.5m),
                            e2 => ValidateBinaryExpression(e2, FieldName.Quantity, BinaryOperator.LessThanOrEqual, 10.5m)))));
        }
Exemplo n.º 2
0
        public IncludeExpression ParseInclude(string resourceType, string includeValue, bool isReversed, bool iterate)
        {
            var valueSpan = includeValue.AsSpan();

            if (!TrySplit(SearchSplitChar, ref valueSpan, out ReadOnlySpan <char> originalType))
            {
                throw new InvalidSearchOperationException(isReversed ? Core.Resources.RevIncludeMissingType : Core.Resources.IncludeMissingType);
            }

            if (resourceType.Equals(KnownResourceTypes.DomainResource, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new InvalidSearchOperationException(Core.Resources.IncludeCannotBeAgainstBase);
            }

            SearchParameterInfo refSearchParameter;
            List <string>       referencedTypes = null;
            bool   wildCard   = false;
            string targetType = null;

            if (valueSpan.Equals("*".AsSpan(), StringComparison.InvariantCultureIgnoreCase))
            {
                refSearchParameter = null;
                wildCard           = true;
            }
            else
            {
                if (!TrySplit(SearchSplitChar, ref valueSpan, out ReadOnlySpan <char> searchParam))
                {
                    searchParam = valueSpan;
                }
                else
                {
                    targetType = valueSpan.ToString();
                }

                refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter(originalType.ToString(), searchParam.ToString());
            }

            if (wildCard)
            {
                referencedTypes = new List <string>();
                var searchParameters = _searchParameterDefinitionManager.GetSearchParameters(resourceType)
                                       .Where(p => p.Type == ValueSets.SearchParamType.Reference);

                foreach (var p in searchParameters)
                {
                    foreach (var t in p.TargetResourceTypes)
                    {
                        if (!referencedTypes.Contains(t))
                        {
                            referencedTypes.Add(t);
                        }
                    }
                }
            }

            return(new IncludeExpression(resourceType, refSearchParameter, originalType.ToString(), targetType, referencedTypes, wildCard, isReversed, iterate));
        }
        public SearchParameterInfo GetSearchParameter(string resourceType, string name)
        {
            SearchParameterInfo parameter = _inner.GetSearchParameter(resourceType, name);

            if (parameter.IsSupported)
            {
                return(parameter);
            }

            throw new SearchParameterNotSupportedException(resourceType, name);
        }
Exemplo n.º 4
0
        public async Task UpdateSearchParameterStatusAsync(IReadOnlyCollection <string> searchParameterUris, SearchParameterStatus status)
        {
            var searchParameterStatusList = new List <ResourceSearchParameterStatus>();
            var updated = new List <SearchParameterInfo>();

            foreach (string uri in searchParameterUris)
            {
                var searchParamUri = new Uri(uri);

                var paramInfo = _searchParameterDefinitionManager.GetSearchParameter(searchParamUri);
                updated.Add(paramInfo);
                paramInfo.IsSearchable = status == SearchParameterStatus.Enabled;
                paramInfo.IsSupported  = status == SearchParameterStatus.Supported || status == SearchParameterStatus.Enabled;

                searchParameterStatusList.Add(new ResourceSearchParameterStatus()
                {
                    LastUpdated = Clock.UtcNow,
                    Status      = status,
                    Uri         = searchParamUri,
                });
            }

            await _searchParameterStatusDataStore.UpsertStatuses(searchParameterStatusList);

            await _mediator.Publish(new SearchParametersUpdated(updated));
        }
        public SearchParameterValidatorTests()
        {
            _searchParameterDefinitionManager.When(s => s.GetSearchParameter(Arg.Is <Uri>(uri => uri != new Uri("http://duplicate")))).
            Do(x => throw new SearchParameterNotSupportedException("message"));
            _searchParameterDefinitionManager.GetSearchParameter(new Uri("http://duplicate")).Returns(new SearchParameterInfo("duplicate", "duplicate"));

            _fhirOperationDataStore.CheckActiveReindexJobsAsync(CancellationToken.None).Returns((false, string.Empty));
        }
        public async Task AddSearchParameterAsync(ITypedElement searchParam, CancellationToken cancellationToken)
        {
            try
            {
                // verify the parameter is supported before continuing
                var searchParameterWrapper = new SearchParameterWrapper(searchParam);
                var searchParameterInfo    = new SearchParameterInfo(searchParameterWrapper);

                if (searchParameterInfo.Component?.Any() == true)
                {
                    foreach (SearchParameterComponentInfo c in searchParameterInfo.Component)
                    {
                        c.ResolvedSearchParameter = _searchParameterDefinitionManager.GetSearchParameter(c.DefinitionUrl.OriginalString);
                    }
                }

                (bool Supported, bool IsPartiallySupported)supportedResult = _searchParameterSupportResolver.IsSearchParameterSupported(searchParameterInfo);

                if (!supportedResult.Supported)
                {
                    throw new SearchParameterNotSupportedException(searchParameterInfo.Url);
                }

                // check data store specific support for SearchParameter
                if (!_dataStoreSearchParameterValidator.ValidateSearchParameter(searchParameterInfo, out var errorMessage))
                {
                    throw new SearchParameterNotSupportedException(errorMessage);
                }

                _logger.LogTrace("Adding the search parameter '{url}'", searchParameterWrapper.Url);
                _searchParameterDefinitionManager.AddNewSearchParameters(new List <ITypedElement> {
                    searchParam
                });

                await _searchParameterStatusManager.AddSearchParameterStatusAsync(new List <string> {
                    searchParameterWrapper.Url
                }, cancellationToken);
            }
            catch (FhirException fex)
            {
                fex.Issues.Add(new OperationOutcomeIssue(
                                   OperationOutcomeConstants.IssueSeverity.Error,
                                   OperationOutcomeConstants.IssueType.Exception,
                                   Core.Resources.CustomSearchCreateError));

                throw;
            }
            catch (Exception ex)
            {
                var customSearchException = new ConfigureCustomSearchException(Core.Resources.CustomSearchCreateError);
                customSearchException.Issues.Add(new OperationOutcomeIssue(
                                                     OperationOutcomeConstants.IssueSeverity.Error,
                                                     OperationOutcomeConstants.IssueType.Exception,
                                                     ex.Message));

                throw customSearchException;
            }
        }
Exemplo n.º 7
0
        private IEnumerable <SearchIndexEntry> ProcessCompositeSearchParameter(SearchParameterInfo searchParameter, Base resource, FhirEvaluationContext context)
        {
            Debug.Assert(searchParameter?.Type == SearchParamType.Composite, "The search parameter must be composite.");

            SearchParameterInfo compositeSearchParameterInfo = searchParameter;

            IEnumerable <Base> rootObjects = resource.Select(searchParameter.Expression, context);

            foreach (var rootObject in rootObjects)
            {
                int  numberOfComponents = searchParameter.Component.Count;
                bool skip = false;

                var componentValues = new IReadOnlyList <ISearchValue> [numberOfComponents];

                // For each object extracted from the expression, we will need to evaluate each component.
                for (int i = 0; i < numberOfComponents; i++)
                {
                    SearchParameterComponentInfo component = searchParameter.Component[i];

                    // First find the type of the component.
                    SearchParameterInfo componentSearchParameterDefinition = _searchParameterDefinitionManager.GetSearchParameter(component.DefinitionUrl);

                    IReadOnlyList <ISearchValue> extractedComponentValues = ExtractSearchValues(
                        componentSearchParameterDefinition.Url.ToString(),
                        componentSearchParameterDefinition.Type,
                        componentSearchParameterDefinition.TargetResourceTypes,
                        rootObject,
                        component.Expression,
                        context);

                    // Filter out any search value that's not valid as a composite component.
                    extractedComponentValues = extractedComponentValues
                                               .Where(sv => sv.IsValidAsCompositeComponent)
                                               .ToArray();

                    if (!extractedComponentValues.Any())
                    {
                        // One of the components didn't have any value and therefore it will not be indexed.
                        skip = true;
                        break;
                    }

                    componentValues[i] = extractedComponentValues;
                }

                if (skip)
                {
                    continue;
                }

                yield return(new SearchIndexEntry(compositeSearchParameterInfo, new CompositeSearchValue(componentValues)));
            }
        }
Exemplo n.º 8
0
        public void GivenAChainedParameterPointingToMultipleResourceTypesAndSearchParamIsNotSupportedByAllTargetResourceTypes_WhenParsed_ThenOnlyExpressionsForResourceTypeThatSupportsSearchParamShouldBeCreated()
        {
            ResourceType sourceResourceType = ResourceType.Patient;

            // The reference will support both Organization and Practitioner,
            // but the search value will only be supported by Practitioner.
            ResourceType[] targetResourceTypes = new[] { ResourceType.Organization, ResourceType.Practitioner };

            string param1 = "ref";
            string param2 = "param";

            string key   = $"{param1}.{param2}";
            string value = "Lewis";

            // Setup the search parameters.
            SetupReferenceSearchParameter(sourceResourceType, param1, targetResourceTypes);

            // Setup the Organization to not support this search param.
            _searchParameterDefinitionManager.GetSearchParameter(ResourceType.Organization.ToString(), param2)
            .Returns(x => throw new SearchParameterNotSupportedException(x.ArgAt <string>(0), x.ArgAt <string>(1)));

            // Setup the Practitioner to support this search param.
            SearchParameterInfo searchParameter = SetupSearchParameter(ResourceType.Practitioner, param2);

            Expression expectedExpression = SetupExpression(searchParameter, value);

            // Parse the expression.
            Expression expression = _expressionParser.Parse(sourceResourceType.ToString(), key, value);

            ValidateMultiaryExpression(
                expression,
                MultiaryOperator.Or,
                chainedExpression => ValidateChainedExpression(
                    chainedExpression,
                    sourceResourceType,
                    param1,
                    ResourceType.Practitioner.ToString(),
                    actualSearchExpression => Assert.Equal(expectedExpression, actualSearchExpression)));
        }
Exemplo n.º 9
0
        public IncludeExpression ParseInclude(string resourceType, string includeValue)
        {
            var valueSpan = includeValue.AsSpan();

            if (!TrySplit(SearchSplitChar, ref valueSpan, out ReadOnlySpan <char> originalType))
            {
                throw new InvalidSearchOperationException(Core.Resources.IncludeMissingType);
            }

            if (resourceType.Equals(typeof(DomainResource).Name, StringComparison.InvariantCultureIgnoreCase))
            {
                throw new InvalidSearchOperationException(Core.Resources.IncludeCannotBeAgainstBase);
            }

            SearchParameterInfo refSearchParameter;
            bool   wildCard   = false;
            string targetType = null;

            if (valueSpan.Equals("*".AsSpan(), StringComparison.InvariantCultureIgnoreCase))
            {
                refSearchParameter = null;
                wildCard           = true;
            }
            else
            {
                if (!TrySplit(SearchSplitChar, ref valueSpan, out ReadOnlySpan <char> searchParam))
                {
                    searchParam = valueSpan;
                }
                else
                {
                    targetType = valueSpan.ToString();
                }

                refSearchParameter = _searchParameterDefinitionManager.GetSearchParameter(originalType.ToString(), searchParam.ToString());
            }

            return(new IncludeExpression(resourceType, refSearchParameter, targetType, wildCard));
        }
Exemplo n.º 10
0
        public SearchOptionsFactory(
            IExpressionParser expressionParser,
            ISearchParameterDefinitionManager searchParameterDefinitionManager,
            ILogger <SearchOptionsFactory> logger)
        {
            EnsureArg.IsNotNull(expressionParser, nameof(expressionParser));
            EnsureArg.IsNotNull(searchParameterDefinitionManager, nameof(searchParameterDefinitionManager));
            EnsureArg.IsNotNull(logger, nameof(logger));

            _expressionParser = expressionParser;
            _logger           = logger;

            _resourceTypeSearchParameter = searchParameterDefinitionManager.GetSearchParameter(ResourceType.Resource.ToString(), SearchParameterNames.ResourceType);
        }
Exemplo n.º 11
0
        public SearchOptionsFactory(
            IExpressionParser expressionParser,
            ISearchParameterDefinitionManager.SearchableSearchParameterDefinitionManagerResolver searchParameterDefinitionManagerResolver,
            IOptions <CoreFeatureConfiguration> featureConfiguration,
            ILogger <SearchOptionsFactory> logger)
        {
            EnsureArg.IsNotNull(expressionParser, nameof(expressionParser));
            EnsureArg.IsNotNull(searchParameterDefinitionManagerResolver, nameof(searchParameterDefinitionManagerResolver));
            EnsureArg.IsNotNull(featureConfiguration?.Value, nameof(featureConfiguration));
            EnsureArg.IsNotNull(logger, nameof(logger));

            _expressionParser = expressionParser;
            _searchParameterDefinitionManager = searchParameterDefinitionManagerResolver();
            _logger = logger;
            _featureConfiguration = featureConfiguration.Value;

            _resourceTypeSearchParameter = _searchParameterDefinitionManager.GetSearchParameter(ResourceType.Resource.ToString(), SearchParameterNames.ResourceType);
        }
Exemplo n.º 12
0
        public FhirCosmosSearchService(
            ISearchOptionsFactory searchOptionsFactory,
            CosmosFhirDataStore fhirDataStore,
            IQueryBuilder queryBuilder,
            ISearchParameterDefinitionManager searchParameterDefinitionManager,
            IFhirRequestContextAccessor requestContextAccessor)
            : base(searchOptionsFactory, fhirDataStore)
        {
            EnsureArg.IsNotNull(fhirDataStore, nameof(fhirDataStore));
            EnsureArg.IsNotNull(queryBuilder, nameof(queryBuilder));
            EnsureArg.IsNotNull(searchParameterDefinitionManager, nameof(searchParameterDefinitionManager));
            EnsureArg.IsNotNull(requestContextAccessor, nameof(requestContextAccessor));

            _fhirDataStore               = fhirDataStore;
            _queryBuilder                = queryBuilder;
            _requestContextAccessor      = requestContextAccessor;
            _resourceTypeSearchParameter = searchParameterDefinitionManager.GetSearchParameter(KnownResourceTypes.Resource, SearchParameterNames.ResourceType);
        }
        public async Task UpdateSearchParameterStatusAsync(IReadOnlyCollection <string> searchParameterUris, SearchParameterStatus status)
        {
            var searchParameterStatusList = new List <ResourceSearchParameterStatus>();
            var updated    = new List <SearchParameterInfo>();
            var parameters = (await _searchParameterStatusDataStore.GetSearchParameterStatuses())
                             .ToDictionary(x => x.Uri);

            foreach (string uri in searchParameterUris)
            {
                var searchParamUri = new Uri(uri);

                var paramInfo = _searchParameterDefinitionManager.GetSearchParameter(searchParamUri);
                updated.Add(paramInfo);
                paramInfo.IsSearchable = status == SearchParameterStatus.Enabled;
                paramInfo.IsSupported  = status == SearchParameterStatus.Supported || status == SearchParameterStatus.Enabled;

                if (parameters.TryGetValue(searchParamUri, out var existingStatus))
                {
                    existingStatus.LastUpdated = Clock.UtcNow;
                    existingStatus.Status      = status;

                    if (paramInfo.IsSearchable && existingStatus.SortStatus == SortParameterStatus.Supported)
                    {
                        existingStatus.SortStatus = SortParameterStatus.Enabled;
                        paramInfo.SortStatus      = SortParameterStatus.Enabled;
                    }

                    searchParameterStatusList.Add(existingStatus);
                }
                else
                {
                    searchParameterStatusList.Add(new ResourceSearchParameterStatus
                    {
                        LastUpdated = Clock.UtcNow,
                        Status      = status,
                        Uri         = searchParamUri,
                    });
                }
            }

            await _searchParameterStatusDataStore.UpsertStatuses(searchParameterStatusList);

            await _mediator.Publish(new SearchParametersUpdated(updated));
        }
Exemplo n.º 14
0
        public void GivenACompositeWithInvalidModifier_WhenBuilding_ThenInvalidSearchOperationExceptionShouldBeThrown(SearchModifier modifier)
        {
            var quantityUri = new Uri("http://quantity");

            SearchParameterComponentInfo[] components = new[] { new SearchParameterComponentInfo(quantityUri), new SearchParameterComponentInfo(quantityUri) };
            var searchParameter1 = new SearchParameterInfo(
                DefaultParamName,
                Microsoft.Health.Fhir.ValueSets.SearchParamType.Composite,
                components: components);
            SearchParameterInfo searchParameter = searchParameter1;

            _searchParameterDefinitionManager.GetSearchParameter(quantityUri).Returns(
                new SearchParameter
            {
                Name = "quantity",
                Type = SearchParamType.Quantity,
            }.ToInfo());

            Assert.Throws <InvalidSearchOperationException>(
                () => _parser.Parse(CreateSearchParameter(SearchParamType.Composite), modifier, "10|s|c$10|s|c"));
        }
Exemplo n.º 15
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);
                    }
        }
Exemplo n.º 16
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));
                    }

                    // 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));
                }
            }
        }
Exemplo n.º 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> >();
            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));
                    }

                    // For circular include iterate expressions, add an informational issue indicating that a single iteration is supported.
                    // See https://www.hl7.org/fhir/search.html#revinclude.
                    if (expression.Iterate && expression.CircularReference)
                    {
                        _contextAccessor.FhirRequestContext.BundleIssues.Add(
                            new OperationOutcomeIssue(
                                OperationOutcomeConstants.IssueSeverity.Information,
                                OperationOutcomeConstants.IssueType.Informational,
                                string.Format(Core.Resources.IncludeIterateCircularReferenceExecutedOnce, p.Item1, p.Item2)));
                    }

                    return expression;
                }));
            }

            void ValidateTotalType(TotalType totalType)
            {
                // Estimate is not yet supported.
                if (totalType == TotalType.Estimate)
                {
                    throw new SearchOperationNotSupportedException(string.Format(Core.Resources.UnsupportedTotalParameter, totalType, SupportedTotalTypes));
                }
            }
        }
        public SearchParameterStatusManagerTests()
        {
            _searchParameterRegistry          = Substitute.For <ISearchParameterRegistry>();
            _searchParameterDefinitionManager = Substitute.For <ISearchParameterDefinitionManager>();
            _searchParameterSupportResolver   = Substitute.For <ISearchParameterSupportResolver>();
            _mediator = Substitute.For <IMediator>();

            _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,
            };

            _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));
        }
Exemplo n.º 19
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 || query.Item1 == KnownQueryParameterNames.Pretty)
                {
                    // _format and _pretty are not search parameters, so we can ignore them.
                }
                else if (string.Equals(query.Item1, KnownQueryParameterNames.Type, StringComparison.OrdinalIgnoreCase))
                {
                    if (string.IsNullOrWhiteSpace(query.Item2))
                    {
                        throw new BadRequestException(string.Format(Core.Resources.InvalidTypeParameter, query.Item2));
                    }

                    var types    = query.Item2.SplitByOrSeparator();
                    var badTypes = types.Where(type => !ModelInfoProvider.IsKnownResource(type)).ToHashSet();

                    if (badTypes.Count != 0)
                    {
                        _contextAccessor.RequestContext?.BundleIssues.Add(
                            new OperationOutcomeIssue(
                                OperationOutcomeConstants.IssueSeverity.Warning,
                                OperationOutcomeConstants.IssueType.NotSupported,
                                string.Format(Core.Resources.InvalidTypeParameter, badTypes.OrderBy(x => x).Select(type => $"'{type}'").JoinByOrSeparator())));
                        if (badTypes.Count != types.Count)
                        {
                            // In case of we have acceptable types, we filter invalid types from search.
                            searchParams.Add(KnownQueryParameterNames.Type, types.Except(badTypes).JoinByOrSeparator());
                        }
                        else
                        {
                            // If all types are invalid, we add them to search params. If we remove them, we wouldn't filter by type, and return all types,
                            // which is incorrect behaviour. Optimally we should indicate in search options what it would yield nothing, and skip search,
                            // but there is no option for that right now.
                            searchParams.Add(KnownQueryParameterNames.Type, query.Item2);
                        }
                    }
                    else
                    {
                        searchParams.Add(KnownQueryParameterNames.Type, query.Item2);
                    }
                }
                else if (string.IsNullOrWhiteSpace(query.Item1) || string.IsNullOrWhiteSpace(query.Item2))
                {
                    // Query parameter with empty value is not supported.
                    unsupportedSearchParameters.Add(query);
                }
                else if (string.Compare(query.Item1, KnownQueryParameterNames.Total, StringComparison.OrdinalIgnoreCase) == 0)
                {
                    if (Enum.TryParse <TotalType>(query.Item2, true, out var totalType))
                    {
                        ValidateTotalType(totalType);

                        searchOptions.IncludeTotal = totalType;
                        setDefaultBundleTotal      = false;
                    }
                    else
                    {
                        throw new BadRequestException(string.Format(Core.Resources.InvalidTotalParameter, query.Item2, SupportedTotalTypes));
                    }
                }
                else
                {
                    // Parse the search parameters.
                    try
                    {
                        // Basic format checking (e.g. integer value for _count key etc.).
                        searchParams.Add(query.Item1, query.Item2);
                    }
                    catch (Exception ex)
                    {
                        throw new BadRequestException(ex.Message);
                    }
                }
            }

            searchOptions.ContinuationToken = continuationToken;

            if (setDefaultBundleTotal)
            {
                ValidateTotalType(_featureConfiguration.IncludeTotalInBundle);
                searchOptions.IncludeTotal = _featureConfiguration.IncludeTotalInBundle;
            }

            // Check the item count.
            if (searchParams.Count != null)
            {
                searchOptions.MaxItemCountSpecifiedByClient = true;

                if (searchParams.Count > _featureConfiguration.MaxItemCountPerSearch)
                {
                    searchOptions.MaxItemCount = _featureConfiguration.MaxItemCountPerSearch;

                    _contextAccessor.RequestContext?.BundleIssues.Add(
                        new OperationOutcomeIssue(
                            OperationOutcomeConstants.IssueSeverity.Information,
                            OperationOutcomeConstants.IssueType.Informational,
                            string.Format(Core.Resources.SearchParamaterCountExceedLimit, _featureConfiguration.MaxItemCountPerSearch, searchParams.Count)));
                }
                else
                {
                    searchOptions.MaxItemCount = searchParams.Count.Value;
                }
            }
            else
            {
                searchOptions.MaxItemCount = _featureConfiguration.DefaultItemCountPerSearch;
            }

            searchOptions.IncludeCount = _featureConfiguration.DefaultIncludeCountPerSearch;

            if (searchParams.Elements?.Any() == true && searchParams.Summary != null && searchParams.Summary != SummaryType.False)
            {
                // The search parameters _elements and _summarize cannot be specified for the same request.
                throw new BadRequestException(string.Format(Core.Resources.ElementsAndSummaryParametersAreIncompatible, KnownQueryParameterNames.Summary, KnownQueryParameterNames.Elements));
            }

            // Check to see if only the count should be returned
            searchOptions.CountOnly = searchParams.Summary == SummaryType.Count;

            // If the resource type is not specified, then the common
            // search parameters should be used.
            ResourceType[] parsedResourceTypes = new[] { ResourceType.DomainResource };

            var searchExpressions = new List <Expression>();

            if (string.IsNullOrWhiteSpace(resourceType))
            {
                // Try to parse resource types from _type Search Parameter
                // This will result in empty array if _type has any modifiers
                // Which is good, since :not modifier changes the meaning of the
                // search parameter and we can no longer use it to deduce types
                // (and should proceed with ResourceType.DomainResource in that case)
                var resourceTypes = searchParams.Parameters
                                    .Where(q => q.Item1 == KnownQueryParameterNames.Type) // <-- Equality comparison to avoid modifiers
                                    .SelectMany(q => q.Item2.SplitByOrSeparator())
                                    .Where(type => ModelInfoProvider.IsKnownResource(type))
                                    .Select(x =>
                {
                    if (!Enum.TryParse(x, out ResourceType parsedType))
                    {
                        // Should never get here
                        throw new ResourceNotSupportedException(x);
                    }

                    return(parsedType);
                })
                                    .Distinct();

                if (resourceTypes.Any())
                {
                    parsedResourceTypes = resourceTypes.ToArray();
                }
            }
            else
            {
                if (!Enum.TryParse(resourceType, out parsedResourceTypes[0]))
                {
                    throw new ResourceNotSupportedException(resourceType);
                }

                searchExpressions.Add(Expression.SearchParameter(_resourceTypeSearchParameter, Expression.StringEquals(FieldName.TokenCode, null, resourceType, false)));
            }

            var resourceTypesString = parsedResourceTypes.Select(x => x.ToString()).ToArray();

            searchExpressions.AddRange(searchParams.Parameters.Select(
                                           q =>
            {
                try
                {
                    return(_expressionParser.Parse(resourceTypesString, q.Item1, q.Item2));
                }
                catch (SearchParameterNotSupportedException)
                {
                    unsupportedSearchParameters.Add(q);

                    return(null);
                }
            })
                                       .Where(item => item != null));

            if (searchParams.Include?.Count > 0)
            {
                searchExpressions.AddRange(searchParams.Include.Select(
                                               q => _expressionParser.ParseInclude(resourceTypesString, q, false /* not reversed */, false /* no iterate */))
                                           .Where(item => item != null));
            }

            if (searchParams.RevInclude?.Count > 0)
            {
                searchExpressions.AddRange(searchParams.RevInclude.Select(
                                               q => _expressionParser.ParseInclude(resourceTypesString, 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())
            {
                bool throwForUnsupported = false;
                if (_contextAccessor.RequestContext?.RequestHeaders != null &&
                    _contextAccessor.RequestContext.RequestHeaders.TryGetValue(KnownHeaders.Prefer, out var values))
                {
                    var handlingValue = values.FirstOrDefault(x => x.StartsWith("handling=", StringComparison.OrdinalIgnoreCase));
                    if (handlingValue != default)
                    {
                        handlingValue = handlingValue.Substring("handling=".Length);

                        if (string.IsNullOrWhiteSpace(handlingValue) || !Enum.TryParse <SearchParameterHandling>(handlingValue, true, out var handling))
                        {
                            throw new BadRequestException(string.Format(
                                                              Core.Resources.InvalidHandlingValue,
                                                              handlingValue,
                                                              string.Join(",", Enum.GetNames <SearchParameterHandling>())));
                        }

                        if (handling == SearchParameterHandling.Strict)
                        {
                            throwForUnsupported = true;
                        }
                    }
                }

                if (throwForUnsupported)
                {
                    throw new BadRequestException(string.Format(
                                                      Core.Resources.SearchParameterNotSupported,
                                                      string.Join(",", unsupportedSearchParameters.Select(x => x.Item1)),
                                                      string.Join(",", resourceTypesString)));
                }
                else
                {
                    foreach (var unsupported in unsupportedSearchParameters)
                    {
                        _contextAccessor.RequestContext?.BundleIssues.Add(new OperationOutcomeIssue(
                                                                              OperationOutcomeConstants.IssueSeverity.Warning,
                                                                              OperationOutcomeConstants.IssueType.NotSupported,
                                                                              string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchParameterNotSupported, unsupported.Item1, string.Join(",", resourceTypesString))));
                    }
                }
            }

            searchOptions.UnsupportedSearchParams = unsupportedSearchParameters;

            if (searchParams.Sort?.Count > 0)
            {
                var  sortings      = new List <(SearchParameterInfo, SortOrder)>(searchParams.Sort.Count);
                bool sortingsValid = true;

                foreach (Tuple <string, Hl7.Fhir.Rest.SortOrder> sorting in searchParams.Sort)
                {
                    try
                    {
                        SearchParameterInfo searchParameterInfo = resourceTypesString.Select(t => _searchParameterDefinitionManager.GetSearchParameter(t, sorting.Item1)).Distinct().First();
                        sortings.Add((searchParameterInfo, sorting.Item2.ToCoreSortOrder()));
                    }
                    catch (SearchParameterNotSupportedException)
                    {
                        sortingsValid = false;
                        _contextAccessor.RequestContext?.BundleIssues.Add(new OperationOutcomeIssue(
                                                                              OperationOutcomeConstants.IssueSeverity.Warning,
                                                                              OperationOutcomeConstants.IssueType.NotSupported,
                                                                              string.Format(CultureInfo.InvariantCulture, Core.Resources.SearchParameterNotSupported, sorting.Item1, string.Join(", ", resourceTypesString))));
                    }
                }

                if (sortingsValid)
                {
                    if (!_sortingValidator.ValidateSorting(sortings, out IReadOnlyList <string> errorMessages))
                    {
                        if (errorMessages == null || errorMessages.Count == 0)
                        {
                            throw new InvalidOperationException($"Expected {_sortingValidator.GetType().Name} to return error messages when {nameof(_sortingValidator.ValidateSorting)} returns false");
                        }

                        sortingsValid = false;

                        foreach (var errorMessage in errorMessages)
                        {
                            _contextAccessor.RequestContext?.BundleIssues.Add(new OperationOutcomeIssue(
                                                                                  OperationOutcomeConstants.IssueSeverity.Warning,
                                                                                  OperationOutcomeConstants.IssueType.NotSupported,
                                                                                  errorMessage));
                        }
                    }
                }

                if (sortingsValid)
                {
                    searchOptions.Sort = sortings;
                }
            }

            if (searchOptions.Sort == null)
            {
                searchOptions.Sort = Array.Empty <(SearchParameterInfo searchParameterInfo, SortOrder sortOrder)>();
            }

            return(searchOptions);

            IEnumerable <IncludeExpression> ParseIncludeIterateExpressions(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(new[] { includeResourceType }, p.Item2, reversed, true);

                    // Reversed Iterate expressions (not wildcard) must specify target type if there is more than one possible target type
                    if (expression.Reversed && expression.Iterate && expression.TargetResourceType == null && expression.ReferenceSearchParameter?.TargetResourceTypes?.Count > 1)
                    {
                        throw new BadRequestException(string.Format(Core.Resources.RevIncludeIterateTargetTypeNotSpecified, p.Item2));
                    }

                    // For circular include iterate expressions, add an informational issue indicating that a single iteration is supported.
                    // See https://www.hl7.org/fhir/search.html#revinclude.
                    if (expression.Iterate && expression.CircularReference)
                    {
                        _contextAccessor.RequestContext?.BundleIssues.Add(
                            new OperationOutcomeIssue(
                                OperationOutcomeConstants.IssueSeverity.Information,
                                OperationOutcomeConstants.IssueType.Informational,
                                string.Format(Core.Resources.IncludeIterateCircularReferenceExecutedOnce, p.Item1, p.Item2)));
                    }

                    return expression;
                }));
            }

            void ValidateTotalType(TotalType totalType)
            {
                // Estimate is not yet supported.
                if (totalType == TotalType.Estimate)
                {
                    throw new SearchOperationNotSupportedException(string.Format(Core.Resources.UnsupportedTotalParameter, totalType, SupportedTotalTypes));
                }
            }
        }
        public async Task ValidateSearchParamterInput(SearchParameter searchParam, string method, CancellationToken cancellationToken)
        {
            if (await _authorizationService.CheckAccess(DataActions.Reindex) != DataActions.Reindex)
            {
                throw new UnauthorizedFhirActionException();
            }

            // check if reindex job is running
            using (IScoped <IFhirOperationDataStore> fhirOperationDataStore = _fhirOperationDataStoreFactory())
            {
                (var activeReindexJobs, var reindexJobId) = await fhirOperationDataStore.Value.CheckActiveReindexJobsAsync(cancellationToken);

                if (activeReindexJobs)
                {
                    throw new JobConflictException(string.Format(Resources.ChangesToSearchParametersNotAllowedWhileReindexing, reindexJobId));
                }
            }

            var validationFailures = new List <ValidationFailure>();

            if (string.IsNullOrEmpty(searchParam.Url))
            {
                validationFailures.Add(
                    new ValidationFailure(nameof(Base.TypeName), Resources.SearchParameterDefinitionInvalidMissingUri));
            }
            else
            {
                try
                {
                    _searchParameterDefinitionManager.GetSearchParameter(new Uri(searchParam.Url));

                    // If a post, then it is a creation of a new search parameter
                    // only allow this if no other parameters exist with the same Uri
                    if (method.Equals(HttpPostName, StringComparison.OrdinalIgnoreCase))
                    {
                        // if no exception is thrown, then the search parameter with the same Uri was found
                        // and this is a conflict
                        validationFailures.Add(
                            new ValidationFailure(
                                nameof(searchParam.Url),
                                string.Format(Resources.SearchParameterDefinitionDuplicatedEntry, searchParam.Url)));
                    }
                }
                catch (FormatException)
                {
                    validationFailures.Add(
                        new ValidationFailure(
                            nameof(searchParam.Url),
                            string.Format(Resources.SearchParameterDefinitionInvalidDefinitionUri, searchParam.Url)));
                }
                catch (SearchParameterNotSupportedException)
                {
                    // if thrown, then the search parameter is not found
                    // if a PUT, then we should be updating an exsting paramter, but it was not found
                    if (method.Equals(HttpPutName, StringComparison.OrdinalIgnoreCase) ||
                        method.Equals(HttpDeleteName, StringComparison.OrdinalIgnoreCase))
                    {
                        // if an exception above was thrown, then the search parameter with the same Uri was not found
                        // and DELETE or PUT can only run on existing parameter
                        validationFailures.Add(
                            new ValidationFailure(
                                nameof(searchParam.Url),
                                string.Format(Resources.SearchParameterDefinitionNotFound, searchParam.Url)));
                    }
                }
            }

            // validate that the url does not correspond to a search param from the spec
            // TODO: still need a method to determine spec defined search params

            // validation of the fhir path
            // TODO: separate user story for this validation

            if (validationFailures.Any())
            {
                throw new ResourceNotValidException(validationFailures);
            }
        }
Exemplo n.º 21
0
        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.Contains(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));
        }
Exemplo n.º 22
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);
        }
        private Type GetSearchValueTypeImpl(SearchParameterInfo searchParameter)
        {
            switch (searchParameter.Type)
            {
            case SearchParamType.Number:
                return(typeof(NumberSearchValue));

            case SearchParamType.Date:
                return(typeof(DateTimeSearchValue));

            case SearchParamType.String:
                return(typeof(StringSearchValue));

            case SearchParamType.Token:
                return(typeof(TokenSearchValue));

            case SearchParamType.Reference:
                return(typeof(ReferenceSearchValue));

            case SearchParamType.Quantity:
                return(typeof(QuantitySearchValue));

            case SearchParamType.Uri:
                return(typeof(UriSearchValue));

            case SearchParamType.Composite:
                return(typeof(Tuple).Assembly.GetType($"{typeof(ValueTuple).FullName}`{searchParameter.Component.Count}", throwOnError: true)
                       .MakeGenericType(searchParameter.Component.Select(c => GetSearchValueType(_searchParameterDefinitionManager.GetSearchParameter(c.DefinitionUrl))).ToArray()));

            default:
                throw new ArgumentOutOfRangeException(searchParameter.Code);
            }
        }
        public SearchParameterStatusManagerTests()
        {
            _searchParameterStatusDataStore   = Substitute.For <ISearchParameterStatusDataStore>();
            _searchParameterDefinitionManager = Substitute.For <ISearchParameterDefinitionManager>();
            _searchParameterSupportResolver   = Substitute.For <ISearchParameterSupportResolver>();
            _mediator = Substitute.For <IMediator>();

            _manager = new SearchParameterStatusManager(
                _searchParameterStatusDataStore,
                _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,
                },
            };

            _searchParameterStatusDataStore.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));
        }
Exemplo n.º 25
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));
        }