Skip to content

Commit 45d0ab9

Browse files
committed
feat: support system.net.http.json generation
1 parent 29e81bf commit 45d0ab9

File tree

5 files changed

+553
-30
lines changed

5 files changed

+553
-30
lines changed

src/libs/AutoSDK.CLI/Commands/GenerateCommand.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ internal sealed class GenerateCommand : Command
116116
Description = "Generate CLI for the client",
117117
};
118118

119+
private Option<bool> UseSystemNetHttpJson { get; } = new(
120+
name: "--use-system-net-http-json")
121+
{
122+
DefaultValueFactory = _ => Settings.Default.GenerateMethodsUsingSystemNetHttpJson,
123+
Description = "Generate System.Text.Json HTTP method bodies via System.Net.Http.Json helpers where safe.",
124+
};
125+
119126
private Option<string[]> SecuritySchemes { get; } = new(
120127
name: "--security-scheme")
121128
{
@@ -222,6 +229,7 @@ internal sealed class GenerateCommand : Command
222229
Options.Add(GenerateModelValidationMethods);
223230
Options.Add(ComputeDiscriminators);
224231
Options.Add(GenerateCli);
232+
Options.Add(UseSystemNetHttpJson);
225233
Options.Add(SecuritySchemes);
226234
Options.Add(BaseUrl);
227235
Options.Add(OpenApiOverrides);
@@ -284,6 +292,7 @@ private async Task HandleAsync(ParseResult parseResult)
284292
GenerateModelValidationMethods = parseResult.GetRequiredValue(GenerateModelValidationMethods),
285293
IgnoreOpenApiErrors = parseResult.GetRequiredValue(IgnoreOpenApiErrors),
286294
IgnoreOpenApiWarnings = parseResult.GetRequiredValue(IgnoreOpenApiWarnings),
295+
GenerateMethodsUsingSystemNetHttpJson = parseResult.GetRequiredValue(UseSystemNetHttpJson),
287296
FromCli = true,
288297
GenerateCli = parseResult.GetRequiredValue(GenerateCli),
289298
SecuritySchemes = parseResult.GetRequiredValue(SecuritySchemes).ToImmutableArray(),

src/libs/AutoSDK.CSharp/Sources/Sources.Methods.cs

Lines changed: 135 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,135 @@ private static string GenerateSuccessResponseReturnWithoutBody(
583583
headers: {GetSuccessResponseHeadersExpression(endPoint)});";
584584
}
585585

