-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
87626b4
commit 2400c35
Showing
10 changed files
with
977 additions
and
24 deletions.
There are no files selected for viewing
51 changes: 51 additions & 0 deletions
51
clients/algoliasearch-client-csharp/algoliasearch/Utils/ApiKeyEquals.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Algolia.Search.Models.Search | ||
{ | ||
public partial class ApiKey | ||
{ | ||
/// <summary> | ||
/// Compare two ApiKey objects | ||
/// </summary> | ||
/// <param name="obj"></param> | ||
/// <returns></returns> | ||
public override bool Equals(object obj) | ||
{ | ||
// We compare the properties of the object | ||
// We DO NOT compare the null props of the obj. | ||
if (obj is ApiKey other) | ||
{ | ||
return | ||
Description == other.Description && | ||
QueryParameters == other.QueryParameters && | ||
CheckSequence(Acl, other.Acl) && | ||
CheckSequence(Indexes, other.Indexes) && | ||
CheckSequence(Referers, other.Referers) && | ||
CheckNullable(MaxHitsPerQuery, other.MaxHitsPerQuery) && | ||
CheckNullable(MaxQueriesPerIPPerHour, other.MaxQueriesPerIPPerHour) && | ||
CheckNullable(Validity, other.Validity); | ||
} | ||
|
||
return base.Equals(obj); | ||
} | ||
|
||
private bool CheckNullable<T>(T objProps, T otherProps) | ||
{ | ||
// if other is null, we don't compare the property | ||
if (otherProps == null) | ||
return true; | ||
|
||
return objProps != null && objProps.Equals(otherProps); | ||
} | ||
|
||
private bool CheckSequence<T>(List<T> objProps, List<T> otherProps) | ||
{ | ||
// if other is null, we don't compare the property | ||
if (otherProps == null) | ||
return true; | ||
|
||
return objProps != null && objProps.SequenceEqual(otherProps); | ||
} | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
clients/algoliasearch-client-csharp/algoliasearch/Utils/ApiKeyOperation.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
namespace Algolia.Search.Utils | ||
{ | ||
/// <summary> | ||
/// ApiKey operations | ||
/// </summary> | ||
public enum ApiKeyOperation | ||
{ | ||
/// <summary> | ||
/// Add a new ApiKey | ||
/// </summary> | ||
Add, | ||
/// <summary> | ||
/// Delete an existing ApiKey | ||
/// </summary> | ||
Delete, | ||
/// <summary> | ||
/// Update an existing ApiKey | ||
/// </summary> | ||
Update, | ||
} | ||
} |
221 changes: 221 additions & 0 deletions
221
clients/algoliasearch-client-csharp/algoliasearch/Utils/Helpers.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,221 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Algolia.Search.Clients; | ||
using Algolia.Search.Exceptions; | ||
using Algolia.Search.Http; | ||
using Algolia.Search.Models.Search; | ||
using TaskStatus = Algolia.Search.Models.Search.TaskStatus; | ||
|
||
namespace Algolia.Search.Utils | ||
{ | ||
/// <summary> | ||
/// A tool class to help with common tasks | ||
/// </summary> | ||
public static class Helpers | ||
{ | ||
private const int DefaultMaxRetries = 50; | ||
|
||
/// <summary> | ||
/// Wait for a task to complete with `indexName` and `taskID`. | ||
/// </summary> | ||
/// <param name="client">Algolia Search Client instance</param> | ||
/// <param name="indexName">The `indexName` where the operation was performed.</param> | ||
/// <param name="taskId">The `taskID` returned in the method response.</param> | ||
/// <param name="maxRetries">The maximum number of retry. 50 by default. (optional)</param> | ||
/// <param name="requestOptions">The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional)</param> | ||
/// <param name="ct">Cancellation token (optional)</param> | ||
public static async Task<GetTaskResponse> WaitForTaskAsync(this SearchClient client, string indexName, long taskId, | ||
int maxRetries = DefaultMaxRetries, RequestOptions requestOptions = null, CancellationToken ct = default) | ||
{ | ||
return await RetryUntil( | ||
async () => await client.GetTaskAsync(indexName, taskId, requestOptions, ct), | ||
resp => resp.Status == TaskStatus.Published, maxRetries, ct).ConfigureAwait(false); | ||
} | ||
|
||
/// <summary> | ||
/// Helper method that waits for an API key task to be processed. | ||
/// </summary> | ||
/// <param name="client">Algolia Search Client instance</param> | ||
/// <param name="operation">The `operation` that was done on a `key`.</param> | ||
/// <param name="key">The key that has been added, deleted or updated.</param> | ||
/// <param name="apiKey">Necessary to know if an `update` operation has been processed, compare fields of the response with it. (optional - mandatory if operation is UPDATE)</param> | ||
/// <param name="maxRetries">The maximum number of retry. 50 by default. (optional)</param> | ||
/// <param name="requestOptions">The requestOptions to send along with the query, they will be merged with the transporter requestOptions. (optional)</param> | ||
/// <param name="ct">Cancellation token (optional)</param> | ||
public static async Task<GetApiKeyResponse> WaitForApiKeyAsync(this SearchClient client, | ||
ApiKeyOperation operation, string key, | ||
ApiKey apiKey = default, int maxRetries = DefaultMaxRetries, RequestOptions requestOptions = null, | ||
CancellationToken ct = default) | ||
{ | ||
if (operation == ApiKeyOperation.Update) | ||
{ | ||
if (apiKey == null) | ||
{ | ||
throw new AlgoliaException("`ApiKey` is required when waiting for an `update` operation."); | ||
} | ||
|
||
return await RetryUntil(() => client.GetApiKeyAsync(key, requestOptions, ct), | ||
resp => | ||
{ | ||
var apiKeyResponse = new ApiKey | ||
{ | ||
Acl = resp.Acl, | ||
Description = resp.Description, | ||
Indexes = resp.Indexes, | ||
Referers = resp.Referers, | ||
Validity = resp.Validity, | ||
QueryParameters = resp.QueryParameters, | ||
MaxHitsPerQuery = resp.MaxHitsPerQuery, | ||
MaxQueriesPerIPPerHour = resp.MaxQueriesPerIPPerHour | ||
}; | ||
return apiKeyResponse.Equals(apiKey); | ||
}, maxRetries: maxRetries, ct: ct).ConfigureAwait(false); | ||
} | ||
|
||
var addedKey = new GetApiKeyResponse(); | ||
|
||
// check the status of the getApiKey method | ||
await RetryUntil(async () => | ||
{ | ||
try | ||
{ | ||
addedKey = await client.GetApiKeyAsync(key, requestOptions, ct).ConfigureAwait(false); | ||
// magic number to signify we found the key | ||
return -2; | ||
} | ||
catch (AlgoliaApiException e) | ||
{ | ||
return e.HttpErrorCode; | ||
} | ||
}, (status) => | ||
{ | ||
return operation switch | ||
{ | ||
ApiKeyOperation.Add => | ||
// stop either when the key is created or when we don't receive 404 | ||
status is -2 or not 404 and not 0, | ||
ApiKeyOperation.Delete => | ||
// stop when the key is not found | ||
status == 404, | ||
_ => false | ||
}; | ||
}, | ||
maxRetries, ct | ||
); | ||
return addedKey; | ||
} | ||
|
||
/// <summary> | ||
/// Iterate on the `browse` method of the client to allow aggregating objects of an index. | ||
/// </summary> | ||
/// <param name="client"></param> | ||
/// <param name="indexName">The index in which to perform the request.</param> | ||
/// <param name="browseParams">The `browse` parameters.</param> | ||
/// <param name="requestOptions">The requestOptions to send along with the query, they will be forwarded to the `browse` method and merged with the transporter requestOptions.</param> | ||
/// <typeparam name="T">The model of the record</typeparam> | ||
public static async Task<IEnumerable<T>> BrowseObjectsAsync<T>(this SearchClient client, string indexName, | ||
BrowseParamsObject browseParams, | ||
RequestOptions requestOptions = null) | ||
{ | ||
browseParams.HitsPerPage = 1000; | ||
var all = await CreateIterable<BrowseResponse<T>>(async prevResp => | ||
{ | ||
browseParams.Cursor = prevResp?.Cursor; | ||
return await client.BrowseAsync<T>(indexName, new BrowseParams(browseParams), requestOptions); | ||
}, resp => resp is { Cursor: null }).ConfigureAwait(false); | ||
|
||
return all.SelectMany(u => u.Hits); | ||
} | ||
|
||
/// <summary> | ||
/// Iterate on the `SearchRules` method of the client to allow aggregating rules of an index. | ||
/// </summary> | ||
/// <param name="client"></param> | ||
/// <param name="indexName">The index in which to perform the request.</param> | ||
/// <param name="searchRulesParams">The `SearchRules` parameters</param> | ||
/// <param name="requestOptions">The requestOptions to send along with the query, they will be forwarded to the `searchRules` method and merged with the transporter requestOptions.</param> | ||
public static async Task<IEnumerable<Rule>> BrowseRulesAsync(this SearchClient client, string indexName, | ||
SearchRulesParams searchRulesParams, | ||
RequestOptions requestOptions = null) | ||
{ | ||
const int hitsPerPage = 1000; | ||
searchRulesParams.HitsPerPage = hitsPerPage; | ||
|
||
var all = await CreateIterable<Tuple<SearchRulesResponse, int>>(async (prevResp) => | ||
{ | ||
var page = prevResp?.Item2 ?? 0; | ||
var searchSynonymsResponse = await client.SearchRulesAsync(indexName, searchRulesParams, requestOptions); | ||
return new Tuple<SearchRulesResponse, int>(searchSynonymsResponse, page + 1); | ||
}, resp => resp?.Item1 is { NbHits: < hitsPerPage }).ConfigureAwait(false); | ||
|
||
return all.SelectMany(u => u.Item1.Hits); | ||
} | ||
|
||
|
||
/// <summary> | ||
/// Iterate on the `SearchSynonyms` method of the client to allow aggregating rules of an index. | ||
/// </summary> | ||
/// <param name="client"></param> | ||
/// <param name="indexName">The index in which to perform the request.</param> | ||
/// <param name="synonymsParams">The `SearchSynonyms` parameters.</param> | ||
/// <param name="requestOptions">The requestOptions to send along with the query, they will be forwarded to the `searchSynonyms` method and merged with the transporter requestOptions.</param> | ||
public static async Task<IEnumerable<SynonymHit>> BrowseSynonymsAsync(this SearchClient client, string indexName, | ||
SearchSynonymsParams synonymsParams, | ||
RequestOptions requestOptions = null) | ||
{ | ||
const int hitsPerPage = 1000; | ||
synonymsParams.HitsPerPage = hitsPerPage; | ||
var all = await CreateIterable<Tuple<SearchSynonymsResponse, int>>(async (prevResp) => | ||
{ | ||
synonymsParams.Page = prevResp?.Item2 ?? 0; | ||
var searchSynonymsResponse = await client.SearchSynonymsAsync(indexName, synonymsParams, requestOptions); | ||
return new Tuple<SearchSynonymsResponse, int>(searchSynonymsResponse, (prevResp?.Item2 ?? 0) + 1); | ||
}, resp => resp?.Item1 is { NbHits: < hitsPerPage }).ConfigureAwait(false); | ||
|
||
return all.SelectMany(u => u.Item1.Hits); | ||
} | ||
|
||
private static async Task<T> RetryUntil<T>(Func<Task<T>> func, Func<T, bool> validate, | ||
int maxRetries = DefaultMaxRetries, CancellationToken ct = default) | ||
{ | ||
var retryCount = 0; | ||
while (retryCount < maxRetries) | ||
{ | ||
var resp = await func().ConfigureAwait(false); | ||
if (validate(resp)) | ||
{ | ||
return resp; | ||
} | ||
|
||
await Task.Delay(NextDelay(retryCount), ct).ConfigureAwait(false); | ||
retryCount++; | ||
} | ||
|
||
throw new AlgoliaException( | ||
"The maximum number of retries exceeded. (" + (retryCount + 1) + "/" + maxRetries + ")"); | ||
} | ||
|
||
private static int NextDelay(int retryCount) | ||
{ | ||
return Math.Min(retryCount * 200, 5000); | ||
} | ||
|
||
private static async Task<List<TU>> CreateIterable<TU>(Func<TU, Task<TU>> executeQuery, | ||
Func<TU, bool> stopCondition) | ||
{ | ||
var responses = new List<TU>(); | ||
var current = default(TU); | ||
do | ||
{ | ||
var response = await executeQuery(current).ConfigureAwait(false); | ||
current = response; | ||
responses.Add(response); | ||
} while (!stopCondition(current)); | ||
|
||
return responses; | ||
} | ||
} | ||
} |
30 changes: 30 additions & 0 deletions
30
clients/algoliasearch-client-csharp/algoliasearch/Utils/ModelConverters.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
using Algolia.Search.Models.Search; | ||
|
||
namespace Algolia.Search.Utils | ||
{ | ||
/// <summary> | ||
/// A tool class to help model conversion | ||
/// </summary> | ||
public static class ModelConverters | ||
{ | ||
/// <summary> | ||
/// Convert a GetApiKeyResponse to an ApiKey | ||
/// </summary> | ||
/// <param name="apiKey"></param> | ||
/// <returns></returns> | ||
public static ApiKey ToApiKey(this GetApiKeyResponse apiKey) | ||
{ | ||
return new ApiKey | ||
{ | ||
Acl = apiKey.Acl, | ||
Description = apiKey.Description, | ||
Indexes = apiKey.Indexes, | ||
Referers = apiKey.Referers, | ||
Validity = apiKey.Validity, | ||
QueryParameters = apiKey.QueryParameters, | ||
MaxHitsPerQuery = apiKey.MaxHitsPerQuery, | ||
MaxQueriesPerIPPerHour = apiKey.MaxQueriesPerIPPerHour | ||
}; | ||
} | ||
} | ||
} |
Oops, something went wrong.