| | 1 | | // Copyright (c) Microsoft Corporation. All rights reserved. |
| | 2 | | // Licensed under the MIT License. |
| | 3 | |
|
| | 4 | | using System; |
| | 5 | | using System.IO; |
| | 6 | | using System.Text.Json; |
| | 7 | | using Azure.Core; |
| | 8 | |
|
| | 9 | | namespace Azure.Search.Documents.Models |
| | 10 | | { |
| | 11 | | /// <summary> |
| | 12 | | /// Creates continuation tokens used for resuming a search that requires |
| | 13 | | /// multiple requests to return the user's desired data. This is only to |
| | 14 | | /// support server-side paging. Client-side paging should be handled with |
| | 15 | | /// Skip and Size. |
| | 16 | | /// </summary> |
| | 17 | | internal static class SearchContinuationToken |
| | 18 | | { |
| | 19 | | private const string ApiVersionName = "apiVersion"; |
| 1 | 20 | | private static readonly JsonEncodedText s_apiVersionEncodedName = JsonEncodedText.Encode(ApiVersionName); |
| | 21 | |
|
| | 22 | | private const string NextLinkName = "nextLink"; |
| 1 | 23 | | private static readonly JsonEncodedText s_nextLinkEncodedName = JsonEncodedText.Encode(NextLinkName); |
| | 24 | |
|
| | 25 | | private const string NextPageParametersName = "nextPageParameters"; |
| 1 | 26 | | private static readonly JsonEncodedText s_nextPageParametersEncodedName = JsonEncodedText.Encode(NextPageParamet |
| | 27 | |
|
| | 28 | | /// <summary> |
| | 29 | | /// Creates a durable, opaque continuation token that can be provided |
| | 30 | | /// to users. |
| | 31 | | /// </summary> |
| | 32 | | /// <param name="nextPageUri"> |
| | 33 | | /// URI of additional results when making GET requests. |
| | 34 | | /// </param> |
| | 35 | | /// <param name="nextPageOptions"> |
| | 36 | | /// <see cref="SearchOptions"/> for additional results when making POST |
| | 37 | | /// requests. |
| | 38 | | /// </param> |
| | 39 | | /// <returns>A continuation token.</returns> |
| | 40 | | public static string Serialize(Uri nextPageUri, SearchOptions nextPageOptions) |
| | 41 | | { |
| | 42 | | // There's no continuation token if there's no next page |
| 34 | 43 | | if (nextPageUri == null || nextPageOptions == null) |
| | 44 | | { |
| 6 | 45 | | return null; |
| | 46 | | } |
| 28 | 47 | | using MemoryStream buffer = new MemoryStream(); |
| 28 | 48 | | using (Utf8JsonWriter writer = new Utf8JsonWriter(buffer)) |
| | 49 | | { |
| 28 | 50 | | writer.WriteStartObject(); |
| 28 | 51 | | writer.WriteString(s_apiVersionEncodedName, SearchClientOptions.ContinuationTokenVersion.ToVersionString |
| 28 | 52 | | writer.WriteString(s_nextLinkEncodedName, nextPageUri.ToString()); |
| 28 | 53 | | writer.WritePropertyName(s_nextPageParametersEncodedName); |
| 28 | 54 | | writer.WriteObjectValue(nextPageOptions); |
| 28 | 55 | | writer.WriteEndObject(); |
| 28 | 56 | | } |
| 28 | 57 | | return Convert.ToBase64String(buffer.ToArray()); |
| 28 | 58 | | } |
| | 59 | |
|
| | 60 | | /// <summary> |
| | 61 | | /// Parses a continuation token and returns the corresponding to the |
| | 62 | | /// next page's <see cref="SearchOptions"/>. |
| | 63 | | /// </summary> |
| | 64 | | /// <param name="continuationToken"> |
| | 65 | | /// The serialized continuation token. |
| | 66 | | /// </param> |
| | 67 | | /// <returns>The continuation token's next page options.</returns> |
| | 68 | | public static SearchOptions Deserialize(string continuationToken) |
| | 69 | | { |
| 14 | 70 | | Argument.AssertNotNullOrEmpty(continuationToken, nameof(continuationToken)); |
| 14 | 71 | | byte[] decoded = Convert.FromBase64String(continuationToken); |
| | 72 | | try |
| | 73 | | { |
| 14 | 74 | | using JsonDocument json = JsonDocument.Parse(decoded); |
| 14 | 75 | | if (json.RootElement.ValueKind == JsonValueKind.Object && |
| 14 | 76 | | json.RootElement.TryGetProperty(ApiVersionName, out JsonElement apiVersion) && |
| 14 | 77 | | apiVersion.ValueKind == JsonValueKind.String && |
| 14 | 78 | | // Today we only validate against a single known version, |
| 14 | 79 | | // but in the future we may want to support a range of |
| 14 | 80 | | // valid continuation token serialization formats. This |
| 14 | 81 | | // will need to be updated accordingly. |
| 14 | 82 | | string.Equals( |
| 14 | 83 | | apiVersion.GetString(), |
| 14 | 84 | | SearchClientOptions.ContinuationTokenVersion.ToVersionString(), |
| 14 | 85 | | StringComparison.OrdinalIgnoreCase) && |
| 14 | 86 | | json.RootElement.TryGetProperty(NextPageParametersName, out JsonElement nextPageParams) && |
| 14 | 87 | | nextPageParams.ValueKind == JsonValueKind.Object) |
| | 88 | | { |
| | 89 | | // We only use the nextPageParameters because we do all of |
| | 90 | | // our searching via HTTP POST requests |
| 14 | 91 | | return SearchOptions.DeserializeSearchOptions(nextPageParams); |
| | 92 | | } |
| 0 | 93 | | } |
| 0 | 94 | | catch (JsonException) |
| | 95 | | { |
| 0 | 96 | | } |
| | 97 | |
|
| 0 | 98 | | throw new ArgumentException("Invalid continuation token", nameof(continuationToken)); |
| 14 | 99 | | } |
| | 100 | | } |
| | 101 | | } |