586+
private static bool IsJsonMediaType(string mediaType)
587+
{
588+
return !string.IsNullOrWhiteSpace(mediaType) &&
589+
mediaType.Contains("json", StringComparison.OrdinalIgnoreCase);
590+
}
591+
592+
private static bool ShouldUseSystemNetHttpJsonForRequest(EndPoint endPoint)
593+
{
594+
return endPoint.Settings.GenerateMethodsUsingSystemNetHttpJson &&
595+
endPoint.Settings.UsesSystemTextJson() &&
596+
IsJsonMediaType(endPoint.RequestMediaType) &&
597+
!endPoint.IsMultipartFormData &&
598+
!endPoint.RequestType.IsBinary &&
599+
!endPoint.RequestType.IsBase64;
600+
}
601+
602+
private static bool ShouldUseSystemNetHttpJsonForSuccessResponse(EndPoint endPoint)
603+
{
604+
return endPoint.Settings.GenerateMethodsUsingSystemNetHttpJson &&
605+
endPoint.Settings.UsesSystemTextJson() &&
606+
endPoint.ContentType == ContentType.String &&
607+
endPoint.SuccessResponse.Type.CSharpTypeWithoutNullability is not "string" &&
608+
IsJsonMediaType(endPoint.SuccessResponse.MimeType) &&
609+
!endPoint.SuccessResponse.Type.UsesGeneratedJsonHelpers;
610+
}
611+
612+
private static string GenerateSystemNetHttpJsonRequestData(
613+
EndPoint endPoint)
614+
{
615+
if (endPoint.Settings.HasJsonSerializerContext())
616+
{
617+
return $@"
618+
var __httpRequestContent = global::{endPoint.Settings.Namespace}.AutoSdkPolyfills.CreateJsonContent(
619+
inputValue: request,
620+
inputType: request.GetType(),
621+
mediaType: ""{endPoint.RequestMediaType}"",
622+
jsonSerializerContext: JsonSerializerContext);
623+
__httpRequest.Content = __httpRequestContent;
624+
".RemoveBlankLinesWhereOnlyWhitespaces();
625+
}
626+
627+
return $@"
628+
var __httpRequestContent = global::{endPoint.Settings.Namespace}.AutoSdkPolyfills.CreateJsonContent(
629+
inputValue: request,
630+
mediaType: ""{endPoint.RequestMediaType}"",
631+
jsonSerializerOptions: JsonSerializerOptions);
632+
__httpRequest.Content = __httpRequestContent;
633+
".RemoveBlankLinesWhereOnlyWhitespaces();
634+
}
635+
636+
private static string GenerateSystemNetHttpJsonReadCall(
637+
EndPoint endPoint)
638+
{
639+
var type = endPoint.SuccessResponse.Type.CSharpTypeWithNullabilityForValueTypes;
640+
641+
if (endPoint.Settings.HasJsonSerializerContext())
642+
{
643+
return $"await global::{endPoint.Settings.Namespace}.AutoSdkPolyfills.ReadFromJsonAsync<{type}>(__response.Content, JsonSerializerContext, cancellationToken).ConfigureAwait(false)";
644+
}
645+
646+
return $"await global::{endPoint.Settings.Namespace}.AutoSdkPolyfills.ReadFromJsonAsync<{type}>(__response.Content, JsonSerializerOptions, cancellationToken).ConfigureAwait(false)";
647+
}
648+
649+
private static string GenerateUnbufferedSuccessResponseHandling(
650+
EndPoint endPoint,
651+
bool wrapSuccessResponse)
652+
{
653+
var jsonSerializer = endPoint.Settings.JsonSerializerType.GetSerializer();
654+
655+
if (string.IsNullOrWhiteSpace(endPoint.SuccessResponse.Type.CSharpType))
656+
{
657+
return GenerateSuccessResponseReturnWithoutBody(endPoint, wrapSuccessResponse);
658+
}
659+
660+
if (endPoint.ContentType == ContentType.String &&
661+
endPoint.SuccessResponse.Type.CSharpTypeWithoutNullability is not "string")
662+
{
663+
if (ShouldUseSystemNetHttpJsonForSuccessResponse(endPoint))
664+
{
665+
var readCall = GenerateSystemNetHttpJsonReadCall(endPoint);
666+
667+
return wrapSuccessResponse
668+
? $@"var __value = {readCall} ??
669+
throw new global::System.InvalidOperationException(""Response deserialization failed."");
670+
{GenerateSuccessResponseReturn(endPoint, "__value", wrapSuccessResponse)}"
671+
: $@"return
672+
{readCall} ??
673+
throw new global::System.InvalidOperationException(""Response deserialization failed."");";
674+
}
675+
676+
return wrapSuccessResponse
677+
? $@"using var __content = await __response.Content.ReadAsStreamAsync(
678+
#if NET5_0_OR_GREATER
679+
cancellationToken
680+
#endif
681+
).ConfigureAwait(false);
682+
683+
var __value = {jsonSerializer.GenerateDeserializeFromStreamCall("__content", endPoint.SuccessResponse.Type, endPoint.Settings.JsonSerializerContext)} ??
684+
throw new global::System.InvalidOperationException(""Response deserialization failed."");
685+
{GenerateSuccessResponseReturn(endPoint, "__value", wrapSuccessResponse)}"
686+
: $@"using var __content = await __response.Content.ReadAsStreamAsync(
687+
#if NET5_0_OR_GREATER
688+
cancellationToken
689+
#endif
690+
).ConfigureAwait(false);
691+
692+
return
693+
{jsonSerializer.GenerateDeserializeFromStreamCall("__content", endPoint.SuccessResponse.Type, endPoint.Settings.JsonSerializerContext)} ??
694+
throw new global::System.InvalidOperationException(""Response deserialization failed."");";
695+
}
696+
697+
return $@"{endPoint.ContentType switch
698+
{
699+
ContentType.Stream => "using ",
700+
_ => string.Empty,
701+
}}var __content = await __response.Content.ReadAs{endPoint.ContentType switch
702+
{
703+
ContentType.String => "String",
704+
ContentType.Stream => "Stream",
705+
_ => "ByteArray",
706+
}}Async(
707+
#if NET5_0_OR_GREATER
708+
cancellationToken
709+
#endif
710+
).ConfigureAwait(false);
711+
712+
{GenerateSuccessResponseReturn(endPoint, "__content", wrapSuccessResponse)}";
713+
}
714+
586715
public static string GenerateResponse(
587716
EndPoint endPoint,
588717
bool wrapSuccessResponse = false)
@@ -897,34 +1026,7 @@ public static string GenerateResponse(
8971026
try
8981027
{{
8991028
__response.EnsureSuccessStatusCode();
900-
901-
{endPoint.ContentType switch
902-
{
903-
ContentType.String when endPoint.SuccessResponse.Type.CSharpTypeWithoutNullability is not "string" => "using ",
904-
ContentType.Stream => "using ",
905-
_ => string.Empty,
906-
}}var __content = await __response.Content.ReadAs{endPoint.ContentType switch
907-
{
908-
ContentType.String when endPoint.SuccessResponse.Type.CSharpTypeWithoutNullability is "string" => "String",
909-
ContentType.String => "Stream",
910-
ContentType.Stream => "Stream",
911-
_ => "ByteArray",
912-
}}Async(
913-
#if NET5_0_OR_GREATER
914-
cancellationToken
915-
#endif
916-
).ConfigureAwait(false);
917-
918-
{(string.IsNullOrWhiteSpace(endPoint.SuccessResponse.Type.CSharpType)
919-
? GenerateSuccessResponseReturnWithoutBody(endPoint, wrapSuccessResponse)
920-
: endPoint is { ContentType: ContentType.String, SuccessResponse.Type.CSharpTypeWithoutNullability: not "string" } ? wrapSuccessResponse ? $@"
921-
var __value = {jsonSerializer.GenerateDeserializeFromStreamCall("__content", endPoint.SuccessResponse.Type, endPoint.Settings.JsonSerializerContext)} ??
922-
throw new global::System.InvalidOperationException(""Response deserialization failed."");
923-
{GenerateSuccessResponseReturn(endPoint, "__value", wrapSuccessResponse)}" : $@"
924-
return
925-
{jsonSerializer.GenerateDeserializeFromStreamCall("__content", endPoint.SuccessResponse.Type, endPoint.Settings.JsonSerializerContext)} ??
926-
throw new global::System.InvalidOperationException(""Response deserialization failed."");" : $@"
927-
{GenerateSuccessResponseReturn(endPoint, "__content", wrapSuccessResponse)}")}
1029+
{GenerateUnbufferedSuccessResponseHandling(endPoint, wrapSuccessResponse)}
9281030
}}
9291031
catch (global::System.Exception __ex)
9301032
{{
@@ -1034,6 +1136,11 @@ public static string GenerateRequestData(
10341136
".RemoveBlankLinesWhereOnlyWhitespaces();
10351137
}
10361138

1139+
if (ShouldUseSystemNetHttpJsonForRequest(endPoint))
1140+
{
1141+
return GenerateSystemNetHttpJsonRequestData(endPoint);
1142+
}
1143+
10371144
var requestContent = endPoint.RequestType.IsBase64
10381145
? "global::System.Convert.ToBase64String(request)"
10391146
: jsonSerializer.GenerateSerializeCall(endPoint.RequestType, endPoint.Settings.JsonSerializerContext);

src/libs/AutoSDK.CSharp/Sources/Sources.Polyfills.cs

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ public static string GeneratePolyfills(CSharpSettings settings)
1313
}
1414

1515
return $@"
16-
#if !NET6_0_OR_GREATER
1716
#nullable enable
1817
1918
namespace {settings.Namespace}
@@ -23,6 +22,7 @@ namespace {settings.Namespace}
2322
/// </summary>
2423
public static partial class AutoSdkPolyfills
2524
{{
25+
#if !NET6_0_OR_GREATER
2626
/// <summary>
2727
///
2828
/// </summary>
@@ -67,8 +67,123 @@ public static partial class AutoSdkPolyfills
6767
6868
return content.ReadAsByteArrayAsync();
6969
}}
70+
#endif
71+
72+
/// <summary>
73+
/// Creates a JSON request content instance.
74+
/// </summary>
75+
public static global::System.Net.Http.HttpContent CreateJsonContent<T>(
76+
T inputValue,
77+
string mediaType,
78+
global::System.Text.Json.JsonSerializerOptions? jsonSerializerOptions)
79+
{{
80+
if (string.IsNullOrWhiteSpace(mediaType))
81+
{{
82+
throw new global::System.ArgumentException(""Media type is required."", nameof(mediaType));
83+
}}
84+
85+
#if NET5_0_OR_GREATER
86+
return global::System.Net.Http.Json.JsonContent.Create(
87+
inputValue: inputValue,
88+
mediaType: new global::System.Net.Http.Headers.MediaTypeHeaderValue(mediaType),
89+
options: jsonSerializerOptions);
90+
#else
91+
var json = global::System.Text.Json.JsonSerializer.Serialize(inputValue, jsonSerializerOptions);
92+
return new global::System.Net.Http.StringContent(
93+
content: json,
94+
encoding: global::System.Text.Encoding.UTF8,
95+
mediaType: mediaType);
96+
#endif
97+
}}
98+
99+
/// <summary>
100+
/// Creates a JSON request content instance using a source-generated serializer context.
101+
/// </summary>
102+
public static global::System.Net.Http.HttpContent CreateJsonContent(
103+
object? inputValue,
104+
global::System.Type inputType,
105+
string mediaType,
106+
global::System.Text.Json.Serialization.JsonSerializerContext jsonSerializerContext)
107+
{{
108+
inputType = inputType ?? throw new global::System.ArgumentNullException(nameof(inputType));
109+
jsonSerializerContext = jsonSerializerContext ?? throw new global::System.ArgumentNullException(nameof(jsonSerializerContext));
110+
111+
if (string.IsNullOrWhiteSpace(mediaType))
112+
{{
113+
throw new global::System.ArgumentException(""Media type is required."", nameof(mediaType));
114+
}}
115+
116+
#if NET5_0_OR_GREATER
117+
var jsonTypeInfo = jsonSerializerContext.GetTypeInfo(inputType) ??
118+
throw new global::System.InvalidOperationException($""No JsonTypeInfo registered for '{{inputType}}'."");
119+
return global::System.Net.Http.Json.JsonContent.Create(
120+
inputValue: inputValue,
121+
jsonTypeInfo: jsonTypeInfo,
122+
mediaType: new global::System.Net.Http.Headers.MediaTypeHeaderValue(mediaType));
123+
#else
124+
var json = global::System.Text.Json.JsonSerializer.Serialize(
125+
value: inputValue,
126+
inputType: inputType,
127+
jsonSerializerContext: jsonSerializerContext);
128+
return new global::System.Net.Http.StringContent(
129+
content: json,
130+
encoding: global::System.Text.Encoding.UTF8,
131+
mediaType: mediaType);
132+
#endif
133+
}}
134+
135+
/// <summary>
136+
/// Reads JSON content into the specified type using serializer options.
137+
/// </summary>
138+
public static async global::System.Threading.Tasks.Task<T?> ReadFromJsonAsync<T>(
139+
this global::System.Net.Http.HttpContent content,
140+
global::System.Text.Json.JsonSerializerOptions? jsonSerializerOptions,
141+
global::System.Threading.CancellationToken cancellationToken)
142+
{{
143+
content = content ?? throw new global::System.ArgumentNullException(nameof(content));
144+
145+
#if NET5_0_OR_GREATER
146+
return await global::System.Net.Http.Json.HttpContentJsonExtensions.ReadFromJsonAsync<T>(
147+
content,
148+
jsonSerializerOptions,
149+
cancellationToken).ConfigureAwait(false);
150+
#else
151+
using var stream = await AutoSdkPolyfills.ReadAsStreamAsync(content, cancellationToken).ConfigureAwait(false);
152+
return await global::System.Text.Json.JsonSerializer.DeserializeAsync<T>(
153+
utf8Json: stream,
154+
options: jsonSerializerOptions,
155+
cancellationToken: cancellationToken).ConfigureAwait(false);
156+
#endif
157+
}}
158+
159+
/// <summary>
160+
/// Reads JSON content into the specified type using a source-generated serializer context.
161+
/// </summary>
162+
public static async global::System.Threading.Tasks.Task<T?> ReadFromJsonAsync<T>(
163+
this global::System.Net.Http.HttpContent content,
164+
global::System.Text.Json.Serialization.JsonSerializerContext jsonSerializerContext,
165+
global::System.Threading.CancellationToken cancellationToken)
166+
{{
167+
content = content ?? throw new global::System.ArgumentNullException(nameof(content));
168+
jsonSerializerContext = jsonSerializerContext ?? throw new global::System.ArgumentNullException(nameof(jsonSerializerContext));
169+
170+
#if NET5_0_OR_GREATER
171+
return (T?)await global::System.Net.Http.Json.HttpContentJsonExtensions.ReadFromJsonAsync(
172+
content,
173+
typeof(T),
174+
jsonSerializerContext,
175+
cancellationToken).ConfigureAwait(false);
176+
#else
177+
using var stream = await AutoSdkPolyfills.ReadAsStreamAsync(content, cancellationToken).ConfigureAwait(false);
178+
return (T?)await global::System.Text.Json.JsonSerializer.DeserializeAsync(
179+
utf8Json: stream,
180+
returnType: typeof(T),
181+
jsonSerializerContext: jsonSerializerContext,
182+
cancellationToken: cancellationToken).ConfigureAwait(false);
183+
#endif
184+
}}
70185
}}
71186
}}
72-
#endif".RemoveBlankLinesWhereOnlyWhitespaces();
187+
".RemoveBlankLinesWhereOnlyWhitespaces();
73188
}
74189
}

0 commit comments

Comments
 (0)