Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(csharp): Misc improvements #5838

Merged
merged 7 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 9 additions & 0 deletions fern/pages/changelogs/csharp-sdk/2025-02-02.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## 1.9.13
**`(internal):`** Miscellaneous improvement for the C# generator
- Call `.ConfigureAwait(false)` on Tasks
- Use `Enumerable<T>.Empty` instead of creating a new empty list
- Add PolySharp to test project and use C# 12
- Remove redundant `#nullable enable` directives
- Improve C# syntax


Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ JsonSerializerOptions options
return;
}

JsonSerializerOptions jsonSerializerOptions = new JsonSerializerOptions(options);
var jsonSerializerOptions = new JsonSerializerOptions(options);
jsonSerializerOptions.Converters.Clear();
jsonSerializerOptions.Converters.Add(Activator.CreateInstance<TConverterType>());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
using System;
using System.Net.Http;
using Grpc.Core;
using <%= namespace%>.Core;

namespace <%= namespace%>;

#nullable enable

public partial class GrpcRequestOptions
{
/// <summary>
/// The maximum number of retry attempts.
/// </summary>
public int? MaxRetries { get; init; }

/// <summary>
/// The timeout for the request.
/// </summary>
Expand All @@ -23,7 +19,7 @@ public partial class GrpcRequestOptions
/// Options for write operations.
/// </summary>
public WriteOptions? WriteOptions { get; init; }

/// <summary>
/// Client-side call credentials. Provide authorization with per-call granularity.
/// </summary>
Expand All @@ -33,4 +29,4 @@ public partial class GrpcRequestOptions
/// Headers to be sent with this particular request.
/// </summary>
internal Headers Headers { get; init; } = new();
}
}
124 changes: 61 additions & 63 deletions generators/csharp/codegen/src/asIs/RawClient.Template.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using System.Text;
using System.Threading;
using SystemTask = System.Threading.Tasks.Task;
using System.Net.Http;
using System.Net.Http.Headers;

namespace <%= namespace%>;

#nullable enable

/// <summary>
/// Utility class for making raw HTTP requests to the API.
/// </summary>
Expand All @@ -28,64 +27,61 @@ internal class RawClient(ClientOptions clientOptions)
/// </summary>
public readonly ClientOptions Options = clientOptions;

public async Task<ApiResponse> MakeRequestAsync(
BaseApiRequest request,
CancellationToken cancellationToken = default
)
{
// Apply the request timeout.
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var timeout = request.Options?.Timeout ?? Options.Timeout;
cts.CancelAfter(timeout);

// Send the request.
return await SendWithRetriesAsync(
request,
cts.Token
);
}
public async Task<ApiResponse> MakeRequestAsync(
BaseApiRequest request,
CancellationToken cancellationToken = default
)
{
// Apply the request timeout.
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var timeout = request.Options?.Timeout ?? Options.Timeout;
cts.CancelAfter(timeout);

public record BaseApiRequest
{
public required string BaseUrl { get; init; }
// Send the request.
return await SendWithRetriesAsync(request, cts.Token).ConfigureAwait(false);
}

public required HttpMethod Method { get; init; }
public record BaseApiRequest
{
public required string BaseUrl { get; init; }

public required string Path { get; init; }
public required HttpMethod Method { get; init; }

public string? ContentType { get; init; }
public required string Path { get; init; }

public Dictionary<string, object> Query { get; init; } = new();
public string? ContentType { get; init; }

public Headers Headers { get; init; } = new();
public Dictionary<string, object> Query { get; init; } = new();

public IRequestOptions? Options { get; init; }
}
public Headers Headers { get; init; } = new();

/// <summary>
/// The request object to be sent for streaming uploads.
/// </summary>
public record StreamApiRequest : BaseApiRequest
{
public Stream? Body { get; init; }
}
public IRequestOptions? Options { get; init; }
}

