Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,86 @@ public static IEnumerable<SynonymHit> BrowseSynonyms(this SearchClient client, s
AsyncHelper.RunSync(() => client.BrowseSynonymsAsync(indexName, synonymsParams, requestOptions));


/// <summary>
/// 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.
/// </summary>
/// <param name="client">Search client</param>
/// <param name="requests">A list of search requests to be executed.</param>
/// <param name="searchStrategy">The search strategy to be employed during the search. (optional)</param>
/// <param name="options">Add extra http header or query parameters to Algolia.</param>
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <exception cref="ArgumentException">Thrown when arguments are not correct</exception>
/// <exception cref="Algolia.Search.Exceptions.AlgoliaApiException">Thrown when the API call was rejected by Algolia</exception>
/// <exception cref="Algolia.Search.Exceptions.AlgoliaUnreachableHostException">Thrown when the client failed to call the endpoint</exception>
/// <returns>Task of List{SearchResponse{T}}</returns>
public static async Task<List<SearchResponse<T>>> SearchForHitsAsync<T>(this SearchClient client,
IEnumerable<SearchForHits> 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<T>(searchMethod, options, cancellationToken);
return searchResponses.Results.Where(x => x.IsSearchResponse()).Select(x => x.AsSearchResponse()).ToList();
}

/// <summary>
/// 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)
/// </summary>
/// <param name="client">Search client</param>
/// <param name="requests">A list of search requests to be executed.</param>
/// <param name="searchStrategy">The search strategy to be employed during the search. (optional)</param>
/// <param name="options">Add extra http header or query parameters to Algolia.</param>
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <exception cref="ArgumentException">Thrown when arguments are not correct</exception>
/// <exception cref="Algolia.Search.Exceptions.AlgoliaApiException">Thrown when the API call was rejected by Algolia</exception>
/// <exception cref="Algolia.Search.Exceptions.AlgoliaUnreachableHostException">Thrown when the client failed to call the endpoint</exception>
/// <returns>Task of List{SearchResponse{T}}</returns>
public static List<SearchResponse<T>> SearchForHits<T>(this SearchClient client,
IEnumerable<SearchForHits> requests, SearchStrategy? searchStrategy, RequestOptions options = null,
CancellationToken cancellationToken = default) =>
AsyncHelper.RunSync(() =>
client.SearchForHitsAsync<T>(requests, searchStrategy, options, cancellationToken));


/// <summary>
/// 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.
/// </summary>
/// <param name="client">Search client</param>
/// <param name="requests">A list of search requests to be executed.</param>
/// <param name="searchStrategy">The search strategy to be employed during the search. (optional)</param>
/// <param name="options">Add extra http header or query parameters to Algolia.</param>
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <exception cref="ArgumentException">Thrown when arguments are not correct</exception>
/// <exception cref="Algolia.Search.Exceptions.AlgoliaApiException">Thrown when the API call was rejected by Algolia</exception>
/// <exception cref="Algolia.Search.Exceptions.AlgoliaUnreachableHostException">Thrown when the client failed to call the endpoint</exception>
/// <returns>Task of List{SearchResponse{T}}</returns>
public static async Task<List<SearchForFacetValuesResponse>> SearchForFacetsAsync(this SearchClient client,
IEnumerable<SearchForFacets> 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<object>(searchMethod, options, cancellationToken);
return searchResponses.Results.Where(x => x.IsSearchForFacetValuesResponse()).Select(x => x.AsSearchForFacetValuesResponse()).ToList();
}

/// <summary>
/// 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.
/// </summary>
/// <param name="client">Search client</param>
/// <param name="requests">A list of search requests to be executed.</param>
/// <param name="searchStrategy">The search strategy to be employed during the search. (optional)</param>
/// <param name="options">Add extra http header or query parameters to Algolia.</param>
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <exception cref="ArgumentException">Thrown when arguments are not correct</exception>
/// <exception cref="Algolia.Search.Exceptions.AlgoliaApiException">Thrown when the API call was rejected by Algolia</exception>
/// <exception cref="Algolia.Search.Exceptions.AlgoliaUnreachableHostException">Thrown when the client failed to call the endpoint</exception>
/// <returns>Task of List{SearchResponse{T}}</returns>
public static List<SearchForFacetValuesResponse> SearchForFacets(this SearchClient client,
IEnumerable<SearchForFacets> requests, SearchStrategy? searchStrategy, RequestOptions options = null,
CancellationToken cancellationToken = default) =>
AsyncHelper.RunSync(() => client.SearchForFacetsAsync(requests, searchStrategy, options, cancellationToken));

private static async Task<T> RetryUntil<T>(Func<Task<T>> func, Func<T, bool> validate,
int maxRetries = DefaultMaxRetries, CancellationToken ct = default)
{
Expand All @@ -266,6 +346,7 @@ private static async Task<T> RetryUntil<T>(Func<Task<T>> func, Func<T, bool> 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);
Expand Down
95 changes: 95 additions & 0 deletions tests/output/csharp/src/ClientExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -469,4 +470,98 @@ public async Task ShouldBrowseRules()

Assert.Equal(6, browseSynonymsAsync.Count());
}

[Fact]
public async Task ShouldSearchForHits()
{
var httpMock = new Mock<IHttpRequester>();
httpMock
.Setup(c =>
c.SendRequestAsync(
It.Is<Request>(r => r.Uri.AbsolutePath.EndsWith("/1/indexes/*/queries")),
It.IsAny<TimeSpan>(),
It.IsAny<TimeSpan>(),
It.IsAny<CancellationToken>()
)
).Returns(
Task.FromResult(
new AlgoliaHttpResponse
{
HttpStatusCode = 200,
Body = new MemoryStream(
Encoding.UTF8.GetBytes(
JsonConvert.SerializeObject(
new SearchResponses<object>([
new(new SearchForFacetValuesResponse(){ FacetHits = new List<FacetHits>()}),
new(new SearchResponse<object> { Hits = [new { ObjectID = "12345" }] }),
new(new SearchResponse<object> { Hits = [new { ObjectID = "678910" }] })
])
)
)
)
}
)
);

var client = new SearchClient(new SearchConfig("test-app-id", "test-api-key"), httpMock.Object);

var hits = await client.SearchForHitsAsync<Hit>(
new List<SearchForHits>
{
new()
{
IndexName = "my-test-index",
Query = " "
}
}, SearchStrategy.None);

Assert.Equal(2, hits.Count);
}

[Fact]
public async Task ShouldSearchForFacets()
{
var httpMock = new Mock<IHttpRequester>();
httpMock
.Setup(c =>
c.SendRequestAsync(
It.Is<Request>(r => r.Uri.AbsolutePath.EndsWith("/1/indexes/*/queries")),
It.IsAny<TimeSpan>(),
It.IsAny<TimeSpan>(),
It.IsAny<CancellationToken>()
)
).Returns(
Task.FromResult(
new AlgoliaHttpResponse
{
HttpStatusCode = 200,
Body = new MemoryStream(
Encoding.UTF8.GetBytes(
JsonConvert.SerializeObject(
new SearchResponses<object>([
new(new SearchForFacetValuesResponse(){ FacetHits = [] }),
new(new SearchResponse<object> { Hits = [new { ObjectID = "12345" }] }),
new(new SearchResponse<object> { 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<SearchForFacets>
{
new()
{
IndexName = "my-test-index",
Query = " "
}
}, SearchStrategy.None);

Assert.Equal(1, hits.Count);
}
}