From 957361f2e07621620d1a7ed2a5dd653a9e90d23e Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 10:20:13 +0000 Subject: [PATCH 1/2] SDK regeneration --- .../Actions/ActionsClient.cs | 2 +- .../Actions/Modules/ModulesClient.cs | 4 +- .../Modules/Versions/VersionsClient.cs | 2 +- .../Triggers/Bindings/BindingsClient.cs | 2 +- .../Actions/Versions/VersionsClient.cs | 2 +- .../ClientGrants/ClientGrantsClient.cs | 2 +- .../Organizations/OrganizationsClient.cs | 2 +- .../Clients/ClientsClient.cs | 2 +- .../Clients/Connections/ConnectionsClient.cs | 2 +- .../ConnectionProfilesClient.cs | 2 +- .../Connections/Clients/ClientsClient.cs | 2 +- .../Connections/ConnectionsClient.cs | 2 +- .../DirectoryProvisioningClient.cs | 70 ++++-- .../IDirectoryProvisioningClient.cs | 2 +- .../IScimConfigurationClient.cs | 2 +- .../ScimConfigurationClient.cs | 6 +- src/Auth0.ManagementApi/Core/Page.cs | 33 +++ src/Auth0.ManagementApi/Core/Pager.cs | 54 ++-- .../Core/QueryStringBuilder.cs | 231 ++++++++++++++++-- src/Auth0.ManagementApi/Core/ValueConvert.cs | 3 +- .../DeviceCredentialsClient.cs | 2 +- .../EventStreams/EventStreamsClient.cs | 2 +- .../Flows/Executions/ExecutionsClient.cs | 2 +- src/Auth0.ManagementApi/Flows/FlowsClient.cs | 2 +- .../Vault/Connections/ConnectionsClient.cs | 2 +- src/Auth0.ManagementApi/Forms/FormsClient.cs | 2 +- .../Groups/GroupsClient.cs | 2 +- .../Groups/Members/MembersClient.cs | 2 +- src/Auth0.ManagementApi/Hooks/HooksClient.cs | 2 +- .../Keys/Encryption/EncryptionClient.cs | 2 +- src/Auth0.ManagementApi/Logs/LogsClient.cs | 2 +- .../NetworkAcls/NetworkAclsClient.cs | 2 +- .../ClientGrants/ClientGrantsClient.cs | 2 +- .../Connections/ConnectionsClient.cs | 2 +- .../DiscoveryDomainsClient.cs | 2 +- .../EnabledConnectionsClient.cs | 2 +- .../Invitations/InvitationsClient.cs | 2 +- .../Organizations/Members/MembersClient.cs | 2 +- .../Members/Roles/RolesClient.cs | 2 +- .../Organizations/OrganizationsClient.cs | 2 +- .../Prompts/Rendering/RenderingClient.cs | 2 +- .../RefreshTokens/RefreshTokensClient.cs | 2 +- .../ResourceServers/ResourceServersClient.cs | 2 +- .../Roles/Permissions/PermissionsClient.cs | 2 +- src/Auth0.ManagementApi/Roles/RolesClient.cs | 2 +- .../Roles/Users/UsersClient.cs | 2 +- src/Auth0.ManagementApi/Rules/RulesClient.cs | 2 +- .../SelfServiceProfilesClient.cs | 2 +- .../TokenExchangeProfilesClient.cs | 2 +- .../UserAttributeProfilesClient.cs | 2 +- .../UserGrants/UserGrantsClient.cs | 2 +- .../AuthenticationMethodsClient.cs | 2 +- .../ConnectedAccountsClient.cs | 2 +- .../Users/Groups/GroupsClient.cs | 2 +- .../Users/Logs/LogsClient.cs | 2 +- .../Organizations/OrganizationsClient.cs | 2 +- .../Users/Permissions/PermissionsClient.cs | 2 +- .../Users/RefreshToken/RefreshTokenClient.cs | 2 +- .../Users/Roles/RolesClient.cs | 2 +- .../Users/Sessions/SessionsClient.cs | 2 +- src/Auth0.ManagementApi/Users/UsersClient.cs | 2 +- .../Verification/Templates/TemplatesClient.cs | 2 +- .../Core/Pagination/GuidCursorTest.cs | 15 +- .../Core/Pagination/HasNextPageOffsetTest.cs | 15 +- .../Core/Pagination/IntOffsetTest.cs | 15 +- .../Core/Pagination/LongOffsetTest.cs | 15 +- .../Core/Pagination/NoRequestCursorTest.cs | 15 +- .../Core/Pagination/NoRequestOffsetTest.cs | 15 +- .../Core/Pagination/StepOffsetTest.cs | 15 +- .../Core/Pagination/StringCursorTest.cs | 17 +- .../Core/QueryStringBuilderTests.cs | 102 +++++++- .../RawClientTests/QueryParameterTests.cs | 2 +- .../Utils/JsonElementComparer.cs | 11 +- 73 files changed, 605 insertions(+), 143 deletions(-) diff --git a/src/Auth0.ManagementApi/Actions/ActionsClient.cs b/src/Auth0.ManagementApi/Actions/ActionsClient.cs index 73b05499e..d3fadd131 100644 --- a/src/Auth0.ManagementApi/Actions/ActionsClient.cs +++ b/src/Auth0.ManagementApi/Actions/ActionsClient.cs @@ -614,7 +614,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Actions/Modules/ModulesClient.cs b/src/Auth0.ManagementApi/Actions/Modules/ModulesClient.cs index 24dc501d7..2466bed81 100644 --- a/src/Auth0.ManagementApi/Actions/Modules/ModulesClient.cs +++ b/src/Auth0.ManagementApi/Actions/Modules/ModulesClient.cs @@ -631,7 +631,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { @@ -803,7 +803,7 @@ public async Task> ListActionsAsync( options, async (request, options, cancellationToken) => await ListActionsInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Actions/Modules/Versions/VersionsClient.cs b/src/Auth0.ManagementApi/Actions/Modules/Versions/VersionsClient.cs index 5caf575ef..26ea8dc10 100644 --- a/src/Auth0.ManagementApi/Actions/Modules/Versions/VersionsClient.cs +++ b/src/Auth0.ManagementApi/Actions/Modules/Versions/VersionsClient.cs @@ -343,7 +343,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Actions/Triggers/Bindings/BindingsClient.cs b/src/Auth0.ManagementApi/Actions/Triggers/Bindings/BindingsClient.cs index 95c1d1848..265825167 100644 --- a/src/Auth0.ManagementApi/Actions/Triggers/Bindings/BindingsClient.cs +++ b/src/Auth0.ManagementApi/Actions/Triggers/Bindings/BindingsClient.cs @@ -247,7 +247,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(triggerId, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Actions/Versions/VersionsClient.cs b/src/Auth0.ManagementApi/Actions/Versions/VersionsClient.cs index 53bb9a005..7fafdbe90 100644 --- a/src/Auth0.ManagementApi/Actions/Versions/VersionsClient.cs +++ b/src/Auth0.ManagementApi/Actions/Versions/VersionsClient.cs @@ -341,7 +341,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(actionId, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/ClientGrants/ClientGrantsClient.cs b/src/Auth0.ManagementApi/ClientGrants/ClientGrantsClient.cs index 48999c3f8..8fadfa5e7 100644 --- a/src/Auth0.ManagementApi/ClientGrants/ClientGrantsClient.cs +++ b/src/Auth0.ManagementApi/ClientGrants/ClientGrantsClient.cs @@ -436,7 +436,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/ClientGrants/Organizations/OrganizationsClient.cs b/src/Auth0.ManagementApi/ClientGrants/Organizations/OrganizationsClient.cs index e09f9db6d..425fa52f5 100644 --- a/src/Auth0.ManagementApi/ClientGrants/Organizations/OrganizationsClient.cs +++ b/src/Auth0.ManagementApi/ClientGrants/Organizations/OrganizationsClient.cs @@ -152,7 +152,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Clients/ClientsClient.cs b/src/Auth0.ManagementApi/Clients/ClientsClient.cs index c43784fc6..738de61d4 100644 --- a/src/Auth0.ManagementApi/Clients/ClientsClient.cs +++ b/src/Auth0.ManagementApi/Clients/ClientsClient.cs @@ -795,7 +795,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Clients/Connections/ConnectionsClient.cs b/src/Auth0.ManagementApi/Clients/Connections/ConnectionsClient.cs index e0bd4f071..84b3c23aa 100644 --- a/src/Auth0.ManagementApi/Clients/Connections/ConnectionsClient.cs +++ b/src/Auth0.ManagementApi/Clients/Connections/ConnectionsClient.cs @@ -185,7 +185,7 @@ public async Task> GetAsync( options, async (request, options, cancellationToken) => await GetInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/ConnectionProfiles/ConnectionProfilesClient.cs b/src/Auth0.ManagementApi/ConnectionProfiles/ConnectionProfilesClient.cs index 4ba283547..131596f88 100644 --- a/src/Auth0.ManagementApi/ConnectionProfiles/ConnectionProfilesClient.cs +++ b/src/Auth0.ManagementApi/ConnectionProfiles/ConnectionProfilesClient.cs @@ -593,7 +593,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Connections/Clients/ClientsClient.cs b/src/Auth0.ManagementApi/Connections/Clients/ClientsClient.cs index 2133316ee..293ae4a34 100644 --- a/src/Auth0.ManagementApi/Connections/Clients/ClientsClient.cs +++ b/src/Auth0.ManagementApi/Connections/Clients/ClientsClient.cs @@ -164,7 +164,7 @@ public async Task> GetAsync( options, async (request, options, cancellationToken) => await GetInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Connections/ConnectionsClient.cs b/src/Auth0.ManagementApi/Connections/ConnectionsClient.cs index e69715b61..9dfc126da 100644 --- a/src/Auth0.ManagementApi/Connections/ConnectionsClient.cs +++ b/src/Auth0.ManagementApi/Connections/ConnectionsClient.cs @@ -493,7 +493,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Connections/DirectoryProvisioning/DirectoryProvisioningClient.cs b/src/Auth0.ManagementApi/Connections/DirectoryProvisioning/DirectoryProvisioningClient.cs index f05b71622..436490796 100644 --- a/src/Auth0.ManagementApi/Connections/DirectoryProvisioning/DirectoryProvisioningClient.cs +++ b/src/Auth0.ManagementApi/Connections/DirectoryProvisioning/DirectoryProvisioningClient.cs @@ -1,13 +1,13 @@ -using System.Text.Json; using Auth0.ManagementApi; using Auth0.ManagementApi.Connections.DirectoryProvisioning; using Auth0.ManagementApi.Core; +using global::System.Text.Json; namespace Auth0.ManagementApi.Connections; public partial class DirectoryProvisioningClient : IDirectoryProvisioningClient { - private RawClient _client; + private readonly RawClient _client; internal DirectoryProvisioningClient(RawClient client) { @@ -54,7 +54,6 @@ private async Task< .SendRequestAsync( new JsonRequest { - BaseUrl = _client.Options.BaseUrl, Method = HttpMethod.Get, Path = "connections-directory-provisionings", QueryString = _queryString, @@ -66,7 +65,9 @@ private async Task< .ConfigureAwait(false); if (response.StatusCode is >= 200 and < 400) { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { var responseData = JsonUtils.Deserialize( @@ -88,13 +89,15 @@ private async Task< throw new ManagementApiException( "Failed to deserialize response", response.StatusCode, - responseBody, + null, e ); } } { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { switch (response.StatusCode) @@ -137,7 +140,6 @@ private async Task> Get .SendRequestAsync( new JsonRequest { - BaseUrl = _client.Options.BaseUrl, Method = HttpMethod.Get, Path = string.Format( "connections/{0}/directory-provisioning", @@ -151,7 +153,9 @@ private async Task> Get .ConfigureAwait(false); if (response.StatusCode is >= 200 and < 400) { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { var responseData = JsonUtils.Deserialize( @@ -173,13 +177,15 @@ private async Task> Get throw new ManagementApiException( "Failed to deserialize response", response.StatusCode, - responseBody, + null, e ); } } { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { switch (response.StatusCode) @@ -225,7 +231,6 @@ private async Task> .SendRequestAsync( new JsonRequest { - BaseUrl = _client.Options.BaseUrl, Method = HttpMethod.Post, Path = string.Format( "connections/{0}/directory-provisioning", @@ -241,7 +246,9 @@ private async Task> .ConfigureAwait(false); if (response.StatusCode is >= 200 and < 400) { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { var responseData = @@ -264,13 +271,15 @@ private async Task> throw new ManagementApiException( "Failed to deserialize response", response.StatusCode, - responseBody, + null, e ); } } { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { switch (response.StatusCode) @@ -318,7 +327,6 @@ private async Task> .SendRequestAsync( new JsonRequest { - BaseUrl = _client.Options.BaseUrl, Method = HttpMethodExtensions.Patch, Path = string.Format( "connections/{0}/directory-provisioning", @@ -334,7 +342,9 @@ private async Task> .ConfigureAwait(false); if (response.StatusCode is >= 200 and < 400) { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { var responseData = @@ -357,13 +367,15 @@ private async Task> throw new ManagementApiException( "Failed to deserialize response", response.StatusCode, - responseBody, + null, e ); } } { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { switch (response.StatusCode) @@ -410,7 +422,6 @@ private async Task< .SendRequestAsync( new JsonRequest { - BaseUrl = _client.Options.BaseUrl, Method = HttpMethod.Get, Path = string.Format( "connections/{0}/directory-provisioning/default-mapping", @@ -424,7 +435,9 @@ private async Task< .ConfigureAwait(false); if (response.StatusCode is >= 200 and < 400) { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { var responseData = @@ -447,13 +460,15 @@ private async Task< throw new ManagementApiException( "Failed to deserialize response", response.StatusCode, - responseBody, + null, e ); } } { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { switch (response.StatusCode) @@ -490,7 +505,7 @@ private async Task< /// new ListDirectoryProvisioningsRequestParameters { From = "from", Take = 1 } /// ); /// - public async Task> ListAsync( + public async Task> ListAsync( ListDirectoryProvisioningsRequestParameters request, RequestOptions? options = null, CancellationToken cancellationToken = default @@ -505,13 +520,13 @@ private async Task< RequestOptions?, ListDirectoryProvisioningsResponseContent, string?, - global::Auth0.ManagementApi.DirectoryProvisioning + Auth0.ManagementApi.DirectoryProvisioning > .CreateInstanceAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; @@ -584,7 +599,6 @@ public async Task DeleteAsync( .SendRequestAsync( new JsonRequest { - BaseUrl = _client.Options.BaseUrl, Method = HttpMethod.Delete, Path = string.Format( "connections/{0}/directory-provisioning", @@ -601,7 +615,9 @@ public async Task DeleteAsync( return; } { - var responseBody = await response.Raw.Content.ReadAsStringAsync(); + var responseBody = await response + .Raw.Content.ReadAsStringAsync(cancellationToken) + .ConfigureAwait(false); try { switch (response.StatusCode) diff --git a/src/Auth0.ManagementApi/Connections/DirectoryProvisioning/IDirectoryProvisioningClient.cs b/src/Auth0.ManagementApi/Connections/DirectoryProvisioning/IDirectoryProvisioningClient.cs index 0220d2624..886026ca8 100644 --- a/src/Auth0.ManagementApi/Connections/DirectoryProvisioning/IDirectoryProvisioningClient.cs +++ b/src/Auth0.ManagementApi/Connections/DirectoryProvisioning/IDirectoryProvisioningClient.cs @@ -11,7 +11,7 @@ public partial interface IDirectoryProvisioningClient /// /// Retrieve a list of directory provisioning configurations of a tenant. /// - Task> ListAsync( + Task> ListAsync( ListDirectoryProvisioningsRequestParameters request, RequestOptions? options = null, CancellationToken cancellationToken = default diff --git a/src/Auth0.ManagementApi/Connections/ScimConfiguration/IScimConfigurationClient.cs b/src/Auth0.ManagementApi/Connections/ScimConfiguration/IScimConfigurationClient.cs index 1e9bc66b1..00236de17 100644 --- a/src/Auth0.ManagementApi/Connections/ScimConfiguration/IScimConfigurationClient.cs +++ b/src/Auth0.ManagementApi/Connections/ScimConfiguration/IScimConfigurationClient.cs @@ -11,7 +11,7 @@ public partial interface IScimConfigurationClient /// /// Retrieve a list of SCIM configurations of a tenant. /// - Task> ListAsync( + Task> ListAsync( ListScimConfigurationsRequestParameters request, RequestOptions? options = null, CancellationToken cancellationToken = default diff --git a/src/Auth0.ManagementApi/Connections/ScimConfiguration/ScimConfigurationClient.cs b/src/Auth0.ManagementApi/Connections/ScimConfiguration/ScimConfigurationClient.cs index af7d133ce..4ea062cbb 100644 --- a/src/Auth0.ManagementApi/Connections/ScimConfiguration/ScimConfigurationClient.cs +++ b/src/Auth0.ManagementApi/Connections/ScimConfiguration/ScimConfigurationClient.cs @@ -477,7 +477,7 @@ private async Task< /// new ListScimConfigurationsRequestParameters { From = "from", Take = 1 } /// ); /// - public async Task> ListAsync( + public async Task> ListAsync( ListScimConfigurationsRequestParameters request, RequestOptions? options = null, CancellationToken cancellationToken = default @@ -492,13 +492,13 @@ private async Task< RequestOptions?, ListScimConfigurationsResponseContent, string?, - ManagementApi.ScimConfiguration + Auth0.ManagementApi.ScimConfiguration > .CreateInstanceAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Core/Page.cs b/src/Auth0.ManagementApi/Core/Page.cs index 8196db8f1..09f6c8360 100644 --- a/src/Auth0.ManagementApi/Core/Page.cs +++ b/src/Auth0.ManagementApi/Core/Page.cs @@ -1,5 +1,6 @@ using global::System.Collections; using global::System.Collections.ObjectModel; +using global::System.Net; namespace Auth0.ManagementApi.Core; @@ -19,11 +20,43 @@ public Page(IReadOnlyList items) Items = items; } + /// + /// Creates a new with the specified items and response metadata. + /// + public Page( + IReadOnlyList items, + object? response, + HttpStatusCode statusCode, + ResponseHeaders? headers + ) + { + Items = items; + Response = response; + StatusCode = statusCode; + Headers = headers; + } + /// /// Gets the items in this . /// public IReadOnlyList Items { get; } + /// + /// The full API response object for this page. Cast to the endpoint's response + /// type to access fields beyond the paginated items (e.g., TotalCount, metadata). + /// + public object? Response { get; } + + /// + /// The HTTP status code of the response that produced this page. + /// + public HttpStatusCode StatusCode { get; } + + /// + /// The HTTP response headers from the response that produced this page. + /// + public ResponseHeaders? Headers { get; } + /// /// Enumerate the items in this . /// diff --git a/src/Auth0.ManagementApi/Core/Pager.cs b/src/Auth0.ManagementApi/Core/Pager.cs index c32a0f14e..cee94f7ee 100644 --- a/src/Auth0.ManagementApi/Core/Pager.cs +++ b/src/Auth0.ManagementApi/Core/Pager.cs @@ -95,7 +95,7 @@ internal sealed class OffsetPager SendRequest( + internal delegate global::System.Threading.Tasks.Task> SendRequest( TRequest request, TRequestOptions? options, CancellationToken cancellationToken @@ -115,7 +115,7 @@ private delegate ( TRequest? nextRequest, bool hasNextPage, Page page - ) ParseApiCallDelegate(TRequest request, TResponse response); + ) ParseApiCallDelegate(TRequest request, WithRawResponse wrappedResponse); public Page CurrentPage { get; private set; } public bool HasNextPage { get; private set; } @@ -152,7 +152,8 @@ bool hasNextPage ) { request ??= Activator.CreateInstance(); - var response = await sendRequest(request, options, cancellationToken).ConfigureAwait(false); + var wrappedResponse = await sendRequest(request, options, cancellationToken) + .ConfigureAwait(false); var parseApiCall = CreateParseApiCallDelegate( getItems, getOffset, @@ -160,7 +161,7 @@ bool hasNextPage getStep, getHasNextPage ); - var (nextRequest, hasNextPage, page) = parseApiCall(request, response); + var (nextRequest, hasNextPage, page) = parseApiCall(request, wrappedResponse); return new OffsetPager( nextRequest, options, @@ -179,10 +180,10 @@ private static ParseApiCallDelegate CreateParseApiCallDelegate( GetHasNextPage? getHasNextPage ) { - return (request, response) => + return (request, wrappedResponse) => ParseApiCall( request, - response, + wrappedResponse, getItems, getOffset, setOffset, @@ -197,9 +198,9 @@ private static ParseApiCallDelegate CreateParseApiCallDelegate( CancellationToken cancellationToken = default ) { - var response = await _sendRequest(request, options, cancellationToken) + var wrappedResponse = await _sendRequest(request, options, cancellationToken) .ConfigureAwait(false); - var (nextRequest, hasNextPageFlag, page) = _parseApiCall(request, response); + var (nextRequest, hasNextPageFlag, page) = _parseApiCall(request, wrappedResponse); _request = nextRequest; HasNextPage = hasNextPageFlag; CurrentPage = page; @@ -208,7 +209,7 @@ private static ParseApiCallDelegate CreateParseApiCallDelegate( private static (TRequest? nextRequest, bool hasNextPage, Page page) ParseApiCall( TRequest request, - TResponse response, + WithRawResponse wrappedResponse, GetItems getItems, GetOffset getOffset, SetOffset setOffset, @@ -216,8 +217,16 @@ private static (TRequest? nextRequest, bool hasNextPage, Page page) Parse GetHasNextPage? getHasNextPage ) { + var response = wrappedResponse.Data; var items = getItems(response); - var page = items is not null ? new Page(items) : Page.Empty; + var page = items is not null + ? new Page( + items, + response, + wrappedResponse.RawResponse.StatusCode, + wrappedResponse.RawResponse.Headers + ) + : Page.Empty; var offset = getOffset(request); var hasNextPage = getHasNextPage?.Invoke(response) ?? items?.Count > 0; if (!hasNextPage) @@ -360,7 +369,7 @@ internal sealed class CursorPager /// Delegate for sending a request. /// - internal delegate global::System.Threading.Tasks.Task SendRequest( + internal delegate global::System.Threading.Tasks.Task> SendRequest( TRequest request, TRequestOptions? options, CancellationToken cancellationToken @@ -385,7 +394,7 @@ private delegate ( TRequest? nextRequest, bool hasNextPage, Page page - ) ParseApiCallDelegate(TRequest request, TResponse response); + ) ParseApiCallDelegate(TRequest request, WithRawResponse wrappedResponse); /// /// The current . @@ -430,9 +439,10 @@ bool hasNextPage ) { request ??= Activator.CreateInstance(); - var response = await sendRequest(request, options, cancellationToken).ConfigureAwait(false); + var wrappedResponse = await sendRequest(request, options, cancellationToken) + .ConfigureAwait(false); var parseApiCall = CreateParseApiCallDelegate(getItems, getNextCursor, setCursor); - var (nextRequest, hasNextPage, page) = parseApiCall(request, response); + var (nextRequest, hasNextPage, page) = parseApiCall(request, wrappedResponse); return new CursorPager( nextRequest, options, @@ -449,10 +459,18 @@ private static ParseApiCallDelegate CreateParseApiCallDelegate( SetCursor setCursor ) { - return (request, response) => + return (request, wrappedResponse) => { + var response = wrappedResponse.Data; var items = getItems(response); - var page = items is not null ? new Page(items) : Page.Empty; + var page = items is not null + ? new Page( + items, + response, + wrappedResponse.RawResponse.StatusCode, + wrappedResponse.RawResponse.Headers + ) + : Page.Empty; var cursor = getNextCursor(response); var hasNextPage = cursor switch { @@ -475,9 +493,9 @@ SetCursor setCursor CancellationToken cancellationToken = default ) { - var response = await _sendRequest(request, options, cancellationToken) + var wrappedResponse = await _sendRequest(request, options, cancellationToken) .ConfigureAwait(false); - var (nextRequest, hasNextPage, page) = _parseApiCall(request, response); + var (nextRequest, hasNextPage, page) = _parseApiCall(request, wrappedResponse); _request = nextRequest; HasNextPage = hasNextPage; CurrentPage = page; diff --git a/src/Auth0.ManagementApi/Core/QueryStringBuilder.cs b/src/Auth0.ManagementApi/Core/QueryStringBuilder.cs index b1357d678..0057c09d8 100644 --- a/src/Auth0.ManagementApi/Core/QueryStringBuilder.cs +++ b/src/Auth0.ManagementApi/Core/QueryStringBuilder.cs @@ -7,18 +7,51 @@ namespace Auth0.ManagementApi.Core; /// -/// High-performance query string builder with cross-platform optimizations. +/// High-performance query string builder with RFC 3986 compliant percent-encoding. /// Uses span-based APIs on .NET 6+ and StringBuilder fallback for older targets. +/// +/// RFC 3986 defines the following relevant productions: +/// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" +/// query = *( pchar / "/" / "?" ) +/// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" +/// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" +/// +/// Three encoding contexts are distinguished: +/// Path segment (pchar): unreserved + sub-delims + ":" + "@" +/// Query key: query chars minus "&", "=", "+", "#" +/// Query value: query chars minus "&", "+", "#" /// internal static class QueryStringBuilder { + // ────────────────────────────────────────────────────────────────────── + // RFC 3986 character sets + // + // Query key safe: unreserved + (sub-delims \ {& = +}) + : @ / ? + // Query value safe: unreserved + (sub-delims \ {& +}) + : @ / ? + // Path segment safe: unreserved + sub-delims + : @ + // ────────────────────────────────────────────────────────────────────── + #if NET8_0_OR_GREATER - private static readonly SearchValues UnreservedChars = SearchValues.Create( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~" + private static readonly SearchValues SafeQueryKeyChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?" + ); + + private static readonly SearchValues SafeQueryValueChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?" + ); + + private static readonly SearchValues SafePathChars = SearchValues.Create( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@" ); #else - private const string UnreservedChars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"; + private const string SafeQueryKeyChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;:@/?"; + + private const string SafeQueryValueChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$'()*,;=:@/?"; + + private const string SafePathChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~!$&'()*+,;=:@"; #endif #if NET7_0_OR_GREATER @@ -45,6 +78,43 @@ internal static class QueryStringBuilder }; #endif + private enum EncodingContext + { + QueryKey, + QueryValue, + Path, + } + + /// + /// Percent-encodes a path segment value per RFC 3986 section 3.3 (pchar). + /// Allowed unencoded: unreserved / sub-delims / ":" / "@" + /// + public static string EncodePathSegment(string value) + { + if (string.IsNullOrEmpty(value)) + return value; + +#if NET6_0_OR_GREATER + if (!NeedsEncoding(value.AsSpan(), EncodingContext.Path)) + return value; + + var buffer = ArrayPool.Shared.Rent(value.Length * 3); + try + { + var written = EncodeSlow(value.AsSpan(), buffer.AsSpan(), EncodingContext.Path); + return new string(buffer.AsSpan(0, written)); + } + finally + { + ArrayPool.Shared.Return(buffer); + } +#else + var sb = new StringBuilder(value.Length); + AppendEncoded(sb, value, EncodingContext.Path); + return sb.ToString(); +#endif + } + /// /// Builds a query string from the provided parameters. /// @@ -135,9 +205,17 @@ public static string Build(IEnumerable> parameters) buffer[position++] = first ? '?' : '&'; first = false; - position += EncodeComponent(kvp.Key.AsSpan(), buffer.AsSpan(position)); + position += EncodeWithCharSet( + kvp.Key.AsSpan(), + buffer.AsSpan(position), + EncodingContext.QueryKey + ); buffer[position++] = '='; - position += EncodeComponent(kvp.Value.AsSpan(), buffer.AsSpan(position)); + position += EncodeWithCharSet( + kvp.Value.AsSpan(), + buffer.AsSpan(position), + EncodingContext.QueryValue + ); } while (enumerator.MoveNext()); return first ? string.Empty : new string(buffer.AsSpan(0, position)); @@ -156,9 +234,9 @@ public static string Build(IEnumerable> parameters) sb.Append(first ? '?' : '&'); first = false; - AppendEncoded(sb, kvp.Key); + AppendEncoded(sb, kvp.Key, EncodingContext.QueryKey); sb.Append('='); - AppendEncoded(sb, kvp.Value); + AppendEncoded(sb, kvp.Value, EncodingContext.QueryValue); } return sb.ToString(); @@ -179,39 +257,61 @@ Span buffer buffer[position++] = first ? '?' : '&'; first = false; - position += EncodeComponent(kvp.Key.AsSpan(), buffer.Slice(position)); + position += EncodeWithCharSet( + kvp.Key.AsSpan(), + buffer.Slice(position), + EncodingContext.QueryKey + ); buffer[position++] = '='; - position += EncodeComponent(kvp.Value.AsSpan(), buffer.Slice(position)); + position += EncodeWithCharSet( + kvp.Value.AsSpan(), + buffer.Slice(position), + EncodingContext.QueryValue + ); } return position; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int EncodeComponent(ReadOnlySpan input, Span output) + private static int EncodeWithCharSet( + ReadOnlySpan input, + Span output, + EncodingContext context + ) { - if (!NeedsEncoding(input)) + if (!NeedsEncoding(input, context)) { input.CopyTo(output); return input.Length; } - return EncodeSlow(input, output); + return EncodeSlow(input, output, context); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool NeedsEncoding(ReadOnlySpan value) + private static bool NeedsEncoding(ReadOnlySpan value, EncodingContext context) { - return value.ContainsAnyExcept(UnreservedChars); + return context switch + { + EncodingContext.QueryKey => value.ContainsAnyExcept(SafeQueryKeyChars), + EncodingContext.QueryValue => value.ContainsAnyExcept(SafeQueryValueChars), + EncodingContext.Path => value.ContainsAnyExcept(SafePathChars), + _ => true, + }; } - private static int EncodeSlow(ReadOnlySpan input, Span output) + private static int EncodeSlow( + ReadOnlySpan input, + Span output, + EncodingContext context + ) { var position = 0; foreach (var c in input) { - if (IsUnreserved(c)) + if (IsSafeChar(c, context)) { output[position++] = c; } @@ -261,11 +361,11 @@ private static int EncodeUtf8(char c, Span output) } #else // netstandard2.0 / net462 StringBuilder-based encoding - private static void AppendEncoded(StringBuilder sb, string value) + private static void AppendEncoded(StringBuilder sb, string value, EncodingContext context) { foreach (var c in value) { - if (IsUnreserved(c)) + if (IsSafeChar(c, context)) { sb.Append(c); } @@ -298,18 +398,103 @@ private static void AppendPercentEncoded(StringBuilder sb, byte value) #endif [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool IsUnreserved(char c) + private static bool IsSafeChar(char c, EncodingContext context) + { + return context switch + { + EncodingContext.QueryKey => IsSafeQueryKeyChar(c), + EncodingContext.QueryValue => IsSafeQueryValueChar(c), + EncodingContext.Path => IsSafePathChar(c), + _ => false, + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeQueryKeyChar(char c) + { +#if NET8_0_OR_GREATER + return SafeQueryKeyChars.Contains(c); +#else + // query = *( pchar / "/" / "?" ) minus "&", "=", "+", "#" + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '-' + || c == '_' + || c == '.' + || c == '~' + || c == '!' + || c == '$' + || c == (char)39 // single quote + || c == '(' + || c == ')' + || c == '*' + || c == ',' + || c == ';' + || c == ':' + || c == '@' + || c == '/' + || c == '?'; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafeQueryValueChar(char c) + { +#if NET8_0_OR_GREATER + return SafeQueryValueChars.Contains(c); +#else + // query = *( pchar / "/" / "?" ) minus "&", "+", "#" + return (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z') + || (c >= '0' && c <= '9') + || c == '-' + || c == '_' + || c == '.' + || c == '~' + || c == '!' + || c == '$' + || c == (char)39 // single quote + || c == '(' + || c == ')' + || c == '*' + || c == ',' + || c == ';' + || c == '=' + || c == ':' + || c == '@' + || c == '/' + || c == '?'; +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool IsSafePathChar(char c) { #if NET8_0_OR_GREATER - return UnreservedChars.Contains(c); + return SafePathChars.Contains(c); #else + // pchar = unreserved / sub-delims / ":" / "@" return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' - || c == '~'; + || c == '~' + || c == '!' + || c == '$' + || c == '&' + || c == (char)39 // single quote + || c == '(' + || c == ')' + || c == '*' + || c == '+' + || c == ',' + || c == ';' + || c == '=' + || c == ':' + || c == '@'; #endif } diff --git a/src/Auth0.ManagementApi/Core/ValueConvert.cs b/src/Auth0.ManagementApi/Core/ValueConvert.cs index 0f5f0d913..4236ad2e4 100644 --- a/src/Auth0.ManagementApi/Core/ValueConvert.cs +++ b/src/Auth0.ManagementApi/Core/ValueConvert.cs @@ -29,7 +29,8 @@ public static class ValueConvert internal static string ToPathParameterString(ulong v) => ToString(v); - internal static string ToPathParameterString(string v) => ToString(v); + internal static string ToPathParameterString(string v) => + QueryStringBuilder.EncodePathSegment(v); internal static string ToPathParameterString(char v) => ToString(v); diff --git a/src/Auth0.ManagementApi/DeviceCredentials/DeviceCredentialsClient.cs b/src/Auth0.ManagementApi/DeviceCredentials/DeviceCredentialsClient.cs index bd4a60496..b54544ac9 100644 --- a/src/Auth0.ManagementApi/DeviceCredentials/DeviceCredentialsClient.cs +++ b/src/Auth0.ManagementApi/DeviceCredentials/DeviceCredentialsClient.cs @@ -261,7 +261,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/EventStreams/EventStreamsClient.cs b/src/Auth0.ManagementApi/EventStreams/EventStreamsClient.cs index c60a11462..4e6ab1beb 100644 --- a/src/Auth0.ManagementApi/EventStreams/EventStreamsClient.cs +++ b/src/Auth0.ManagementApi/EventStreams/EventStreamsClient.cs @@ -504,7 +504,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Flows/Executions/ExecutionsClient.cs b/src/Auth0.ManagementApi/Flows/Executions/ExecutionsClient.cs index 77410eaa9..c151fb840 100644 --- a/src/Auth0.ManagementApi/Flows/Executions/ExecutionsClient.cs +++ b/src/Auth0.ManagementApi/Flows/Executions/ExecutionsClient.cs @@ -248,7 +248,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(flowId, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Flows/FlowsClient.cs b/src/Auth0.ManagementApi/Flows/FlowsClient.cs index f01a3eb31..4fdeac550 100644 --- a/src/Auth0.ManagementApi/Flows/FlowsClient.cs +++ b/src/Auth0.ManagementApi/Flows/FlowsClient.cs @@ -421,7 +421,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Flows/Vault/Connections/ConnectionsClient.cs b/src/Auth0.ManagementApi/Flows/Vault/Connections/ConnectionsClient.cs index e97c13e2c..e9fbb9336 100644 --- a/src/Auth0.ManagementApi/Flows/Vault/Connections/ConnectionsClient.cs +++ b/src/Auth0.ManagementApi/Flows/Vault/Connections/ConnectionsClient.cs @@ -421,7 +421,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Forms/FormsClient.cs b/src/Auth0.ManagementApi/Forms/FormsClient.cs index b19fcf1bd..2165397ce 100644 --- a/src/Auth0.ManagementApi/Forms/FormsClient.cs +++ b/src/Auth0.ManagementApi/Forms/FormsClient.cs @@ -410,7 +410,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Groups/GroupsClient.cs b/src/Auth0.ManagementApi/Groups/GroupsClient.cs index 08bb0d9c1..8bf67a853 100644 --- a/src/Auth0.ManagementApi/Groups/GroupsClient.cs +++ b/src/Auth0.ManagementApi/Groups/GroupsClient.cs @@ -254,7 +254,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Groups/Members/MembersClient.cs b/src/Auth0.ManagementApi/Groups/Members/MembersClient.cs index c800308e7..2775d87c7 100644 --- a/src/Auth0.ManagementApi/Groups/Members/MembersClient.cs +++ b/src/Auth0.ManagementApi/Groups/Members/MembersClient.cs @@ -166,7 +166,7 @@ public async Task> GetAsync( options, async (request, options, cancellationToken) => await GetInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Hooks/HooksClient.cs b/src/Auth0.ManagementApi/Hooks/HooksClient.cs index b2fb96ebd..47a394839 100644 --- a/src/Auth0.ManagementApi/Hooks/HooksClient.cs +++ b/src/Auth0.ManagementApi/Hooks/HooksClient.cs @@ -435,7 +435,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Keys/Encryption/EncryptionClient.cs b/src/Auth0.ManagementApi/Keys/Encryption/EncryptionClient.cs index 9eb0947e2..53268fd99 100644 --- a/src/Auth0.ManagementApi/Keys/Encryption/EncryptionClient.cs +++ b/src/Auth0.ManagementApi/Keys/Encryption/EncryptionClient.cs @@ -522,7 +522,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Logs/LogsClient.cs b/src/Auth0.ManagementApi/Logs/LogsClient.cs index cc749da33..e0d140ef1 100644 --- a/src/Auth0.ManagementApi/Logs/LogsClient.cs +++ b/src/Auth0.ManagementApi/Logs/LogsClient.cs @@ -307,7 +307,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/NetworkAcls/NetworkAclsClient.cs b/src/Auth0.ManagementApi/NetworkAcls/NetworkAclsClient.cs index 4ee6d5d18..a9d445bcd 100644 --- a/src/Auth0.ManagementApi/NetworkAcls/NetworkAclsClient.cs +++ b/src/Auth0.ManagementApi/NetworkAcls/NetworkAclsClient.cs @@ -430,7 +430,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Organizations/ClientGrants/ClientGrantsClient.cs b/src/Auth0.ManagementApi/Organizations/ClientGrants/ClientGrantsClient.cs index f95b548f2..9e6219e95 100644 --- a/src/Auth0.ManagementApi/Organizations/ClientGrants/ClientGrantsClient.cs +++ b/src/Auth0.ManagementApi/Organizations/ClientGrants/ClientGrantsClient.cs @@ -262,7 +262,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Organizations/Connections/ConnectionsClient.cs b/src/Auth0.ManagementApi/Organizations/Connections/ConnectionsClient.cs index 5dda23cc5..b848aeba2 100644 --- a/src/Auth0.ManagementApi/Organizations/Connections/ConnectionsClient.cs +++ b/src/Auth0.ManagementApi/Organizations/Connections/ConnectionsClient.cs @@ -444,7 +444,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Organizations/DiscoveryDomains/DiscoveryDomainsClient.cs b/src/Auth0.ManagementApi/Organizations/DiscoveryDomains/DiscoveryDomainsClient.cs index 10eb4829c..30766708f 100644 --- a/src/Auth0.ManagementApi/Organizations/DiscoveryDomains/DiscoveryDomainsClient.cs +++ b/src/Auth0.ManagementApi/Organizations/DiscoveryDomains/DiscoveryDomainsClient.cs @@ -540,7 +540,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Organizations/EnabledConnections/EnabledConnectionsClient.cs b/src/Auth0.ManagementApi/Organizations/EnabledConnections/EnabledConnectionsClient.cs index b1f3e7fd9..8cf28292f 100644 --- a/src/Auth0.ManagementApi/Organizations/EnabledConnections/EnabledConnectionsClient.cs +++ b/src/Auth0.ManagementApi/Organizations/EnabledConnections/EnabledConnectionsClient.cs @@ -440,7 +440,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Organizations/Invitations/InvitationsClient.cs b/src/Auth0.ManagementApi/Organizations/Invitations/InvitationsClient.cs index 7eb7f0b66..483dfe5cf 100644 --- a/src/Auth0.ManagementApi/Organizations/Invitations/InvitationsClient.cs +++ b/src/Auth0.ManagementApi/Organizations/Invitations/InvitationsClient.cs @@ -374,7 +374,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Organizations/Members/MembersClient.cs b/src/Auth0.ManagementApi/Organizations/Members/MembersClient.cs index 4bef02d51..b459da142 100644 --- a/src/Auth0.ManagementApi/Organizations/Members/MembersClient.cs +++ b/src/Auth0.ManagementApi/Organizations/Members/MembersClient.cs @@ -214,7 +214,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Organizations/Members/Roles/RolesClient.cs b/src/Auth0.ManagementApi/Organizations/Members/Roles/RolesClient.cs index 9d3386795..bea5965db 100644 --- a/src/Auth0.ManagementApi/Organizations/Members/Roles/RolesClient.cs +++ b/src/Auth0.ManagementApi/Organizations/Members/Roles/RolesClient.cs @@ -174,7 +174,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, userId, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Organizations/OrganizationsClient.cs b/src/Auth0.ManagementApi/Organizations/OrganizationsClient.cs index 85dcb86cf..0f5eb6aaf 100644 --- a/src/Auth0.ManagementApi/Organizations/OrganizationsClient.cs +++ b/src/Auth0.ManagementApi/Organizations/OrganizationsClient.cs @@ -565,7 +565,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Prompts/Rendering/RenderingClient.cs b/src/Auth0.ManagementApi/Prompts/Rendering/RenderingClient.cs index 3a8649891..5a3a3a4c8 100644 --- a/src/Auth0.ManagementApi/Prompts/Rendering/RenderingClient.cs +++ b/src/Auth0.ManagementApi/Prompts/Rendering/RenderingClient.cs @@ -448,7 +448,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/RefreshTokens/RefreshTokensClient.cs b/src/Auth0.ManagementApi/RefreshTokens/RefreshTokensClient.cs index 997901bc9..6455f86d8 100644 --- a/src/Auth0.ManagementApi/RefreshTokens/RefreshTokensClient.cs +++ b/src/Auth0.ManagementApi/RefreshTokens/RefreshTokensClient.cs @@ -346,7 +346,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/ResourceServers/ResourceServersClient.cs b/src/Auth0.ManagementApi/ResourceServers/ResourceServersClient.cs index e8f602ecc..1442c0a77 100644 --- a/src/Auth0.ManagementApi/ResourceServers/ResourceServersClient.cs +++ b/src/Auth0.ManagementApi/ResourceServers/ResourceServersClient.cs @@ -443,7 +443,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Roles/Permissions/PermissionsClient.cs b/src/Auth0.ManagementApi/Roles/Permissions/PermissionsClient.cs index 25a76c965..e1ab3dd40 100644 --- a/src/Auth0.ManagementApi/Roles/Permissions/PermissionsClient.cs +++ b/src/Auth0.ManagementApi/Roles/Permissions/PermissionsClient.cs @@ -167,7 +167,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Roles/RolesClient.cs b/src/Auth0.ManagementApi/Roles/RolesClient.cs index 8d226aa37..ef50c4c8e 100644 --- a/src/Auth0.ManagementApi/Roles/RolesClient.cs +++ b/src/Auth0.ManagementApi/Roles/RolesClient.cs @@ -425,7 +425,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Roles/Users/UsersClient.cs b/src/Auth0.ManagementApi/Roles/Users/UsersClient.cs index 87daad6a1..55380723f 100644 --- a/src/Auth0.ManagementApi/Roles/Users/UsersClient.cs +++ b/src/Auth0.ManagementApi/Roles/Users/UsersClient.cs @@ -192,7 +192,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Rules/RulesClient.cs b/src/Auth0.ManagementApi/Rules/RulesClient.cs index 238dfb967..52cda25c0 100644 --- a/src/Auth0.ManagementApi/Rules/RulesClient.cs +++ b/src/Auth0.ManagementApi/Rules/RulesClient.cs @@ -438,7 +438,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/SelfServiceProfiles/SelfServiceProfilesClient.cs b/src/Auth0.ManagementApi/SelfServiceProfiles/SelfServiceProfilesClient.cs index e75014720..938be6060 100644 --- a/src/Auth0.ManagementApi/SelfServiceProfiles/SelfServiceProfilesClient.cs +++ b/src/Auth0.ManagementApi/SelfServiceProfiles/SelfServiceProfilesClient.cs @@ -441,7 +441,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/TokenExchangeProfiles/TokenExchangeProfilesClient.cs b/src/Auth0.ManagementApi/TokenExchangeProfiles/TokenExchangeProfilesClient.cs index 0f51d83cc..9753e87fa 100644 --- a/src/Auth0.ManagementApi/TokenExchangeProfiles/TokenExchangeProfilesClient.cs +++ b/src/Auth0.ManagementApi/TokenExchangeProfiles/TokenExchangeProfilesClient.cs @@ -347,7 +347,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/UserAttributeProfiles/UserAttributeProfilesClient.cs b/src/Auth0.ManagementApi/UserAttributeProfiles/UserAttributeProfilesClient.cs index ca4ce69b8..91c61ee3b 100644 --- a/src/Auth0.ManagementApi/UserAttributeProfiles/UserAttributeProfilesClient.cs +++ b/src/Auth0.ManagementApi/UserAttributeProfiles/UserAttributeProfilesClient.cs @@ -593,7 +593,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/UserGrants/UserGrantsClient.cs b/src/Auth0.ManagementApi/UserGrants/UserGrantsClient.cs index 5b56ee85d..317164f1b 100644 --- a/src/Auth0.ManagementApi/UserGrants/UserGrantsClient.cs +++ b/src/Auth0.ManagementApi/UserGrants/UserGrantsClient.cs @@ -160,7 +160,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Users/AuthenticationMethods/AuthenticationMethodsClient.cs b/src/Auth0.ManagementApi/Users/AuthenticationMethods/AuthenticationMethodsClient.cs index f3dde8586..60c2557d2 100644 --- a/src/Auth0.ManagementApi/Users/AuthenticationMethods/AuthenticationMethodsClient.cs +++ b/src/Auth0.ManagementApi/Users/AuthenticationMethods/AuthenticationMethodsClient.cs @@ -553,7 +553,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Users/ConnectedAccounts/ConnectedAccountsClient.cs b/src/Auth0.ManagementApi/Users/ConnectedAccounts/ConnectedAccountsClient.cs index 72b078a4c..8df4374ec 100644 --- a/src/Auth0.ManagementApi/Users/ConnectedAccounts/ConnectedAccountsClient.cs +++ b/src/Auth0.ManagementApi/Users/ConnectedAccounts/ConnectedAccountsClient.cs @@ -157,7 +157,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Users/Groups/GroupsClient.cs b/src/Auth0.ManagementApi/Users/Groups/GroupsClient.cs index 24dc931a4..63fdb8908 100644 --- a/src/Auth0.ManagementApi/Users/Groups/GroupsClient.cs +++ b/src/Auth0.ManagementApi/Users/Groups/GroupsClient.cs @@ -166,7 +166,7 @@ public async Task> GetAsync( options, async (request, options, cancellationToken) => await GetInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Users/Logs/LogsClient.cs b/src/Auth0.ManagementApi/Users/Logs/LogsClient.cs index 7e37f79ef..83ca9bf4d 100644 --- a/src/Auth0.ManagementApi/Users/Logs/LogsClient.cs +++ b/src/Auth0.ManagementApi/Users/Logs/LogsClient.cs @@ -175,7 +175,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Users/Organizations/OrganizationsClient.cs b/src/Auth0.ManagementApi/Users/Organizations/OrganizationsClient.cs index 87b4ef5b5..8cbba0c75 100644 --- a/src/Auth0.ManagementApi/Users/Organizations/OrganizationsClient.cs +++ b/src/Auth0.ManagementApi/Users/Organizations/OrganizationsClient.cs @@ -163,7 +163,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Users/Permissions/PermissionsClient.cs b/src/Auth0.ManagementApi/Users/Permissions/PermissionsClient.cs index 31d020e83..23e3694a2 100644 --- a/src/Auth0.ManagementApi/Users/Permissions/PermissionsClient.cs +++ b/src/Auth0.ManagementApi/Users/Permissions/PermissionsClient.cs @@ -167,7 +167,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Users/RefreshToken/RefreshTokenClient.cs b/src/Auth0.ManagementApi/Users/RefreshToken/RefreshTokenClient.cs index 382994a7e..e909e2ffd 100644 --- a/src/Auth0.ManagementApi/Users/RefreshToken/RefreshTokenClient.cs +++ b/src/Auth0.ManagementApi/Users/RefreshToken/RefreshTokenClient.cs @@ -157,7 +157,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(userId, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Users/Roles/RolesClient.cs b/src/Auth0.ManagementApi/Users/Roles/RolesClient.cs index 2fac0d843..8c65bf47d 100644 --- a/src/Auth0.ManagementApi/Users/Roles/RolesClient.cs +++ b/src/Auth0.ManagementApi/Users/Roles/RolesClient.cs @@ -164,7 +164,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(id, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/Users/Sessions/SessionsClient.cs b/src/Auth0.ManagementApi/Users/Sessions/SessionsClient.cs index 9786bbacd..5b83dad59 100644 --- a/src/Auth0.ManagementApi/Users/Sessions/SessionsClient.cs +++ b/src/Auth0.ManagementApi/Users/Sessions/SessionsClient.cs @@ -157,7 +157,7 @@ public async Task> ListAsync( options, async (request, options, cancellationToken) => await ListInternalAsync(userId, request, options, cancellationToken) - .ConfigureAwait(false), + .WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/src/Auth0.ManagementApi/Users/UsersClient.cs b/src/Auth0.ManagementApi/Users/UsersClient.cs index 452b5536c..9e3b89b3b 100644 --- a/src/Auth0.ManagementApi/Users/UsersClient.cs +++ b/src/Auth0.ManagementApi/Users/UsersClient.cs @@ -710,7 +710,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), request => request.Page.GetValueOrDefault(0), (request, offset) => { diff --git a/src/Auth0.ManagementApi/VerifiableCredentials/Verification/Templates/TemplatesClient.cs b/src/Auth0.ManagementApi/VerifiableCredentials/Verification/Templates/TemplatesClient.cs index 56b06f2f0..fbf414ce4 100644 --- a/src/Auth0.ManagementApi/VerifiableCredentials/Verification/Templates/TemplatesClient.cs +++ b/src/Auth0.ManagementApi/VerifiableCredentials/Verification/Templates/TemplatesClient.cs @@ -431,7 +431,7 @@ public async Task> ListAsync( request, options, async (request, options, cancellationToken) => - await ListInternalAsync(request, options, cancellationToken), + await ListInternalAsync(request, options, cancellationToken).WithRawResponse(), (request, cursor) => { request.From = cursor; diff --git a/tests/Auth0.ManagementApi.Test/Core/Pagination/GuidCursorTest.cs b/tests/Auth0.ManagementApi.Test/Core/Pagination/GuidCursorTest.cs index b16076172..0e3bd0def 100644 --- a/tests/Auth0.ManagementApi.Test/Core/Pagination/GuidCursorTest.cs +++ b/tests/Auth0.ManagementApi.Test/Core/Pagination/GuidCursorTest.cs @@ -1,4 +1,5 @@ using Auth0.ManagementApi.Core; +using global::System.Net; using NUnit.Framework; using SystemTask = global::System.Threading.Tasks.Task; @@ -45,7 +46,7 @@ public async SystemTask CursorPagerShouldWorkWithGuidCursors() (_, _, _) => { responses.MoveNext(); - return SystemTask.FromResult(responses.Current); + return SystemTask.FromResult(Wrap(responses.Current)); }, (request, cursor) => { @@ -100,4 +101,16 @@ private class Cursor { public required Guid? Next { get; set; } } + + private static WithRawResponse Wrap(Response response) => + new() + { + Data = response, + RawResponse = new RawResponse + { + StatusCode = HttpStatusCode.OK, + Url = new Uri("https://localhost"), + Headers = default, + }, + }; } diff --git a/tests/Auth0.ManagementApi.Test/Core/Pagination/HasNextPageOffsetTest.cs b/tests/Auth0.ManagementApi.Test/Core/Pagination/HasNextPageOffsetTest.cs index 8542c0999..8fe729af5 100644 --- a/tests/Auth0.ManagementApi.Test/Core/Pagination/HasNextPageOffsetTest.cs +++ b/tests/Auth0.ManagementApi.Test/Core/Pagination/HasNextPageOffsetTest.cs @@ -1,4 +1,5 @@ using Auth0.ManagementApi.Core; +using global::System.Net; using NUnit.Framework; using SystemTask = global::System.Threading.Tasks.Task; @@ -41,7 +42,7 @@ public async SystemTask OffsetPagerShouldWorkWithHasNextPage() (_, _, _) => { responses.MoveNext(); - return SystemTask.FromResult(responses.Current); + return SystemTask.FromResult(Wrap(responses.Current)); }, request => request?.Pagination?.Page ?? 0, (request, offset) => @@ -89,4 +90,16 @@ private class Data { public IEnumerable? Items { get; set; } } + + private static WithRawResponse Wrap(Response response) => + new() + { + Data = response, + RawResponse = new RawResponse + { + StatusCode = HttpStatusCode.OK, + Url = new Uri("https://localhost"), + Headers = default, + }, + }; } diff --git a/tests/Auth0.ManagementApi.Test/Core/Pagination/IntOffsetTest.cs b/tests/Auth0.ManagementApi.Test/Core/Pagination/IntOffsetTest.cs index 5dd88c4a6..1387c3dbc 100644 --- a/tests/Auth0.ManagementApi.Test/Core/Pagination/IntOffsetTest.cs +++ b/tests/Auth0.ManagementApi.Test/Core/Pagination/IntOffsetTest.cs @@ -1,4 +1,5 @@ using Auth0.ManagementApi.Core; +using global::System.Net; using NUnit.Framework; using SystemTask = global::System.Threading.Tasks.Task; @@ -29,7 +30,7 @@ public async SystemTask OffsetPagerShouldWorkWithIntPage() (_, _, _) => { responses.MoveNext(); - return SystemTask.FromResult(responses.Current); + return SystemTask.FromResult(Wrap(responses.Current)); }, request => request?.Pagination?.Page ?? 0, (request, offset) => @@ -79,4 +80,16 @@ private class Data { public IEnumerable? Items { get; set; } } + + private static WithRawResponse Wrap(Response response) => + new() + { + Data = response, + RawResponse = new RawResponse + { + StatusCode = HttpStatusCode.OK, + Url = new Uri("https://localhost"), + Headers = default, + }, + }; } diff --git a/tests/Auth0.ManagementApi.Test/Core/Pagination/LongOffsetTest.cs b/tests/Auth0.ManagementApi.Test/Core/Pagination/LongOffsetTest.cs index ee4ebe75f..e658a9e01 100644 --- a/tests/Auth0.ManagementApi.Test/Core/Pagination/LongOffsetTest.cs +++ b/tests/Auth0.ManagementApi.Test/Core/Pagination/LongOffsetTest.cs @@ -1,4 +1,5 @@ using Auth0.ManagementApi.Core; +using global::System.Net; using NUnit.Framework; using SystemTask = global::System.Threading.Tasks.Task; @@ -29,7 +30,7 @@ public async SystemTask OffsetPagerShouldWorkWithLongPage() (_, _, _) => { responses.MoveNext(); - return SystemTask.FromResult(responses.Current); + return SystemTask.FromResult(Wrap(responses.Current)); }, request => request?.Pagination?.Page ?? 0, (request, offset) => @@ -76,4 +77,16 @@ private class Data { public IEnumerable? Items { get; set; } } + + private static WithRawResponse Wrap(Response response) => + new() + { + Data = response, + RawResponse = new RawResponse + { + StatusCode = HttpStatusCode.OK, + Url = new Uri("https://localhost"), + Headers = default, + }, + }; } diff --git a/tests/Auth0.ManagementApi.Test/Core/Pagination/NoRequestCursorTest.cs b/tests/Auth0.ManagementApi.Test/Core/Pagination/NoRequestCursorTest.cs index 1a71881eb..edf491dff 100644 --- a/tests/Auth0.ManagementApi.Test/Core/Pagination/NoRequestCursorTest.cs +++ b/tests/Auth0.ManagementApi.Test/Core/Pagination/NoRequestCursorTest.cs @@ -1,4 +1,5 @@ using Auth0.ManagementApi.Core; +using global::System.Net; using NUnit.Framework; using SystemTask = global::System.Threading.Tasks.Task; @@ -45,7 +46,7 @@ public async SystemTask CursorPagerShouldWorkWithStringCursor() (_, _, _) => { responses.MoveNext(); - return SystemTask.FromResult(responses.Current); + return SystemTask.FromResult(Wrap(responses.Current)); }, (request, cursor) => { @@ -103,4 +104,16 @@ private class Cursor { public required string? Next { get; set; } } + + private static WithRawResponse Wrap(Response response) => + new() + { + Data = response, + RawResponse = new RawResponse + { + StatusCode = HttpStatusCode.OK, + Url = new Uri("https://localhost"), + Headers = default, + }, + }; } diff --git a/tests/Auth0.ManagementApi.Test/Core/Pagination/NoRequestOffsetTest.cs b/tests/Auth0.ManagementApi.Test/Core/Pagination/NoRequestOffsetTest.cs index 61bd0295b..f9898b93b 100644 --- a/tests/Auth0.ManagementApi.Test/Core/Pagination/NoRequestOffsetTest.cs +++ b/tests/Auth0.ManagementApi.Test/Core/Pagination/NoRequestOffsetTest.cs @@ -1,4 +1,5 @@ using Auth0.ManagementApi.Core; +using global::System.Net; using NUnit.Framework; using SystemTask = global::System.Threading.Tasks.Task; @@ -29,7 +30,7 @@ public async SystemTask OffsetPagerShouldWorkWithoutRequest() (_, _, _) => { responses.MoveNext(); - return SystemTask.FromResult(responses.Current); + return SystemTask.FromResult(Wrap(responses.Current)); }, request => request?.Pagination?.Page ?? 0, (request, offset) => @@ -79,4 +80,16 @@ private class Data { public IEnumerable? Items { get; set; } } + + private static WithRawResponse Wrap(Response response) => + new() + { + Data = response, + RawResponse = new RawResponse + { + StatusCode = HttpStatusCode.OK, + Url = new Uri("https://localhost"), + Headers = default, + }, + }; } diff --git a/tests/Auth0.ManagementApi.Test/Core/Pagination/StepOffsetTest.cs b/tests/Auth0.ManagementApi.Test/Core/Pagination/StepOffsetTest.cs index 9598fbc35..078a11d01 100644 --- a/tests/Auth0.ManagementApi.Test/Core/Pagination/StepOffsetTest.cs +++ b/tests/Auth0.ManagementApi.Test/Core/Pagination/StepOffsetTest.cs @@ -1,4 +1,5 @@ using Auth0.ManagementApi.Core; +using global::System.Net; using NUnit.Framework; using SystemTask = global::System.Threading.Tasks.Task; @@ -30,7 +31,7 @@ public async SystemTask OffsetPagerShouldWorkWithStep() (_, _, _) => { responses.MoveNext(); - return SystemTask.FromResult(responses.Current); + return SystemTask.FromResult(Wrap(responses.Current)); }, request => request?.Pagination?.ItemOffset ?? 0, (request, offset) => @@ -89,4 +90,16 @@ private class Data { public IEnumerable? Items { get; set; } } + + private static WithRawResponse Wrap(Response response) => + new() + { + Data = response, + RawResponse = new RawResponse + { + StatusCode = HttpStatusCode.OK, + Url = new Uri("https://localhost"), + Headers = default, + }, + }; } diff --git a/tests/Auth0.ManagementApi.Test/Core/Pagination/StringCursorTest.cs b/tests/Auth0.ManagementApi.Test/Core/Pagination/StringCursorTest.cs index b9779df0d..acabdf6f2 100644 --- a/tests/Auth0.ManagementApi.Test/Core/Pagination/StringCursorTest.cs +++ b/tests/Auth0.ManagementApi.Test/Core/Pagination/StringCursorTest.cs @@ -1,4 +1,5 @@ using Auth0.ManagementApi.Core; +using global::System.Net; using NUnit.Framework; using SystemTask = global::System.Threading.Tasks.Task; @@ -46,7 +47,7 @@ public async SystemTask CursorPagerShouldWorkWithStringCursor() (_, _, _) => { responses.MoveNext(); - return SystemTask.FromResult(responses.Current); + return SystemTask.FromResult(Wrap(responses.Current)); }, (request, cursor) => { @@ -115,7 +116,7 @@ public async SystemTask CursorPagerShouldWorkWithStringCursor_EmptyStringCursor( (_, _, _) => { responses.MoveNext(); - return SystemTask.FromResult(responses.Current); + return SystemTask.FromResult(Wrap(responses.Current)); }, (request, cursor) => { @@ -164,4 +165,16 @@ private class Cursor { public required string? Next { get; set; } } + + private static WithRawResponse Wrap(Response response) => + new() + { + Data = response, + RawResponse = new RawResponse + { + StatusCode = HttpStatusCode.OK, + Url = new Uri("https://localhost"), + Headers = default, + }, + }; } diff --git a/tests/Auth0.ManagementApi.Test/Core/QueryStringBuilderTests.cs b/tests/Auth0.ManagementApi.Test/Core/QueryStringBuilderTests.cs index 8e59aca9f..92aff7674 100644 --- a/tests/Auth0.ManagementApi.Test/Core/QueryStringBuilderTests.cs +++ b/tests/Auth0.ManagementApi.Test/Core/QueryStringBuilderTests.cs @@ -46,7 +46,7 @@ public void Build_SpecialCharacters() Assert.That( result, Is.EqualTo( - "?email=test%40example.com&url=https%3A%2F%2Fexample.com%2Fpath%3Fquery%3Dvalue&special=a%2Bb%3Dc%26d" + "?email=test@example.com&url=https://example.com/path?query=value&special=a%2Bb=c%26d" ) ); } @@ -159,7 +159,7 @@ public void Build_ReservedCharacters_NotEncoded() var result = QueryStringBuilder.Build(parameters); - // Unreserved characters: A-Z a-z 0-9 - _ . ~ + // Safe query characters include RFC 3986 unreserved + sub-delimiters (except & = +) + : @ / Assert.That(result, Is.EqualTo("?path=some-path&id=123-456_789.test~value")); } @@ -557,4 +557,102 @@ public void Builder_Set_WithCollection_ReplacesAllPreviousValues() Assert.That(result, Does.Not.EndWith("id=1")); Assert.That(result, Does.Not.EndWith("id=2")); } + + [Test] + public void EncodePathSegment_UnreservedChars_NotEncoded() + { + var result = QueryStringBuilder.EncodePathSegment("hello-world_test.value~123"); + Assert.That(result, Is.EqualTo("hello-world_test.value~123")); + } + + [Test] + public void EncodePathSegment_SubDelimiters_NotEncoded() + { + // All sub-delimiters are safe in path segments per RFC 3986 + var result = QueryStringBuilder.EncodePathSegment("a!b$c&d'e(f)g*h+i,j;k=l"); + Assert.That(result, Is.EqualTo("a!b$c&d'e(f)g*h+i,j;k=l")); + } + + [Test] + public void EncodePathSegment_ColonAndAt_NotEncoded() + { + var result = QueryStringBuilder.EncodePathSegment("user@host:8080"); + Assert.That(result, Is.EqualTo("user@host:8080")); + } + + [Test] + public void EncodePathSegment_SlashAndQuestion_Encoded() + { + // "/" and "?" are NOT part of pchar, so they must be encoded in path segments + var result = QueryStringBuilder.EncodePathSegment("path/with?query"); + Assert.That(result, Is.EqualTo("path%2Fwith%3Fquery")); + } + + [Test] + public void EncodePathSegment_Space_Encoded() + { + var result = QueryStringBuilder.EncodePathSegment("hello world"); + Assert.That(result, Is.EqualTo("hello%20world")); + } + + [Test] + public void EncodePathSegment_EmptyAndNull() + { + Assert.That(QueryStringBuilder.EncodePathSegment(""), Is.EqualTo("")); + Assert.That(QueryStringBuilder.EncodePathSegment(null!), Is.Null); + } + + [Test] + public void Build_QueryKeyVsValue_DifferentEncoding() + { + // "=" is safe in query values but NOT in query keys + var parameters = new List> + { + new("key=with=equals", "value=with=equals"), + }; + + var result = QueryStringBuilder.Build(parameters); + + // Key: "=" must be encoded + // Value: "=" is safe (part of query value safe chars) + Assert.That(result, Is.EqualTo("?key%3Dwith%3Dequals=value=with=equals")); + } + + [Test] + public void Build_QueryValue_QuestionMarkNotEncoded() + { + // "?" is safe in both query keys and query values per RFC 3986 + var parameters = new List> { new("q?key", "is this?") }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo("?q?key=is%20this?")); + } + + [Test] + public void Build_QueryKey_PlusEncoded() + { + // "+" must be encoded in both query keys and query values + var parameters = new List> { new("a+b", "c+d") }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Is.EqualTo("?a%2Bb=c%2Bd")); + } + + [Test] + public void Build_ODataFilter_DollarPreserved() + { + // "$" is safe in query keys (sub-delimiter), verifies OData-style parameters work + var parameters = new List> + { + new("$filter", "status eq 'active'"), + new("$top", "10"), + }; + + var result = QueryStringBuilder.Build(parameters); + + Assert.That(result, Does.Contain("$filter=status%20eq%20'active'")); + Assert.That(result, Does.Contain("$top=10")); + } } diff --git a/tests/Auth0.ManagementApi.Test/Core/RawClientTests/QueryParameterTests.cs b/tests/Auth0.ManagementApi.Test/Core/RawClientTests/QueryParameterTests.cs index 6cec03c2e..bcb17a2d4 100644 --- a/tests/Auth0.ManagementApi.Test/Core/RawClientTests/QueryParameterTests.cs +++ b/tests/Auth0.ManagementApi.Test/Core/RawClientTests/QueryParameterTests.cs @@ -27,7 +27,7 @@ public void QueryParameters_SpecialCharacterEscaping() .Add("space test", "hello world") .Build(); - Assert.That(queryString, Does.Contain("email=bob%2Btest%40example.com")); + Assert.That(queryString, Does.Contain("email=bob%2Btest@example.com")); Assert.That(queryString, Does.Contain("%25Complete=100")); Assert.That(queryString, Does.Contain("space%20test=hello%20world")); } diff --git a/tests/Auth0.ManagementApi.Test/Utils/JsonElementComparer.cs b/tests/Auth0.ManagementApi.Test/Utils/JsonElementComparer.cs index a37ef402c..36fa0e9d4 100644 --- a/tests/Auth0.ManagementApi.Test/Utils/JsonElementComparer.cs +++ b/tests/Auth0.ManagementApi.Test/Utils/JsonElementComparer.cs @@ -92,8 +92,15 @@ private bool CompareJsonElements(JsonElement x, JsonElement y, string path) case JsonValueKind.Number: if (x.GetDecimal() != y.GetDecimal()) { - _failurePath = $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; - return false; + if (x.GetDouble() != y.GetDouble()) + { + if (x.GetSingle() != y.GetSingle()) + { + _failurePath = + $"{path}: Expected {x.GetDecimal()} but got {y.GetDecimal()}"; + return false; + } + } } return true; From e82d086abad9e4d673a0996d57e6105c0d2ac1c7 Mon Sep 17 00:00:00 2001 From: Kailash B Date: Mon, 13 Apr 2026 16:38:01 +0530 Subject: [PATCH 2/2] Updates Migration guide and integration test --- V8_MIGRATION_GUIDE.md | 12 ++++++++++++ .../UsersTests.cs | 1 + 2 files changed, 13 insertions(+) diff --git a/V8_MIGRATION_GUIDE.md b/V8_MIGRATION_GUIDE.md index f1d697d96..2e2c60daa 100644 --- a/V8_MIGRATION_GUIDE.md +++ b/V8_MIGRATION_GUIDE.md @@ -435,6 +435,12 @@ var request = new ListUsersRequestParameters var pager = await client.Users.ListAsync(request); +// Access total count from the paginated response metadata (IncludeTotals must be true). +// Note: `Total` is the server-side total across all pages, not just the current page's item count. +var firstPageResponse = (ListUsersOffsetPaginatedResponseContent?)pager.CurrentPage.Response; +var totalUsers = firstPageResponse?.Total; +Console.WriteLine($"Total users: {totalUsers}"); + // Option 1: Iterate through all items across all pages automatically // The pager fetches subsequent pages as needed await foreach (var user in pager) @@ -600,6 +606,12 @@ var request = new ListUsersRequestParameters var pager = await client.Users.ListAsync(request); +// Access total count from the paginated response metadata (IncludeTotals must be true). +// Note: `Total` is the server-side total across all pages, not just the current page's item count. +var firstPageResponse = (ListUsersOffsetPaginatedResponseContent?)pager.CurrentPage.Response; +var totalUsers = firstPageResponse?.Total; +Console.WriteLine($"Total users: {totalUsers}"); + // Iterate through all pages automatically await foreach (var user in pager) { diff --git a/tests/Auth0.ManagementApi.IntegrationTests/UsersTests.cs b/tests/Auth0.ManagementApi.IntegrationTests/UsersTests.cs index af3919546..e2042f187 100644 --- a/tests/Auth0.ManagementApi.IntegrationTests/UsersTests.cs +++ b/tests/Auth0.ManagementApi.IntegrationTests/UsersTests.cs @@ -232,6 +232,7 @@ public async Task Test_paging_with_totals() // Assert usersPager.CurrentPage.Items.Should().NotBeNull(); + ((ListUsersOffsetPaginatedResponseContent) usersPager.CurrentPage.Response).Total.Should().BeGreaterThan(0); } [Fact]