/// <summary>
/// The request object to be sent for JSON APIs.
/// </summary>
public record JsonApiRequest : BaseApiRequest
{
public object? Body { get; init; }
}
/// <summary>
/// The request object to be sent for streaming uploads.
/// </summary>
public record StreamApiRequest : BaseApiRequest
{
public Stream? Body { get; init; }
}

/// <summary>
/// The response object returned from the API.
/// </summary>
public record ApiResponse
{
public required int StatusCode { get; init; }
/// <summary>
/// The request object to be sent for JSON APIs.
/// </summary>
public record JsonApiRequest : BaseApiRequest
{
public object? Body { get; init; }
}

public required HttpResponseMessage Raw { get; init; }
/// <summary>
/// The response object returned from the API.
/// </summary>
public record ApiResponse
{
public required int StatusCode { get; init; }

public required HttpResponseMessage Raw { get; init; }
}

private async Task<ApiResponse> SendWithRetriesAsync(
Expand All @@ -95,16 +91,16 @@ CancellationToken cancellationToken
{
var httpClient = request.Options?.HttpClient ?? Options.HttpClient;
var maxRetries = request.Options?.MaxRetries ?? Options.MaxRetries;
var response = await httpClient.SendAsync(BuildHttpRequest(request), cancellationToken);
var response = await httpClient.SendAsync(BuildHttpRequest(request), cancellationToken).ConfigureAwait(false);
for (var i = 0; i < maxRetries; i++)
{
if (!ShouldRetry(response))
{
break;
}
var delayMs = Math.Min(InitialRetryDelayMs * (int)Math.Pow(2, i), MaxRetryDelayMs);
await System.Threading.Tasks.Task.Delay(delayMs, cancellationToken);
response = await httpClient.SendAsync(BuildHttpRequest(request), cancellationToken);
await SystemTask.Delay(delayMs, cancellationToken).ConfigureAwait(false);
response = await httpClient.SendAsync(BuildHttpRequest(request), cancellationToken).ConfigureAwait(false);
}
return new ApiResponse { StatusCode = (int)response.StatusCode, Raw = response };
}
Expand All @@ -123,24 +119,26 @@ private HttpRequestMessage BuildHttpRequest(BaseApiRequest request)
{
// Add the request body to the request.
case JsonApiRequest jsonRequest:
{
if (jsonRequest.Body != null)
{
if (jsonRequest.Body != null)
{
httpRequest.Content = new StringContent(
JsonUtils.Serialize(jsonRequest.Body),
Encoding.UTF8,
"application/json"
);
}
break;
httpRequest.Content = new StringContent(
JsonUtils.Serialize(jsonRequest.Body),
Encoding.UTF8,
"application/json"
);
}
break;
}
case StreamApiRequest { Body: not null } streamRequest:
httpRequest.Content = new StreamContent(streamRequest.Body);
break;
}
if (request.ContentType != null)
{
httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(request.ContentType);
httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(
request.ContentType
);
}
SetHeaders(httpRequest, Options.Headers);
SetHeaders(httpRequest, request.Headers);
Expand Down Expand Up @@ -200,4 +198,4 @@ private static void SetHeaders(HttpRequestMessage httpRequest, Headers headers)
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using Grpc.Core;
using Grpc.Net.Client;

Expand Down Expand Up @@ -53,14 +52,11 @@ public CallOptions CreateCallOptions(
);
}

private void SetHeaders(global::Grpc.Core.Metadata metadata, Headers headers)
private static void SetHeaders(global::Grpc.Core.Metadata metadata, Headers headers)
{
foreach (var header in headers)
{
var value = header.Value?.Match(
str => str,
func => func.Invoke()
);
var value = header.Value?.Match(str => str, func => func.Invoke());
if (value != null)
{
metadata.Add(header.Key, value);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,24 @@
using System;
using System.Runtime.Serialization;
using System.Text.Json;
using System.Text.Json.Serialization;
using NUnit.Framework;
using <%= namespace%>.Core;

namespace <%= namespace%>.Test.Core
{
[TestFixture]
namespace <%= namespace%>.Test.Core;

[TestFixture]
public class StringEnumSerializerTests
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
};
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };

private const DummyEnum KnownEnumValue2 = DummyEnum.KnownValue2;
private const string KnownEnumValue2String = "known_value2";

private static readonly string JsonWithKnownEnum2 =
$$"""
{
"enum_property": "{{KnownEnumValue2String}}"
}
""";
private const string JsonWithKnownEnum2 = $$"""
{
Copy link
Member

Choose a reason for hiding this comment

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

Is this string format preferred? I would have thought the previous nesting was better.

Copy link
Member Author

Choose a reason for hiding this comment

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

This must be csharpier, the source template doesn't format it like this

"enum_property": "{{KnownEnumValue2String}}"
}
""";

[Test]
public void ShouldParseKnownEnumValue2()
Expand All @@ -36,8 +31,10 @@ public void ShouldParseKnownEnumValue2()
[Test]
public void ShouldSerializeKnownEnumValue2()
{
var json = JsonSerializer.SerializeToElement(new DummyObject { EnumProperty = KnownEnumValue2 },
JsonOptions);
var json = JsonSerializer.SerializeToElement(
new DummyObject { EnumProperty = KnownEnumValue2 },
JsonOptions
);
TestContext.Out.WriteLine("Serialized JSON: \n" + json);
var enumString = json.GetProperty("enum_property").GetString();
Assert.That(enumString, Is.Not.Null);
Expand All @@ -54,10 +51,9 @@ public class DummyObject
[JsonConverter(typeof(EnumSerializer<DummyEnum>))]
public enum DummyEnum
{

[EnumMember(Value = "known_value1")]
KnownValue1,

[EnumMember(Value = "known_value2")]
KnownValue2,
}
}
KnownValue2
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NUnit.Framework;
using SystemTask = System.Threading.Tasks.Task;
using <%= namespace%>.Core;

namespace <%= namespace%>.Test.Core.Pagination;
Expand All @@ -7,7 +8,7 @@ namespace <%= namespace%>.Test.Core.Pagination;
public class GuidCursorTest
{
[Test]
public async Task CursorPagerShouldWorkWithGuidCursors()
public async SystemTask CursorPagerShouldWorkWithGuidCursors()
{
var pager = CreatePager();
await AssertPager(pager);
Expand Down Expand Up @@ -72,7 +73,7 @@ private Pager<object> CreatePager()
(_, _, _) =>
{
responses.MoveNext();
return Task.FromResult(responses.Current);
return SystemTask.FromResult(responses.Current);
},
(request, cursor) =>
{
Expand All @@ -85,7 +86,7 @@ private Pager<object> CreatePager()
return pager;
}

private async Task AssertPager(Pager<object> pager)
private async SystemTask AssertPager(Pager<object> pager)
{
var pageEnumerator = pager.AsPagesAsync().GetAsyncEnumerator();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NUnit.Framework;
using SystemTask = System.Threading.Tasks.Task;
using <%= namespace%>.Core;

namespace <%= namespace%>.Test.Core.Pagination;
Expand All @@ -7,7 +8,7 @@ namespace <%= namespace%>.Test.Core.Pagination;
public class HasNextPageOffsetTest
{
[Test]
public async Task OffsetPagerShouldWorkWithHasNextPage()
public async SystemTask OffsetPagerShouldWorkWithHasNextPage()
{
var pager = CreatePager();
await AssertPager(pager);
Expand Down Expand Up @@ -61,7 +62,7 @@ private static Pager<object> CreatePager()
(_, _, _) =>
{
responses.MoveNext();
return Task.FromResult(responses.Current);
return SystemTask.FromResult(responses.Current);
},
request => request?.Pagination?.Page ?? 0,
(request, offset) =>
Expand All @@ -76,7 +77,7 @@ private static Pager<object> CreatePager()
return pager;
}

private static async Task AssertPager(Pager<object> pager)
private static async SystemTask AssertPager(Pager<object> pager)
{
var pageCounter = 0;
var itemCounter = 0;
Expand Down
Loading
Loading