Skip to content
19 changes: 9 additions & 10 deletions src/InterfaceStubGenerator.Shared/Emitter.Inline.Content.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,29 +8,28 @@ namespace Refit.Generator;
internal static partial class Emitter
{
/// <summary>Builds request content assignment for an inline generated method.</summary>
/// <param name="bodyParameter">The body parameter model.</param>
/// <param name="requestLocal">The generated request message local name.</param>
/// <param name="settingsLocal">The generated settings local name.</param>
/// <param name="plan">The method-scope locals and pre-built request fragments.</param>
/// <param name="formFieldsFieldName">The cached form field descriptor array name, or null to use the reflection path.</param>
/// <param name="supportsNullable">Whether the consumer compilation supports nullable reference type syntax.</param>
/// <param name="emission">The shared emission locals and helper state.</param>
/// <param name="locals">The method-scope unique local name builder.</param>
/// <param name="methodAdditionalIndent">The additional indentation for the method body.</param>
/// <returns>The generated content assignment.</returns>
internal static string BuildInlineContent(
Comment thread
glennawatson marked this conversation as resolved.
in RequestParameterModel bodyParameter,
string requestLocal,
string settingsLocal,
in InlineMethodPlan plan,
string? formFieldsFieldName,
bool supportsNullable,
in InlineValueEmission emission,
UniqueNameBuilder locals)
int methodAdditionalIndent)
{
var bodyIndent = Indent(MethodBodyIndentation);
var bodyIndent = Indent(MethodBodyIndentation + methodAdditionalIndent);
var requestLocal = plan.RequestLocal;
var settingsLocal = plan.SettingsLocal;
var bodyParameter = plan.BodyParameter!.Value;
if (bodyParameter.BodySerializationMethod == "UrlEncoded")
{
if (IsUnrollableFormBody(bodyParameter))
{
return BuildInlineFormUnroll(bodyParameter, requestLocal, supportsNullable, emission, locals);
return BuildInlineFormUnroll(bodyParameter, requestLocal, supportsNullable, emission, plan.Locals);
}

return formFieldsFieldName is not null
Expand Down
39 changes: 23 additions & 16 deletions src/InterfaceStubGenerator.Shared/Emitter.Inline.Method.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ internal static InlineMethodFragments BuildInlineMethodFragments(
var settingsLocal = plan.SettingsLocal;
var requestLocal = plan.RequestLocal;
var emission = plan.Emission;
var bodyIndent = Indent(MethodBodyIndentation);
var innerBodyIndent = Indent(MethodBodyIndentation + 1);

var requestPrologueSource = BuildInlineRequestPrologue(request, plan, bodyIndent, out var requestPathExpression);
var requestPrologueSource = BuildInlineRequestPrologue(request, plan, innerBodyIndent, out var requestPathExpression);
var (httpMethodFieldSource, httpMethodExpression) = BuildHttpMethodField(request, uniqueNames);

// A [Url] method dispatches to an absolute URI (the validated [Url] value with any query appended), bypassing
// the base-address merge; every other method builds a relative URI merged onto the base address.
var requestUriExpression = HasUrlParameter(request)
? $"new global::System.Uri({requestPathExpression}, global::System.UriKind.Absolute)"
: BuildRelativeUriExpression(request, requestPathExpression, settingsLocal);
: BuildRelativeUriExpression(request, requestPathExpression, settingsLocal, 1);
var (formFieldsSource, formFieldsFieldName) = BuildFormFieldsField(
plan.BodyParameter,
uniqueNames,
Expand All @@ -45,19 +45,19 @@ internal static InlineMethodFragments BuildInlineMethodFragments(
// parameter (a multipart method never carries one), so the two paths never both apply.
var contentSource = plan.BodyParameter is null
? string.Empty
: BuildInlineContent(plan.BodyParameter.Value, requestLocal, settingsLocal, formFieldsFieldName, interfaceModel.SupportsNullable, emission, plan.Locals);
: BuildInlineContent(plan, formFieldsFieldName, interfaceModel.SupportsNullable, emission, 1);
if (request.IsMultipart)
{
contentSource = BuildInlineMultipartContent(request, requestLocal, settingsLocal, plan.Locals);
contentSource = BuildInlineMultipartContent(request, requestLocal, settingsLocal, plan.Locals, 1);
}

var headerSource = BuildInlineHeaders(request, requestLocal, settingsLocal);
var requestPropertySource = BuildInlineRequestProperties(request, interfaceModel, methodModel, requestLocal, settingsLocal);
var headerSource = BuildInlineHeaders(request, requestLocal, settingsLocal, 1);
var requestPropertySource = BuildInlineRequestProperties(request, interfaceModel, methodModel, requestLocal, settingsLocal, 1);

// A method that declares a positive [Timeout] stashes it on the request so the send helpers apply it; every
// other method emits nothing here and pays no per-call timeout cost.
var timeoutSource = request.TimeoutMilliseconds > 0
? $"{bodyIndent}global::Refit.GeneratedRequestRunner.SetRequestTimeout({requestLocal}, {request.TimeoutMilliseconds});\n"
? $"{innerBodyIndent}global::Refit.GeneratedRequestRunner.SetRequestTimeout({requestLocal}, {request.TimeoutMilliseconds});\n"
: string.Empty;
var opening = BuildMethodOpening(methodModel, isExplicit, isExplicit, interfaceModel.SupportsNullable);

Expand Down Expand Up @@ -101,6 +101,7 @@ internal static void AppendInlineStandardRefitMethod(
var requestLocal = plan.RequestLocal;
var bodyIndent = Indent(MethodBodyIndentation);
var methodIndent = Indent(MethodMemberIndentation);
var httpRequestMessageIndent = Indent(MethodBodyIndentation + 1);

var prologue = new PooledStringBuilder();
var requestPathExpression = AppendInlineRequestPrologue(prologue, request, plan, bodyIndent);
Expand All @@ -110,7 +111,7 @@ internal static void AppendInlineStandardRefitMethod(
// the base-address merge; every other method builds a relative URI merged onto the base address.
var requestUriExpression = HasUrlParameter(request)
? $"new global::System.Uri({requestPathExpression}, global::System.UriKind.Absolute)"
: BuildRelativeUriExpression(request, requestPathExpression, settingsLocal);
: BuildRelativeUriExpression(request, requestPathExpression, settingsLocal, 0);
var (formFieldsSource, formFieldsFieldName) = BuildFormFieldsField(
plan.BodyParameter,
uniqueNames,
Expand All @@ -121,13 +122,13 @@ internal static void AppendInlineStandardRefitMethod(
// parameter (a multipart method never carries one), so the two paths never both apply.
var contentSource = plan.BodyParameter is null
? string.Empty
: BuildInlineContent(plan.BodyParameter.Value, requestLocal, settingsLocal, formFieldsFieldName, interfaceModel.SupportsNullable, plan.Emission, plan.Locals);
: BuildInlineContent(plan, formFieldsFieldName, interfaceModel.SupportsNullable, plan.Emission, 0);
if (request.IsMultipart)
{
contentSource = BuildInlineMultipartContent(request, requestLocal, settingsLocal, plan.Locals);
contentSource = BuildInlineMultipartContent(request, requestLocal, settingsLocal, plan.Locals, 0);
}

var headerSource = BuildInlineHeaders(request, requestLocal, settingsLocal);
var headerSource = BuildInlineHeaders(request, requestLocal, settingsLocal, 0);

// A method that declares a positive [Timeout] stashes it on the request so the send helpers apply it; every
// other method emits nothing here and pays no per-call timeout cost.
Expand All @@ -149,11 +150,13 @@ internal static void AppendInlineStandardRefitMethod(
.Append(bodyIndent).Append("var ").Append(settingsLocal).Append(" = ").Append(settingsFieldName).AppendLine(";")
.Append(prologue)
.Append(bodyIndent).Append("var ").Append(requestLocal)
.Append(" = new global::System.Net.Http.HttpRequestMessage(").Append(httpMethodExpression)
.Append(", ").Append(requestUriExpression).AppendLine(");")
.AppendLine(" = new global::System.Net.Http.HttpRequestMessage(")
.Append(httpRequestMessageIndent).Append(httpMethodExpression).AppendLine(",")
.Append(httpRequestMessageIndent).Append(requestUriExpression).AppendLine()
.Append(bodyIndent).AppendLine(");")
.Append(contentSource)
.Append(headerSource);
AppendInlineRequestProperties(builder, request, interfaceModel, methodModel, requestLocal, settingsLocal);
AppendInlineRequestProperties(builder, request, interfaceModel, methodModel, requestLocal, settingsLocal, 0);

// The optional per-call timeout, then the send-and-deserialize statement (appended straight into the buffer,
// dispatching on the return shape), then the method's closing brace.
Expand All @@ -179,14 +182,18 @@ internal static void AppendInlineObservableRefitMethod(
var settingsLocal = plan.SettingsLocal;
var requestLocal = plan.RequestLocal;
var bodyIndent = Indent(MethodBodyIndentation);
var innerBodyIndent = Indent(MethodBodyIndentation + 1);
var methodIndent = Indent(MethodMemberIndentation);
var prologue = fragments.RequestPrologueSource;
var httpMethod = fragments.HttpMethodExpression;
var uri = fragments.RequestUriExpression;

var methodPrefix = $"{plan.ParamInfoBuilder}{fragments.FormFieldsSource}{fragments.HttpMethodFieldSource}{fragments.Opening}{bodyIndent}var {settingsLocal} = {settingsFieldName};\n";
var requestConstruction = $$"""
{{prologue}}{{bodyIndent}}var {{requestLocal}} = new global::System.Net.Http.HttpRequestMessage({{httpMethod}}, {{uri}});
{{prologue}}{{innerBodyIndent}}var {{requestLocal}} = new global::System.Net.Http.HttpRequestMessage(
{{innerBodyIndent}} {{httpMethod}},
{{innerBodyIndent}} {{uri}}
{{innerBodyIndent}});
{{fragments.ContentSource}}{{fragments.HeaderSource}}{{fragments.RequestPropertySource}}{{fragments.TimeoutSource}}
""";
var buildRequestLocal = plan.Locals.New("BuildRefitRequest");
Expand Down
14 changes: 9 additions & 5 deletions src/InterfaceStubGenerator.Shared/Emitter.Inline.Multipart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ internal static partial class Emitter
/// <param name="settingsLocal">The generated settings local name.</param>
/// <param name="locals">The method-scope unique local name builder.</param>
/// <returns>The generated multipart content statements, ending with the request-content assignment.</returns>
/// <param name="methodAdditionalIndent">The additional indentation for the method body.</param>
internal static string BuildInlineMultipartContent(
in RequestModel request,
string requestLocal,
string settingsLocal,
UniqueNameBuilder locals)
UniqueNameBuilder locals,
int methodAdditionalIndent)
{
var bodyIndent = Indent(MethodBodyIndentation);
var bodyIndent = Indent(MethodBodyIndentation + methodAdditionalIndent);
var contentLocal = locals.New("refitMultipart");

var sb = new PooledStringBuilder();
Expand All @@ -52,7 +54,7 @@ internal static string BuildInlineMultipartContent(
{
if (parameter is { Kind: RequestParameterKind.MultipartPart, MultipartPart: { } part })
{
AppendMultipartPart(sb, parameter, part, settingsLocal, contentLocal, locals);
AppendMultipartPart(sb, parameter, part, settingsLocal, contentLocal, locals, methodAdditionalIndent);
}
}

Expand All @@ -69,15 +71,17 @@ internal static string BuildInlineMultipartContent(
/// <param name="settingsLocal">The generated settings local name.</param>
/// <param name="contentLocal">The generated multipart content local name.</param>
/// <param name="locals">The method-scope unique local name builder.</param>
/// <param name="methodAdditionalIndent">The additional indentation for the method body.</param>
internal static void AppendMultipartPart(
PooledStringBuilder sb,
in RequestParameterModel parameter,
MultipartPartModel part,
string settingsLocal,
string contentLocal,
UniqueNameBuilder locals)
UniqueNameBuilder locals,
int methodAdditionalIndent)
{
var bodyIndent = Indent(MethodBodyIndentation);
var bodyIndent = Indent(MethodBodyIndentation + methodAdditionalIndent);
var valueExpression = $"@{parameter.Name}";

// A reference-typed enumerable adds one part per element; a null collection contributes no parts, matching the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,11 +212,13 @@ internal static Dictionary<string, string> BuildParameterInfoFields(
/// <param name="request">The parsed request model.</param>
/// <param name="uniqueNameLookup">The map of parameter name to cached attribute-provider field name.</param>
/// <param name="emission">The shared emission locals and helper state.</param>
/// <param name="methodAdditionalIndent">The additional indentation for the method body.</param>
/// <returns>The generated argument list fragment.</returns>
internal static string GetParametersArg(
in RequestModel request,
Dictionary<string, string> uniqueNameLookup,
in InlineValueEmission emission)
in InlineValueEmission emission,
int methodAdditionalIndent)
{
// A single pre-encoded path parameter switches every replacement to the overload carrying the
// per-value encoding flag, because a params call cannot mix tuple arities.
Expand All @@ -234,12 +236,14 @@ internal static string GetParametersArg(
replacements.Sort(static (left, right) => left.Start.CompareTo(right.Start));

var parametersSb = new PooledStringBuilder();
var indent = Indent(HttpAdditionalBuildRequestPathIndentation + MethodBodyIndentation + methodAdditionalIndent);

var first = true;
foreach (var replacement in replacements)
{
if (!first)
{
_ = parametersSb.Append(", ");
_ = parametersSb.AppendLine(",").Append(indent);
}

first = false;
Expand Down
Loading
Loading