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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/WorkOS.net/Client/Utilities/RequestUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace WorkOS
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
Expand Down Expand Up @@ -353,7 +354,7 @@ private static void AddScalar(string key, object? value, List<KeyValuePair<strin
rendered = dto.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture);
break;
case Enum en:
rendered = en.ToString();
rendered = ResolveEnumWireValue(en);
break;
case double d:
rendered = d.ToString("R", CultureInfo.InvariantCulture);
Expand All @@ -372,6 +373,26 @@ private static void AddScalar(string key, object? value, List<KeyValuePair<strin
result.Add(new KeyValuePair<string, string>(key, rendered));
}

/// <summary>
/// Return the wire value for an enum member by reading its
/// <see cref="EnumMemberAttribute.Value"/>. Falls back to
/// <c>ToString()</c> when the attribute is absent.
/// </summary>
private static string ResolveEnumWireValue(Enum value)
{
var member = value.GetType().GetField(value.ToString());
if (member != null)
{
var attr = member.GetCustomAttribute<EnumMemberAttribute>();
if (attr != null && attr.Value != null)
{
return attr.Value;
}
}

return value.ToString();
}
Comment on lines +381 to +394
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 [Flags] enum combinations will miss the EnumMember lookup

value.GetType().GetField(value.ToString()) works correctly for simple enums, but for a [Flags] enum holding a combined value (e.g., A | B), ToString() produces "A, B" — a string that will never match any single field name, so GetField returns null and the method falls back to ToString(). No current PaginationOrder-style enum in this SDK uses [Flags], but this edge case is silently broken if any is added in the future. A defensive comment noting the limitation would prevent confusion.


/// <summary>
/// Contract resolver that includes internal properties so that
/// service-injected fields (grant_type, client_id, client_secret)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ public class ListOptions : BaseOptions
/// </summary>
[JsonProperty("order")]
[STJS.JsonPropertyName("order")]
public PaginationOrder Order { get; set; } = PaginationOrder.Desc;
public PaginationOrder? Order { get; set; } = PaginationOrder.Desc;
}
}
20 changes: 8 additions & 12 deletions test/WorkOSTests/Client/NonGetQueryParamsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ public async Task DeleteWithBoolQueryParam_IncludesCascadeDeleteOnUri()

httpMock.MockResponse(
HttpMethod.Delete,
"/authorization/organizations/org_1/resources/file/ext_42",
"/authorization/resources/res_1",
HttpStatusCode.NoContent,
"{}");

await client.Authorization.DeleteResourceByExternalId(
"org_1",
"file",
"ext_42",
new AuthorizationDeleteResourceByExternalIdOptions { CascadeDelete = true });
await client.Authorization.DeleteResource(
"res_1",
new AuthorizationDeleteResourceOptions { CascadeDelete = true });

var last = Assert.Single(httpMock.CapturedRequests);
Assert.Equal(HttpMethod.Delete, last.Method);
Expand All @@ -56,15 +54,13 @@ public async Task DeleteWithBoolFalse_SerializesAsFalse()

httpMock.MockResponse(
HttpMethod.Delete,
"/authorization/organizations/org_1/resources/file/ext_42",
"/authorization/resources/res_1",
HttpStatusCode.NoContent,
"{}");

await client.Authorization.DeleteResourceByExternalId(
"org_1",
"file",
"ext_42",
new AuthorizationDeleteResourceByExternalIdOptions { CascadeDelete = false });
await client.Authorization.DeleteResource(
"res_1",
new AuthorizationDeleteResourceOptions { CascadeDelete = false });

var last = Assert.Single(httpMock.CapturedRequests);
Assert.Contains("cascade_delete=false", last.RequestUri!.Query);
Expand Down