< Summary

Class:Azure.Search.Documents.Models.SearchResultsPage`1
Assembly:Azure.Search.Documents
File(s):C:\Git\azure-sdk-for-net\sdk\search\Azure.Search.Documents\src\Models\SearchResults{T}.cs
Covered lines:5
Uncovered lines:4
Coverable lines:9
Total lines:431
Line coverage:55.5% (5 of 9)
Covered branches:2
Total branches:2
Branch coverage:100% (2 of 2)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
get_TotalCount()-0%100%
get_Coverage()-0%100%
get_Facets()-0%100%
get_Values()-100%100%
get_ContinuationToken()-100%100%
GetRawResponse()-0%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\search\Azure.Search.Documents\src\Models\SearchResults{T}.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Collections.ObjectModel;
 7using System.Diagnostics;
 8using System.IO;
 9using System.Text.Json;
 10using System.Threading;
 11using System.Threading.Tasks;
 12using Azure.Core;
 13using Azure.Core.Pipeline;
 14#if EXPERIMENTAL_SERIALIZER
 15using Azure.Core.Serialization;
 16#endif
 17
 18#pragma warning disable SA1402 // File may only contain a single type
 19
 20namespace Azure.Search.Documents.Models
 21{
 22    // Hide the untyped SearchDocumentsResult
 23    [CodeGenModel("SearchDocumentsResult")]
 24    internal partial class SearchDocumentsResult { }
 25
 26    /// <summary>
 27    /// Response containing search results from an index.
 28    /// </summary>
 29    public class SearchResults<T>
 30    {
 31        /// <summary>
 32        /// The total count of results found by the search operation, or null
 33        /// if the count was not requested via
 34        /// <see cref="SearchOptions.IncludeTotalCount"/>.  If present, the
 35        /// count may be greater than the number of results in this response.
 36        /// This can happen if you use the <see cref="SearchOptions.Size"/> or
 37        /// <see cref="SearchOptions.Skip"/> parameters, or if Azure Cognitive
 38        /// Search can't return all the requested documents in a single Search
 39        /// response.
 40        /// </summary>
 41        public long? TotalCount { get; internal set; }
 42
 43        /// <summary>
 44        /// A value indicating the percentage of the index that was included in
 45        /// the query, or null if <see cref="SearchOptions.MinimumCoverage"/>
 46        /// was not specified in the request.
 47        /// </summary>
 48        public double? Coverage { get; internal set; }
 49
 50        /// <summary>
 51        /// The facet query results for the search operation, organized as a
 52        /// collection of buckets for each faceted field; null if the query did
 53        /// not include any facet expressions via
 54        /// <see cref="SearchOptions.Facets"/>.
 55        /// </summary>
 56        public IDictionary<string, IList<FacetResult>> Facets { get; internal set; }
 57
 58        /// <summary>
 59        /// Gets the first (server side) page of search result values.
 60        /// </summary>
 61        internal List<SearchResult<T>> Values { get; } = new List<SearchResult<T>>();
 62
 63        /// <summary>
 64        /// Gets or sets the fully constructed URI for the next page of
 65        /// results.
 66        /// </summary>
 67        internal Uri NextUri { get; set; }
 68
 69        /// <summary>
 70        /// Gets or sets the SearchOptions required to fetch the next page of
 71        /// results.
 72        /// </summary>
 73        internal SearchOptions NextOptions { get; set; }
 74
 75        /// <summary>
 76        /// Gets the raw Response that obtained these results from the service.
 77        /// This is only used when paging.
 78        /// </summary>
 79        internal Response RawResponse { get; set; }
 80
 81        /// <summary>
 82        /// The SearchClient used to fetch the next page of results.  This is
 83        /// only used when paging.
 84        /// </summary>
 85        private SearchClient _pagingClient;
 86
 87        /// <summary>
 88        /// Initializes a new instance of the SearchResults class.
 89        /// </summary>
 90        internal SearchResults() { }
 91
 92        /// <summary>
 93        /// Get all of the <see cref="SearchResult{T}"/>s synchronously.
 94        /// </summary>
 95        /// <returns>The search results.</returns>
 96        public Pageable<SearchResult<T>> GetResults() =>
 97            new SearchPageable<T>(this);
 98
 99        /// <summary>
 100        /// Get all of the <see cref="SearchResult{T}"/>s asynchronously.
 101        /// </summary>
 102        /// <returns>The search results.</returns>
 103        public AsyncPageable<SearchResult<T>> GetResultsAsync() =>
 104            new SearchAsyncPageable<T>(this);
 105
 106        /// <summary>
 107        /// Initialize the state needed to allow paging.
 108        /// </summary>
 109        /// <param name="client">
 110        /// The SearchClient to make requests.
 111        /// </param>
 112        /// <param name="rawResponse">
 113        /// The raw response that obtained these results.
 114        /// </param>
 115        internal void ConfigurePaging(SearchClient client, Response rawResponse)
 116        {
 117            Debug.Assert(client != null);
 118            Debug.Assert(rawResponse != null);
 119            _pagingClient = client;
 120            RawResponse = rawResponse;
 121        }
 122
 123        /// <summary>
 124        /// Get the next (server-side) page of results.
 125        /// </summary>
 126        /// <param name="async">
 127        /// Whether to execute synchronously or asynchronously.
 128        /// </param>
 129        /// <param name="cancellationToken">
 130        /// Optional <see cref="CancellationToken"/> to propagate notifications
 131        /// that the operation should be canceled.
 132        /// </param>
 133        /// <returns>The next page of SearchResults.</returns>
 134        internal async Task<SearchResults<T>> GetNextPageAsync(bool async, CancellationToken cancellationToken)
 135        {
 136            SearchResults<T> next = null;
 137            if (_pagingClient != null && NextOptions != null)
 138            {
 139                next = async ?
 140                    await _pagingClient.SearchAsync<T>(
 141                        NextOptions.SearchText,
 142                        NextOptions,
 143                        cancellationToken)
 144                        .ConfigureAwait(false) :
 145                    _pagingClient.Search<T>(
 146                        NextOptions.SearchText,
 147                        NextOptions,
 148                        cancellationToken);
 149            }
 150            return next;
 151        }
 152
 153        #pragma warning disable CS1572 // Not all parameters will be used depending on feature flags
 154        /// <summary>
 155        /// Deserialize the SearchResults.
 156        /// </summary>
 157        /// <param name="json">A JSON stream.</param>
 158        /// <param name="serializer">
 159        /// Optional serializer that can be used to customize the serialization
 160        /// of strongly typed models.
 161        /// </param>
 162        /// <param name="async">Whether to execute sync or async.</param>
 163        /// <param name="cancellationToken">
 164        /// Optional <see cref="CancellationToken"/> to propagate notifications
 165        /// that the operation should be canceled.
 166        /// </param>
 167        /// <returns>Deserialized SearchResults.</returns>
 168        internal static async Task<SearchResults<T>> DeserializeAsync(
 169            Stream json,
 170#if EXPERIMENTAL_SERIALIZER
 171            ObjectSerializer serializer,
 172#endif
 173            bool async,
 174            CancellationToken cancellationToken)
 175        #pragma warning restore CS1572
 176        {
 177            // Parse the JSON
 178            using JsonDocument doc = async ?
 179                await JsonDocument.ParseAsync(json, cancellationToken: cancellationToken).ConfigureAwait(false) :
 180                JsonDocument.Parse(json);
 181
 182            JsonSerializerOptions defaultSerializerOptions = JsonSerialization.SerializerOptions;
 183
 184            SearchResults<T> results = new SearchResults<T>();
 185            foreach (JsonProperty prop in doc.RootElement.EnumerateObject())
 186            {
 187                if (prop.NameEquals(Constants.ODataCountKeyJson.EncodedUtf8Bytes) &&
 188                    prop.Value.ValueKind != JsonValueKind.Null)
 189                {
 190                    results.TotalCount = prop.Value.GetInt64();
 191                }
 192                else if (prop.NameEquals(Constants.SearchCoverageKeyJson.EncodedUtf8Bytes) &&
 193                    prop.Value.ValueKind != JsonValueKind.Null)
 194                {
 195                    results.Coverage = prop.Value.GetDouble();
 196                }
 197                else if (prop.NameEquals(Constants.SearchFacetsKeyJson.EncodedUtf8Bytes))
 198                {
 199                    results.Facets = new Dictionary<string, IList<FacetResult>>();
 200                    foreach (JsonProperty facetObject in prop.Value.EnumerateObject())
 201                    {
 202                        // Get the values of the facet
 203                        List<FacetResult> facets = new List<FacetResult>();
 204                        foreach (JsonElement facetValue in facetObject.Value.EnumerateArray())
 205                        {
 206                            Dictionary<string, object> facetValues = new Dictionary<string, object>();
 207                            long? facetCount = null;
 208                            foreach (JsonProperty facetProperty in facetValue.EnumerateObject())
 209                            {
 210                                if (facetProperty.NameEquals(Constants.CountKeyJson.EncodedUtf8Bytes))
 211                                {
 212                                    if (facetProperty.Value.ValueKind != JsonValueKind.Null)
 213                                    {
 214                                        facetCount = facetProperty.Value.GetInt64();
 215                                    }
 216                                }
 217                                else
 218                                {
 219                                    object value = facetProperty.Value.GetSearchObject();
 220                                    facetValues[facetProperty.Name] = value;
 221                                }
 222                            }
 223                            facets.Add(new FacetResult(facetCount, facetValues));
 224                        }
 225                        // Add the facet to the results
 226                        results.Facets[facetObject.Name] = facets;
 227                    }
 228                }
 229                else if (prop.NameEquals(Constants.ODataNextLinkKeyJson.EncodedUtf8Bytes))
 230                {
 231                    results.NextUri = new Uri(prop.Value.GetString());
 232                }
 233                else if (prop.NameEquals(Constants.SearchNextPageKeyJson.EncodedUtf8Bytes))
 234                {
 235                    results.NextOptions = SearchOptions.DeserializeSearchOptions(prop.Value);
 236                }
 237                else if (prop.NameEquals(Constants.ValueKeyJson.EncodedUtf8Bytes))
 238                {
 239                    foreach (JsonElement element in prop.Value.EnumerateArray())
 240                    {
 241                        SearchResult<T> result = await SearchResult<T>.DeserializeAsync(
 242                            element,
 243#if EXPERIMENTAL_SERIALIZER
 244                            serializer,
 245#endif
 246                            defaultSerializerOptions,
 247                            async,
 248                            cancellationToken)
 249                            .ConfigureAwait(false);
 250                        results.Values.Add(result);
 251                    }
 252                }
 253            }
 254            return results;
 255        }
 256    }
 257
 258    /// <summary>
 259    /// A page of <see cref="SearchResult{T}"/>s returned from
 260    /// <see cref="SearchResults{T}.GetResultsAsync"/>'s
 261    /// <see cref="AsyncPageable{T}.AsPages(string, int?)"/> method.
 262    /// </summary>
 263    /// <typeparam name="T">
 264    /// The .NET type that maps to the index schema. Instances of this type can
 265    /// be retrieved as documents from the index.
 266    /// </typeparam>
 267    public class SearchResultsPage<T> : Page<SearchResult<T>>
 268    {
 269        private SearchResults<T> _results;
 270        private IReadOnlyList<SearchResult<T>> _values;
 271
 82272        internal SearchResultsPage(SearchResults<T> results)
 273        {
 274            Debug.Assert(results != null);
 82275            _results = results;
 82276        }
 277
 278        /// <summary>
 279        /// The total count of results found by the search operation, or null
 280        /// if the count was not requested via
 281        /// <see cref="SearchOptions.IncludeTotalCount"/>.  If present, the
 282        /// count may be greater than the number of results in this response.
 283        /// This can happen if you use the <see cref="SearchOptions.Size"/> or
 284        /// <see cref="SearchOptions.Skip"/> parameters, or if Azure Cognitive
 285        /// Search can't return all the requested documents in a single Search
 286        /// response.
 287        /// </summary>
 0288        public long? TotalCount => _results.TotalCount;
 289
 290        /// <summary>
 291        /// A value indicating the percentage of the index that was included in
 292        /// the query, or null if <see cref="SearchOptions.MinimumCoverage"/>
 293        /// was not specified in the request.
 294        /// </summary>
 0295        public double? Coverage => _results.Coverage;
 296
 297        /// <summary>
 298        /// The facet query results for the search operation, organized as a
 299        /// collection of buckets for each faceted field; null if the query did
 300        /// not include any facet expressions via
 301        /// <see cref="SearchOptions.Facets"/>.
 302        /// </summary>
 0303        public IDictionary<string, IList<FacetResult>> Facets => _results.Facets;
 304
 305        /// <inheritdoc />
 306        public override IReadOnlyList<SearchResult<T>> Values =>
 122307            _values ??= new ReadOnlyCollection<SearchResult<T>>(_results.Values);
 308
 309        /// <inheritdoc />
 310        public override string ContinuationToken =>
 34311            SearchContinuationToken.Serialize(_results.NextUri, _results.NextOptions);
 312
 313        /// <inheritdoc />
 0314        public override Response GetRawResponse() => _results.RawResponse;
 315    }
 316
 317    /// <summary>
 318    /// <see cref="AsyncPageable{T}"/> of <see cref="SearchResult{T}"/>s
 319    /// returned from <see cref="SearchResults{T}.GetResultsAsync"/> to
 320    /// enumerate all of the search results.
 321    /// </summary>
 322    /// <typeparam name="T">
 323    /// The .NET type that maps to the index schema. Instances of this type can
 324    /// be retrieved as documents from the index.
 325    /// </typeparam>
 326    internal class SearchAsyncPageable<T> : AsyncPageable<SearchResult<T>>
 327    {
 328        private SearchResults<T> _results;
 329        public SearchAsyncPageable(SearchResults<T> results)
 330        {
 331            Debug.Assert(results != null);
 332            _results = results;
 333        }
 334
 335        /// <inheritdoc />
 336        public override async IAsyncEnumerable<Page<SearchResult<T>>> AsPages(string continuationToken = default, int? p
 337        {
 338            // The first page of our results is always provided so we can
 339            // ignore the continuation token.  Users can only provide a token
 340            // directly to the Search method.
 341            Debug.Assert(continuationToken == null);
 342
 343            SearchResults<T> initial = _results;
 344            for (SearchResults<T> results = initial;
 345                 results != null;
 346                 results = await results.GetNextPageAsync(async: true, CancellationToken).ConfigureAwait(false))
 347            {
 348                yield return new SearchResultsPage<T>(results);
 349            }
 350        }
 351    }
 352
 353    /// <summary>
 354    /// <see cref="Pageable{T}"/> of <see cref="SearchResult{T}"/>s returned
 355    /// from <see cref="SearchResults{T}.GetResults"/> to enumerate all of the
 356    /// search results.
 357    /// </summary>
 358    /// <typeparam name="T">
 359    /// The .NET type that maps to the index schema. Instances of this type can
 360    /// be retrieved as documents from the index.
 361    /// </typeparam>
 362    internal class SearchPageable<T> : Pageable<SearchResult<T>>
 363    {
 364        private SearchResults<T> _results;
 365        public SearchPageable(SearchResults<T> results)
 366        {
 367            Debug.Assert(results != null);
 368            _results = results;
 369        }
 370
 371        /// <inheritdoc />
 372        public override IEnumerable<Page<SearchResult<T>>> AsPages(string continuationToken = default, int? pageSizeHint
 373        {
 374            // The first page of our results is always provided so we can
 375            // ignore the continuation token.  Users can only provide a token
 376            // directly to the Search method.
 377            Debug.Assert(continuationToken == null);
 378
 379            SearchResults<T> initial = _results;
 380            for (SearchResults<T> results = initial;
 381                 results != null;
 382                 results = results.GetNextPageAsync(async: false, CancellationToken).EnsureCompleted())
 383            {
 384                yield return new SearchResultsPage<T>(results);
 385            }
 386        }
 387    }
 388
 389    public static partial class SearchModelFactory
 390    {
 391        /// <summary> Initializes a new instance of SearchResults. </summary>
 392        /// <typeparam name="T">
 393        /// The .NET type that maps to the index schema. Instances of this type can
 394        /// be retrieved as documents from the index.
 395        /// </typeparam>
 396        /// <param name="values">The search result values.</param>
 397        /// <param name="totalCount">The total count of results found by the search operation.</param>
 398        /// <param name="facets">The facet query results for the search operation.</param>
 399        /// <param name="coverage">A value indicating the percentage of the index that was included in the query</param>
 400        /// <param name="rawResponse">The raw Response that obtained these results from the service.</param>
 401        /// <returns>A new SearchResults instance for mocking.</returns>
 402        public static SearchResults<T> SearchResults<T>(
 403            IEnumerable<SearchResult<T>> values,
 404            long? totalCount,
 405            IDictionary<string, IList<FacetResult>> facets,
 406            double? coverage,
 407            Response rawResponse)
 408        {
 409            var results = new SearchResults<T>()
 410            {
 411                TotalCount = totalCount,
 412                Coverage = coverage,
 413                Facets = facets,
 414                RawResponse = rawResponse
 415            };
 416            results.Values.AddRange(values);
 417            return results;
 418        }
 419
 420        /// <summary> Initializes a new instance of SearchResultsPage. </summary>
 421        /// <typeparam name="T">
 422        /// The .NET type that maps to the index schema. Instances of this type can
 423        /// be retrieved as documents from the index.
 424        /// </typeparam>
 425        /// <param name="results">The search results for this page.</param>
 426        /// <returns>A new SearchResultsPage instance for mocking.</returns>
 427        public static SearchResultsPage<T> SearchResultsPage<T>(
 428            SearchResults<T> results) =>
 429            new SearchResultsPage<T>(results);
 430    }
 431}