diff --git a/clients/algoliasearch-client-csharp/algoliasearch/Utils/ClientExtensions.cs b/clients/algoliasearch-client-csharp/algoliasearch/Utils/ClientExtensions.cs index dd2c4ae6b31..8e136c69c2f 100644 --- a/clients/algoliasearch-client-csharp/algoliasearch/Utils/ClientExtensions.cs +++ b/clients/algoliasearch-client-csharp/algoliasearch/Utils/ClientExtensions.cs @@ -247,6 +247,86 @@ public static IEnumerable BrowseSynonyms(this SearchClient client, s AsyncHelper.RunSync(() => client.BrowseSynonymsAsync(indexName, synonymsParams, requestOptions)); + /// + /// Executes a synchronous search for the provided search requests, with certainty that we will only request Algolia records (hits). Results will be received in the same order as the queries. + /// + /// Search client + /// A list of search requests to be executed. + /// The search strategy to be employed during the search. (optional) + /// Add extra http header or query parameters to Algolia. + /// Cancellation Token to cancel the request. + /// Thrown when arguments are not correct + /// Thrown when the API call was rejected by Algolia + /// Thrown when the client failed to call the endpoint + /// Task of List{SearchResponse{T}} + public static async Task>> SearchForHitsAsync(this SearchClient client, + IEnumerable requests, SearchStrategy? searchStrategy, RequestOptions options = null, + CancellationToken cancellationToken = default) + { + var queries = requests.Select(t => new SearchQuery(t)).ToList(); + var searchMethod = new SearchMethodParams(queries) { Strategy = searchStrategy }; + var searchResponses = await client.SearchAsync(searchMethod, options, cancellationToken); + return searchResponses.Results.Where(x => x.IsSearchResponse()).Select(x => x.AsSearchResponse()).ToList(); + } + + /// + /// Executes a synchronous search for the provided search requests, with certainty that we will only request Algolia records (hits). Results will be received in the same order as the queries. (Synchronous version) + /// + /// Search client + /// A list of search requests to be executed. + /// The search strategy to be employed during the search. (optional) + /// Add extra http header or query parameters to Algolia. + /// Cancellation Token to cancel the request. + /// Thrown when arguments are not correct + /// Thrown when the API call was rejected by Algolia + /// Thrown when the client failed to call the endpoint + /// Task of List{SearchResponse{T}} + public static List> SearchForHits(this SearchClient client, + IEnumerable requests, SearchStrategy? searchStrategy, RequestOptions options = null, + CancellationToken cancellationToken = default) => + AsyncHelper.RunSync(() => + client.SearchForHitsAsync(requests, searchStrategy, options, cancellationToken)); + + + /// + /// Executes a synchronous search for the provided search requests, with certainty that we will only request Algolia facets. Results will be received in the same order as the queries. + /// + /// Search client + /// A list of search requests to be executed. + /// The search strategy to be employed during the search. (optional) + /// Add extra http header or query parameters to Algolia. + /// Cancellation Token to cancel the request. + /// Thrown when arguments are not correct + /// Thrown when the API call was rejected by Algolia + /// Thrown when the client failed to call the endpoint + /// Task of List{SearchResponse{T}} + public static async Task> SearchForFacetsAsync(this SearchClient client, + IEnumerable requests, SearchStrategy? searchStrategy, RequestOptions options = null, + CancellationToken cancellationToken = default) + { + var queries = requests.Select(t => new SearchQuery(t)).ToList(); + var searchMethod = new SearchMethodParams(queries) { Strategy = searchStrategy }; + var searchResponses = await client.SearchAsync(searchMethod, options, cancellationToken); + return searchResponses.Results.Where(x => x.IsSearchForFacetValuesResponse()).Select(x => x.AsSearchForFacetValuesResponse()).ToList(); + } + + /// + /// Executes a synchronous search for the provided search requests, with certainty that we will only request Algolia facets. Results will be received in the same order as the queries. + /// + /// Search client + /// A list of search requests to be executed. + /// The search strategy to be employed during the search. (optional) + /// Add extra http header or query parameters to Algolia. + /// Cancellation Token to cancel the request. + /// Thrown when arguments are not correct + /// Thrown when the API call was rejected by Algolia + /// Thrown when the client failed to call the endpoint + /// Task of List{SearchResponse{T}} + public static List SearchForFacets(this SearchClient client, + IEnumerable requests, SearchStrategy? searchStrategy, RequestOptions options = null, + CancellationToken cancellationToken = default) => + AsyncHelper.RunSync(() => client.SearchForFacetsAsync(requests, searchStrategy, options, cancellationToken)); + private static async Task RetryUntil(Func> func, Func validate, int maxRetries = DefaultMaxRetries, CancellationToken ct = default) { @@ -266,6 +346,7 @@ private static async Task RetryUntil(Func> func, Func val throw new AlgoliaException( "The maximum number of retries exceeded. (" + (retryCount + 1) + "/" + maxRetries + ")"); } + private static int NextDelay(int retryCount) { return Math.Min(retryCount * 200, 5000); diff --git a/tests/output/csharp/src/ClientExtensionsTests.cs b/tests/output/csharp/src/ClientExtensionsTests.cs index ac3fbd4e951..5827504e3fb 100644 --- a/tests/output/csharp/src/ClientExtensionsTests.cs +++ b/tests/output/csharp/src/ClientExtensionsTests.cs @@ -5,6 +5,7 @@ using Algolia.Search.Models.Search; using Algolia.Search.Utils; using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Moq; using Newtonsoft.Json; using Xunit; @@ -469,4 +470,98 @@ public async Task ShouldBrowseRules() Assert.Equal(6, browseSynonymsAsync.Count()); } + + [Fact] + public async Task ShouldSearchForHits() + { + var httpMock = new Mock(); + httpMock + .Setup(c => + c.SendRequestAsync( + It.Is(r => r.Uri.AbsolutePath.EndsWith("/1/indexes/*/queries")), + It.IsAny(), + It.IsAny(), + It.IsAny() + ) + ).Returns( + Task.FromResult( + new AlgoliaHttpResponse + { + HttpStatusCode = 200, + Body = new MemoryStream( + Encoding.UTF8.GetBytes( + JsonConvert.SerializeObject( + new SearchResponses([ + new(new SearchForFacetValuesResponse(){ FacetHits = new List()}), + new(new SearchResponse { Hits = [new { ObjectID = "12345" }] }), + new(new SearchResponse { Hits = [new { ObjectID = "678910" }] }) + ]) + ) + ) + ) + } + ) + ); + + var client = new SearchClient(new SearchConfig("test-app-id", "test-api-key"), httpMock.Object); + + var hits = await client.SearchForHitsAsync( + new List + { + new() + { + IndexName = "my-test-index", + Query = " " + } + }, SearchStrategy.None); + + Assert.Equal(2, hits.Count); + } + + [Fact] + public async Task ShouldSearchForFacets() + { + var httpMock = new Mock(); + httpMock + .Setup(c => + c.SendRequestAsync( + It.Is(r => r.Uri.AbsolutePath.EndsWith("/1/indexes/*/queries")), + It.IsAny(), + It.IsAny(), + It.IsAny() + ) + ).Returns( + Task.FromResult( + new AlgoliaHttpResponse + { + HttpStatusCode = 200, + Body = new MemoryStream( + Encoding.UTF8.GetBytes( + JsonConvert.SerializeObject( + new SearchResponses([ + new(new SearchForFacetValuesResponse(){ FacetHits = [] }), + new(new SearchResponse { Hits = [new { ObjectID = "12345" }] }), + new(new SearchResponse { Hits = [new { ObjectID = "678910" }] }) + ]) + ) + ) + ) + } + ) + ); + + var client = new SearchClient(new SearchConfig("test-app-id", "test-api-key"), httpMock.Object); + + var hits = await client.SearchForFacetsAsync( + new List + { + new() + { + IndexName = "my-test-index", + Query = " " + } + }, SearchStrategy.None); + + Assert.Equal(1, hits.Count); + } }