diff --git a/src/InterfaceStubGenerator.Shared/Emitter.Inline.Content.cs b/src/InterfaceStubGenerator.Shared/Emitter.Inline.Content.cs
index fa4552f63..8d71a284d 100644
--- a/src/InterfaceStubGenerator.Shared/Emitter.Inline.Content.cs
+++ b/src/InterfaceStubGenerator.Shared/Emitter.Inline.Content.cs
@@ -8,29 +8,28 @@ namespace Refit.Generator;
internal static partial class Emitter
{
/// Builds request content assignment for an inline generated method.
- /// The body parameter model.
- /// The generated request message local name.
- /// The generated settings local name.
+ /// The method-scope locals and pre-built request fragments.
/// The cached form field descriptor array name, or null to use the reflection path.
/// Whether the consumer compilation supports nullable reference type syntax.
/// The shared emission locals and helper state.
- /// The method-scope unique local name builder.
+ /// The additional indentation for the method body.
/// The generated content assignment.
internal static string BuildInlineContent(
- 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
diff --git a/src/InterfaceStubGenerator.Shared/Emitter.Inline.Method.cs b/src/InterfaceStubGenerator.Shared/Emitter.Inline.Method.cs
index 9983cfe89..8acc1a780 100644
--- a/src/InterfaceStubGenerator.Shared/Emitter.Inline.Method.cs
+++ b/src/InterfaceStubGenerator.Shared/Emitter.Inline.Method.cs
@@ -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,
@@ -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);
@@ -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);
@@ -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,
@@ -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.
@@ -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.
@@ -179,6 +182,7 @@ 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;
@@ -186,7 +190,10 @@ internal static void AppendInlineObservableRefitMethod(
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");
diff --git a/src/InterfaceStubGenerator.Shared/Emitter.Inline.Multipart.cs b/src/InterfaceStubGenerator.Shared/Emitter.Inline.Multipart.cs
index 25877cc08..c11850be4 100644
--- a/src/InterfaceStubGenerator.Shared/Emitter.Inline.Multipart.cs
+++ b/src/InterfaceStubGenerator.Shared/Emitter.Inline.Multipart.cs
@@ -35,13 +35,15 @@ internal static partial class Emitter
/// The generated settings local name.
/// The method-scope unique local name builder.
/// The generated multipart content statements, ending with the request-content assignment.
+ /// The additional indentation for the method body.
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();
@@ -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);
}
}
@@ -69,15 +71,17 @@ internal static string BuildInlineMultipartContent(
/// The generated settings local name.
/// The generated multipart content local name.
/// The method-scope unique local name builder.
+ /// The additional indentation for the method body.
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
diff --git a/src/InterfaceStubGenerator.Shared/Emitter.Inline.ParameterInfo.cs b/src/InterfaceStubGenerator.Shared/Emitter.Inline.ParameterInfo.cs
index bf9e122f7..d3c715064 100644
--- a/src/InterfaceStubGenerator.Shared/Emitter.Inline.ParameterInfo.cs
+++ b/src/InterfaceStubGenerator.Shared/Emitter.Inline.ParameterInfo.cs
@@ -212,11 +212,13 @@ internal static Dictionary BuildParameterInfoFields(
/// The parsed request model.
/// The map of parameter name to cached attribute-provider field name.
/// The shared emission locals and helper state.
+ /// The additional indentation for the method body.
/// The generated argument list fragment.
internal static string GetParametersArg(
in RequestModel request,
Dictionary 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.
@@ -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;
diff --git a/src/InterfaceStubGenerator.Shared/Emitter.Inline.Path.cs b/src/InterfaceStubGenerator.Shared/Emitter.Inline.Path.cs
index e5a5c6fc8..5fbe22155 100644
--- a/src/InterfaceStubGenerator.Shared/Emitter.Inline.Path.cs
+++ b/src/InterfaceStubGenerator.Shared/Emitter.Inline.Path.cs
@@ -16,7 +16,7 @@ internal static partial class Emitter
/// ReadOnlySpan overload accepts via the array-to-span conversion. The array element type is inferred from the
/// tuple values rather than stated, so no nullable reference annotation is emitted into a pre-C# 8 consumer.
internal static string WrapPathReplacements(string tuples, bool supportsCollectionExpressions) =>
- supportsCollectionExpressions ? $", [{tuples}]" : $", new[] {{ {tuples} }}";
+ supportsCollectionExpressions ? $"[{tuples}]" : $"new[] {{ {tuples} }}";
/// Determines whether any path parameter passes its value through pre-encoded.
/// The parsed request model.
@@ -64,33 +64,59 @@ internal static void AppendPathTuple(
/// The shared emission locals and helper state.
/// The generated settings local name.
/// The default path builder argument fragment.
+ /// The additional indentation for the method body.
/// The generated path expression.
internal static string BuildInlinePathExpression(
in RequestModel request,
Dictionary parameterInfoNames,
in InlineValueEmission emission,
string settingsLocal,
- string parameters)
+ string parameters,
+ int methodAdditionalIndent)
{
// A template with placeholders but no bound path parameters still runs the unmatched-placeholder
// check so AllowUnmatchedRouteParameters keeps its reflection-path semantics.
- return TryBuildInlinePathFastExpression(request, parameterInfoNames, emission)
- ?? (parameters.Length > 0 || request.Path.IndexOf('{') >= 0
- ? $"global::Refit.GeneratedRequestRunner.BuildRequestPath({ToCSharpStringLiteral(request.Path)}, {settingsLocal}.AllowUnmatchedRouteParameters{parameters})"
- : ToCSharpStringLiteral(request.Path));
+ var expression = TryBuildInlinePathFastExpression(request, parameterInfoNames, emission, methodAdditionalIndent);
+ if (expression is not null)
+ {
+ return expression;
+ }
+
+ if (parameters.Length > 0 || request.Path.IndexOf('{') >= 0)
+ {
+ var indent = Indent(HttpAdditionalBuildRequestPathIndentation + MethodBodyIndentation + methodAdditionalIndent);
+ var stringBuilder = new PooledStringBuilder()
+ .AppendLine("global::Refit.GeneratedRequestRunner.BuildRequestPath(")
+ .Append(indent).Append(ToCSharpStringLiteral(request.Path)).AppendLine(",")
+ .Append(indent).Append(settingsLocal).Append(".AllowUnmatchedRouteParameters");
+
+ if (parameters.Length > 0)
+ {
+ _ = stringBuilder
+ .AppendLine(",")
+ .Append(indent)
+ .Append(parameters);
+ }
+
+ return stringBuilder.Append(')').ToString();
+ }
+
+ return ToCSharpStringLiteral(request.Path);
}
/// Builds the allocation-free path expression for a single span-formattable path parameter, or null.
/// The parsed request model.
/// The map of parameter name to cached attribute-provider field name.
/// The shared emission locals and helper state.
+ /// The additional indentation for the method body.
/// The path expression using the span-formattable fast overload, or null to use the default path building.
/// The default-formatting branch formats the value straight into the path buffer (net6+ integers with no
/// escaping, net10+ span-escaped values); a customized IUrlParameterFormatter falls back to the string overload.
internal static string? TryBuildInlinePathFastExpression(
in RequestModel request,
Dictionary parameterInfoNames,
- in InlineValueEmission emission)
+ in InlineValueEmission emission,
+ int methodAdditionalIndent)
{
RequestParameterModel? pathParameter = null;
foreach (var parameter in request.Parameters)
@@ -122,20 +148,47 @@ internal static string BuildInlinePathExpression(
var start = location.Start.GetOffset(pathLength);
var end = location.End.GetOffset(pathLength);
var template = ToCSharpStringLiteral(request.Path);
- var settingsLocal = emission.SettingsLocal;
- var allowUnmatched = $"{settingsLocal}.AllowUnmatchedRouteParameters";
+
+ var allowUnmatched = $"{emission.SettingsLocal}.AllowUnmatchedRouteParameters";
var valueExpression = $"@{pathParameter.Value.Name}";
_ = parameterInfoNames.TryGetValue(pathParameter.Value.Name, out var providerField);
- const string runner = "global::Refit.GeneratedRequestRunner.BuildRequestPath";
- var fastExpression = valueFormat.IsUrlSafeSpanFormattable
- ? $"{runner}({template}, {allowUnmatched}, ({start}, {end}), {valueExpression})"
- : $"{runner}({template}, {allowUnmatched}, ({start}, {end}), {valueExpression}, {ToNullableCSharpStringLiteral(valueFormat.Format)})";
+ var indentBuildRelativeUriParameter = Indent(HttpAdditionalBuildRelativeUriIndentation + MethodBodyIndentation + methodAdditionalIndent);
+ var indentBuilderBuildRequestPath = Indent(HttpAdditionalBuildRequestPathIndentation + MethodBodyIndentation + methodAdditionalIndent);
+ var stringBuilder = new PooledStringBuilder().Append('(').AppendLine(emission.UseDefaultFormattingLocal)
+ .Append(indentBuildRelativeUriParameter).Append("? ");
+ _ = BuildCommonInlinePathFastExpression(stringBuilder, indentBuilderBuildRequestPath, template, allowUnmatched)
+ .Append('(').Append(start).Append(", ").Append(end).AppendLine("),")
+ .Append(indentBuilderBuildRequestPath).Append(valueExpression);
+ if (!valueFormat.IsUrlSafeSpanFormattable)
+ {
+ _ = stringBuilder
+ .AppendLine(",")
+ .Append(indentBuilderBuildRequestPath).Append(ToNullableCSharpStringLiteral(valueFormat.Format));
+ }
+
var customTuple =
$"(({start}, {end}), {EmitFormatUrlParameter(valueExpression, providerField, $"typeof({pathParameter.Value.Type})", emission)})";
- var customReplacements = WrapPathReplacements(customTuple, emission.SupportsCollectionExpressions);
- var customExpression = $"{runner}({template}, {allowUnmatched}{customReplacements})";
+ _ = stringBuilder
+ .AppendLine(")")
+ .Append(indentBuildRelativeUriParameter).Append(": ");
+ return BuildCommonInlinePathFastExpression(stringBuilder, indentBuilderBuildRequestPath, template, allowUnmatched)
+ .Append(WrapPathReplacements(customTuple, emission.SupportsCollectionExpressions)).Append("))").ToString();
+ }
- return $"({emission.UseDefaultFormattingLocal} ? {fastExpression} : {customExpression})";
+ /// Writes the shared inline path-building helper call for generated request path emission.
+ /// The pooled string builder receiving the generated source.
+ /// The indentation prefix used for the helper call arguments.
+ /// The escaped path template literal emitted into the generated code.
+ /// The expression that controls whether unmatched route parameters are allowed.
+ /// The same pooled string builder, allowing chained emission.
+ internal static PooledStringBuilder BuildCommonInlinePathFastExpression(PooledStringBuilder builder, string indentBuilderBuildRequestPath, string template, string allowUnmatched)
+ {
+ const string runner = "global::Refit.GeneratedRequestRunner.BuildRequestPath";
+ return builder
+ .Append(runner).AppendLine("(")
+ .Append(indentBuilderBuildRequestPath).Append(template).AppendLine(",")
+ .Append(indentBuilderBuildRequestPath).Append(allowUnmatched).AppendLine(",")
+ .Append(indentBuilderBuildRequestPath);
}
}
diff --git a/src/InterfaceStubGenerator.Shared/Emitter.Inline.RequestProperties.cs b/src/InterfaceStubGenerator.Shared/Emitter.Inline.RequestProperties.cs
index 2c2e2dcfb..dbd4fab04 100644
--- a/src/InterfaceStubGenerator.Shared/Emitter.Inline.RequestProperties.cs
+++ b/src/InterfaceStubGenerator.Shared/Emitter.Inline.RequestProperties.cs
@@ -26,6 +26,7 @@ internal static partial class Emitter
/// The method model being emitted.
/// The generated request message local name.
/// The generated settings local name.
+ /// The additional indentation for the method body.
/// The generated request option/property statements.
/// The cold-observable shape reuses this block inside a string-interpolated construction block, so it
/// materializes it here; the standard shape appends it straight into the interface buffer.
@@ -34,10 +35,11 @@ internal static string BuildInlineRequestProperties(
InterfaceModel interfaceModel,
in MethodModel methodModel,
string requestLocal,
- string settingsLocal)
+ string settingsLocal,
+ int methodAdditionalIndent)
{
var sb = new PooledStringBuilder();
- AppendInlineRequestProperties(sb, request, interfaceModel, methodModel, requestLocal, settingsLocal);
+ AppendInlineRequestProperties(sb, request, interfaceModel, methodModel, requestLocal, settingsLocal, methodAdditionalIndent);
return sb.ToString();
}
@@ -48,15 +50,17 @@ internal static string BuildInlineRequestProperties(
/// The method model being emitted.
/// The generated request message local name.
/// The generated settings local name.
+ /// The additional indentation for the method body.
internal static void AppendInlineRequestProperties(
PooledStringBuilder sb,
in RequestModel request,
InterfaceModel interfaceModel,
in MethodModel methodModel,
string requestLocal,
- string settingsLocal)
+ string settingsLocal,
+ int methodAdditionalIndent)
{
- var bodyIndent = Indent(MethodBodyIndentation);
+ var bodyIndent = Indent(MethodBodyIndentation + methodAdditionalIndent);
_ = sb.Append(bodyIndent)
.Append("global::Refit.GeneratedRequestRunner.AddConfiguredRequestOptions(")
.Append(requestLocal).Append(ArgumentSeparator).Append(settingsLocal)
diff --git a/src/InterfaceStubGenerator.Shared/Emitter.Inline.cs b/src/InterfaceStubGenerator.Shared/Emitter.Inline.cs
index 22b80f86b..2a749c1e2 100644
--- a/src/InterfaceStubGenerator.Shared/Emitter.Inline.cs
+++ b/src/InterfaceStubGenerator.Shared/Emitter.Inline.cs
@@ -141,8 +141,9 @@ internal static void BuildInlineRefitMethod(
enumFormatterScope,
paramInfoSb,
interfaceModel.SupportsCollectionExpressions);
- var parameters = GetParametersArg(request, parameterInfoNames, emission);
- var pathExpression = BuildInlinePathExpression(request, parameterInfoNames, emission, settingsLocal, parameters);
+ var methodAdditionalIntent = methodModel.ReturnTypeMetadata == ReturnTypeInfo.Observable ? 1 : 0;
+ var parameters = GetParametersArg(request, parameterInfoNames, emission, methodAdditionalIntent);
+ var pathExpression = BuildInlinePathExpression(request, parameterInfoNames, emission, settingsLocal, parameters, methodAdditionalIntent);
var plan = new InlineMethodPlan(
bodyParameter,
@@ -474,13 +475,14 @@ internal static void AppendInlineSendAsyncReturn(
/// The parsed request model.
/// The generated request message local name.
/// The generated settings local name, read for the header validation flag.
+ /// The additional indentation for the method body.
/// The generated header statements.
- internal static string BuildInlineHeaders(in RequestModel request, string requestLocal, string settingsLocal)
+ internal static string BuildInlineHeaders(in RequestModel request, string requestLocal, string settingsLocal, int methodAdditionalIndent)
{
// Append directly into a pooled buffer allocated only once a header is actually emitted; the previous
// string[] + interpolated-fragment + ConcatParts shape allocated the array (and a validate-flag string) even
// for the common header-less method. The emitted text is identical.
- var bodyIndent = Indent(MethodBodyIndentation);
+ var bodyIndent = Indent(MethodBodyIndentation + methodAdditionalIndent);
PooledStringBuilder? sb = null;
foreach (var header in request.StaticHeaders)
{
@@ -586,13 +588,27 @@ internal static bool HasUrlParameter(in RequestModel request) =>
/// The parsed request model.
/// The built path-and-query expression.
/// The generated settings local name.
+ /// The additional indentation for the method body.
/// The generated BuildRelativeUri call.
/// A [QueryUriFormat] method re-encodes the whole path and query with the attribute's UriFormat,
/// matching the reflection builder's final GetComponents pass; every other method uses the direct relative URI.
- internal static string BuildRelativeUriExpression(in RequestModel request, string requestPathExpression, string settingsLocal) =>
- request.QueryUriFormat is { } queryUriFormat
- ? $"global::Refit.GeneratedRequestRunner.BuildRelativeUri(this.Client, {requestPathExpression}, {settingsLocal}.UrlResolution, (global::System.UriFormat){queryUriFormat})"
- : $"global::Refit.GeneratedRequestRunner.BuildRelativeUri(this.Client, {requestPathExpression}, {settingsLocal}.UrlResolution)";
+ internal static string BuildRelativeUriExpression(in RequestModel request, string requestPathExpression, string settingsLocal, int methodAdditionalIndent)
+ {
+ var bodyAndExtraIndent = Indent(HttpAdditionalBuildRelativeUriIndentation + MethodBodyIndentation + methodAdditionalIndent);
+ var stringBuilder = new PooledStringBuilder()
+ .AppendLine("global::Refit.GeneratedRequestRunner.BuildRelativeUri(")
+ .Append(bodyAndExtraIndent).AppendLine("this.Client,")
+ .Append(bodyAndExtraIndent).Append(requestPathExpression).AppendLine(",")
+ .Append(bodyAndExtraIndent).Append(settingsLocal).Append(".UrlResolution");
+ if (request.QueryUriFormat is { } queryUriFormat)
+ {
+ _ = stringBuilder
+ .AppendLine(",")
+ .Append(bodyAndExtraIndent).Append("(global::System.UriFormat)").Append(queryUriFormat);
+ }
+
+ return stringBuilder.Append(')').ToString();
+ }
/// Finds the first request parameter of the given kind.
/// The request model to inspect.
diff --git a/src/InterfaceStubGenerator.Shared/Emitter.cs b/src/InterfaceStubGenerator.Shared/Emitter.cs
index 5e1e09b5a..01ed108c4 100644
--- a/src/InterfaceStubGenerator.Shared/Emitter.cs
+++ b/src/InterfaceStubGenerator.Shared/Emitter.cs
@@ -58,6 +58,12 @@ internal static partial class Emitter
/// Indentation level for generated method constraints and statements.
private const int MethodBodyIndentation = 4;
+ /// Indentation level for generated BuildRelativeUri relative to .
+ private const int HttpAdditionalBuildRelativeUriIndentation = 2;
+
+ /// Indentation level for generated BuildRequestPath relative to .
+ private const int HttpAdditionalBuildRequestPathIndentation = 3;
+
/// The generated attribute that identifies source produced by this generator.
private static readonly string GeneratedCodeAttribute = BuildGeneratedCodeAttribute();
diff --git a/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.InlineEmissionAndFallbacks.cs b/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.InlineEmissionAndFallbacks.cs
index 8188ea28d..815520ca0 100644
--- a/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.InlineEmissionAndFallbacks.cs
+++ b/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.InlineEmissionAndFallbacks.cs
@@ -60,7 +60,8 @@ public async Task SwitchOnGeneratesInlineForQueryUriFormat()
generatedRequestBuilding: true);
await Assert.That(generated).DoesNotContain(ReflectiveRequestBuilderCall);
- await Assert.That(generated).Contains(".UrlResolution, (global::System.UriFormat)");
+ await Assert.That(generated).Contains(".UrlResolution,");
+ await Assert.That(generated).Contains("(global::System.UriFormat)");
}
/// Verifies custom HTTP method attributes are discovered but fall back to the runtime builder.
@@ -113,7 +114,7 @@ public interface IGeneratedClient
// The custom verb is allocated once in a static field and the request references it, not a per-call allocation.
await Assert.That(generated).Contains("private static readonly global::System.Net.Http.HttpMethod ______httpMethod = new global::System.Net.Http.HttpMethod(\"PURGE\");");
- await Assert.That(generated).Contains("new global::System.Net.Http.HttpRequestMessage(______httpMethod,");
+ await Assert.That(generated).Contains("______httpMethod,");
}
/// Verifies a custom HTTP QUERY verb attribute (a draft-standard body-carrying method) with an explicit
@@ -218,7 +219,7 @@ public async Task SwitchOnRemovesWhitespaceAndEmptyQueryKeysFromInlineConstantPa
GeneratedClientHintName,
generatedRequestBuilding: true);
- await Assert.That(generated).Contains("BuildRelativeUri(this.Client, \"/foo?one=1&two\"");
+ await Assert.That(generated).Contains("\"/foo?one=1&two\"");
await Assert.That(generated).DoesNotContain("drop");
await Assert.That(generated).DoesNotContain(ReflectiveRequestBuilderCall);
}
diff --git a/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.PropertiesFormBodiesAndPaths.cs b/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.PropertiesFormBodiesAndPaths.cs
index 735a9d437..5e6d55252 100644
--- a/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.PropertiesFormBodiesAndPaths.cs
+++ b/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.PropertiesFormBodiesAndPaths.cs
@@ -304,7 +304,9 @@ public interface IGeneratedClient
var generated = result.GeneratedSources[GeneratedClientHintName];
await Assert.That(result.CompilesWithoutErrors).IsTrue();
- await Assert.That(generated).Contains("""GeneratedRequestRunner.BuildRequestPath("/a/{aVal}", refitSettings.AllowUnmatchedRouteParameters, [((3, 9), """);
+ await Assert.That(generated).Contains(""" "/a/{aVal}",""");
+ await Assert.That(generated).Contains("refitSettings.AllowUnmatchedRouteParameters,");
+ await Assert.That(generated).Contains("[((3, 9), ");
}
/// Verifies that path parameters are supported by the source generator.
@@ -330,7 +332,7 @@ public interface IGeneratedClient
var generated = result.GeneratedSources[GeneratedClientHintName];
await Assert.That(result.CompilesWithoutErrors).IsTrue();
- await Assert.That(generated).Contains("""GeneratedRequestRunner.BuildRequestPath("/a?b={bVal}", refitSettings.AllowUnmatchedRouteParameters, [((5, 11), """);
+ await Assert.That(generated).Contains("[((5, 11), ");
}
/// Verifies that auto-appended query parameters generate inline query construction.
diff --git a/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.cs b/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.cs
index 6610e0c7f..dd4b412df 100644
--- a/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.cs
+++ b/src/tests/Refit.GeneratorTests/GeneratedRequestBuildingTests.cs
@@ -210,7 +210,7 @@ public async Task SwitchOnStripsFragmentsFromInlineConstantPaths()
GeneratedClientHintName,
generatedRequestBuilding: true);
- await Assert.That(generated).Contains("BuildRelativeUri(this.Client, \"/foo?key=value\"");
+ await Assert.That(generated).Contains("\"/foo?key=value\",");
await Assert.That(generated).DoesNotContain("#name");
await Assert.That(generated).DoesNotContain(ReflectiveRequestBuilderCall);
}
@@ -228,7 +228,7 @@ public async Task SwitchOnStripsQueryAfterFragmentFromInlineConstantPaths()
GeneratedClientHintName,
generatedRequestBuilding: true);
- await Assert.That(generated).Contains("BuildRelativeUri(this.Client, \"/foo\"");
+ await Assert.That(generated).Contains("\"/foo\",");
await Assert.That(generated).DoesNotContain("?key=value");
await Assert.That(generated).DoesNotContain(ReflectiveRequestBuilderCall);
}
@@ -246,7 +246,7 @@ public async Task SwitchOnRemovesEmptyQueryKeysFromInlineConstantPaths()
GeneratedClientHintName,
generatedRequestBuilding: true);
- await Assert.That(generated).Contains("BuildRelativeUri(this.Client, \"/foo?key=&two=2\"");
+ await Assert.That(generated).Contains("\"/foo?key=&two=2\",");
await Assert.That(generated).DoesNotContain("=drop");
await Assert.That(generated).DoesNotContain(ReflectiveRequestBuilderCall);
}
@@ -363,9 +363,9 @@ public interface IGeneratedClient
GeneratedClientHintName,
generatedRequestBuilding: true);
- await Assert.That(generated).Contains("BuildRelativeUri(this.Client, \"/global\"");
- await Assert.That(generated).Contains("BuildRelativeUri(this.Client, \"/alias\"");
- await Assert.That(generated).Contains("BuildRelativeUri(this.Client, \"/qualified\"");
+ await Assert.That(generated).Contains("\"/global\",");
+ await Assert.That(generated).Contains("\"/alias\",");
+ await Assert.That(generated).Contains("\"/qualified\",");
await Assert.That(generated).Contains("HttpMethod.Get");
await Assert.That(generated).Contains("HttpMethod.Post");
await Assert.That(generated).Contains("HttpMethod.Put");
diff --git a/src/tests/Refit.GeneratorTests/RelativePathResolutionLiveTests.cs b/src/tests/Refit.GeneratorTests/RelativePathResolutionLiveTests.cs
index 8213a9732..e0a75edee 100644
--- a/src/tests/Refit.GeneratorTests/RelativePathResolutionLiveTests.cs
+++ b/src/tests/Refit.GeneratorTests/RelativePathResolutionLiveTests.cs
@@ -53,7 +53,7 @@ public async Task GeneratedInlinesNoLeadingSlashPath()
{
var generated = string.Concat(Fixture.RunGenerator(ApiSource, generatedRequestBuilding: true).GeneratedSources.Values);
- await Assert.That(generated).Contains("BuildRequestPath(\"relative/{id}\"");
+ await Assert.That(generated).Contains("\"relative/{id}\",");
await Assert.That(generated).DoesNotContain("BuildRestResultFuncForMethod(\"Relative\"");
}
diff --git a/src/tests/Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs b/src/tests/Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs
index 6f0be4fca..68998032e 100644
--- a/src/tests/Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs
+++ b/src/tests/Refit.GeneratorTests/_snapshots/GeneratedTest.ShouldEmitAllFiles#IGeneratedClient.g.verified.cs
@@ -49,7 +49,13 @@ public RefitGeneratorTestIGeneratedClient(global::System.Net.Http.HttpClient cli
public global::System.Threading.Tasks.Task Get()
{
var refitSettings = _settings;
- var refitRequest = new global::System.Net.Http.HttpRequestMessage(global::System.Net.Http.HttpMethod.Get, global::Refit.GeneratedRequestRunner.BuildRelativeUri(this.Client, "/users", refitSettings.UrlResolution));
+ var refitRequest = new global::System.Net.Http.HttpRequestMessage(
+ global::System.Net.Http.HttpMethod.Get,
+ global::Refit.GeneratedRequestRunner.BuildRelativeUri(
+ this.Client,
+ "/users",
+ refitSettings.UrlResolution)
+ );
global::Refit.GeneratedRequestRunner.AddConfiguredRequestOptions(refitRequest, refitSettings, typeof(global::RefitGeneratorTest.IGeneratedClient));
global::Refit.GeneratedRequestRunner.AddRequestProperty(refitRequest, global::Refit.HttpRequestMessageOptions.MethodName, "Get");
global::Refit.GeneratedRequestRunner.AddRequestProperty(refitRequest, global::Refit.HttpRequestMessageOptions.RelativePathTemplate, "/users");
diff --git a/src/tests/Refit.GeneratorTests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs b/src/tests/Refit.GeneratorTests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs
index 50e9db9ff..bc5f9d71f 100644
--- a/src/tests/Refit.GeneratorTests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs
+++ b/src/tests/Refit.GeneratorTests/_snapshots/InterfaceStubGeneratorTests.FindInterfacesSmokeTest#IGitHubApi.g.verified.cs
@@ -50,7 +50,16 @@ public RefitTestsIGitHubApi(global::System.Net.Http.HttpClient client, global::R
{
var refitSettings = _settings;
var refitUseDefaultFormatting = global::Refit.GeneratedRequestRunner.UsesDefaultUrlParameterFormatting(refitSettings);
- var refitRequest = new global::System.Net.Http.HttpRequestMessage(global::System.Net.Http.HttpMethod.Get, global::Refit.GeneratedRequestRunner.BuildRelativeUri(this.Client, global::Refit.GeneratedRequestRunner.BuildRequestPath("/users/{username}", refitSettings.AllowUnmatchedRouteParameters, [((7, 17), refitUseDefaultFormatting ? (@userName) : global::Refit.GeneratedRequestRunner.FormatUrlParameter(refitSettings, @userName, global::Refit.GeneratedParameterAttributeProvider.Empty, typeof(string)))]), refitSettings.UrlResolution));
+ var refitRequest = new global::System.Net.Http.HttpRequestMessage(
+ global::System.Net.Http.HttpMethod.Get,
+ global::Refit.GeneratedRequestRunner.BuildRelativeUri(
+ this.Client,
+ global::Refit.GeneratedRequestRunner.BuildRequestPath(
+ "/users/{username}",
+ refitSettings.AllowUnmatchedRouteParameters,
+ [((7, 17), refitUseDefaultFormatting ? (@userName) : global::Refit.GeneratedRequestRunner.FormatUrlParameter(refitSettings, @userName, global::Refit.GeneratedParameterAttributeProvider.Empty, typeof(string)))]),
+ refitSettings.UrlResolution)
+ );
global::Refit.GeneratedRequestRunner.SetHeader(refitRequest, "User-Agent", "Refit Integration Tests", refitSettings.ValidateHeaders);
global::Refit.GeneratedRequestRunner.AddConfiguredRequestOptions(refitRequest, refitSettings, typeof(global::Refit.Tests.IGitHubApi));
global::Refit.GeneratedRequestRunner.AddRequestProperty(refitRequest, global::Refit.HttpRequestMessageOptions.MethodName, "GetUser");
@@ -72,13 +81,22 @@ public RefitTestsIGitHubApi(global::System.Net.Http.HttpClient client, global::R
var refitSettings = _settings;
global::System.Net.Http.HttpRequestMessage BuildRefitRequest()
{
- var refitUseDefaultFormatting = global::Refit.GeneratedRequestRunner.UsesDefaultUrlParameterFormatting(refitSettings);
- var refitRequest = new global::System.Net.Http.HttpRequestMessage(global::System.Net.Http.HttpMethod.Get, global::Refit.GeneratedRequestRunner.BuildRelativeUri(this.Client, global::Refit.GeneratedRequestRunner.BuildRequestPath("/users/{username}", refitSettings.AllowUnmatchedRouteParameters, [((7, 17), refitUseDefaultFormatting ? (@userName) : global::Refit.GeneratedRequestRunner.FormatUrlParameter(refitSettings, @userName, global::Refit.GeneratedParameterAttributeProvider.Empty, typeof(string)))]), refitSettings.UrlResolution));
- global::Refit.GeneratedRequestRunner.SetHeader(refitRequest, "User-Agent", "Refit Integration Tests", refitSettings.ValidateHeaders);
- global::Refit.GeneratedRequestRunner.AddConfiguredRequestOptions(refitRequest, refitSettings, typeof(global::Refit.Tests.IGitHubApi));
- global::Refit.GeneratedRequestRunner.AddRequestProperty(refitRequest, global::Refit.HttpRequestMessageOptions.MethodName, "GetUserObservable");
- global::Refit.GeneratedRequestRunner.AddRequestProperty(refitRequest, global::Refit.HttpRequestMessageOptions.RelativePathTemplate, "/users/{username}");
- if (refitSettings.CaptureMethodArguments) { global::Refit.GeneratedRequestRunner.AddRequestProperty