Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 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
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal sealed class ClientUriBuilderDefinition : TypeProvider
private const string AppendQueryDelimitedMethodName = "AppendQueryDelimited";
private const string AppendPathDelimitedMethodName = "AppendPathDelimited";
private const string AppendPathMethodName = "AppendPath";
private const string UpdateQueryMethodName = "UpdateQuery";

private readonly FieldProvider _uriBuilderField;
private readonly FieldProvider _pathAndQueryField;
Expand Down Expand Up @@ -100,6 +101,7 @@ protected override MethodProvider[] BuildMethods()
methods.AddRange(BuildAppendPathDelimitedMethods());
methods.AddRange(BuildAppendQueryMethods());
methods.AddRange(BuildAppendQueryDelimitedMethods());
methods.Add(BuildUpdateQueryMethod());
methods.Add(BuildToUriMethod());

return methods.ToArray();
Expand Down Expand Up @@ -334,6 +336,73 @@ private MethodProvider BuildAppendDelimitedMethod(string appendDelimitedMethodNa
return new(signature, body, this, XmlDocProvider.Empty);
}

private MethodProvider BuildUpdateQueryMethod()
{
var nameParameter = new ParameterProvider("name", $"The name.", typeof(string));
var valueParameter = new ParameterProvider("value", $"The value.", typeof(string));

var signature = new MethodSignature(
Name: UpdateQueryMethodName,
Modifiers: MethodSignatureModifiers.Public,
Parameters: [nameParameter, valueParameter],
ReturnType: null,
Description: null, ReturnDescription: null);
Comment thread
radhgupta marked this conversation as resolved.
Outdated

var stringBuilder = PathAndQueryProperty.As<StringBuilder>();
var pathLength = (ValueExpression)_pathLengthField;
Comment thread
radhgupta marked this conversation as resolved.
Outdated

var body = new MethodBodyStatement[]
{
MethodBodyStatement.Empty,
// Check if we have any query string
new IfStatement(stringBuilder.Length().Equal(pathLength))
{
// No query string exists, append the parameter
stringBuilder.Append(Literal('?')).Terminate(),
stringBuilder.Append(nameParameter).Terminate(),
stringBuilder.Append(Literal('=')).Terminate(),
stringBuilder.Append(valueParameter).Terminate()
},
new IfElseStatement(
// Check if parameter already exists in query string
stringBuilder.Invoke("ToString").Invoke("Contains", new BinaryOperatorExpression("+", nameParameter, Literal("="))),
// Update existing parameter
new MethodBodyStatement[]
{
Declare("currentQuery", typeof(string), stringBuilder.Invoke("ToString", [new BinaryOperatorExpression("+", pathLength, Int(1)), new BinaryOperatorExpression("-", new BinaryOperatorExpression("-", stringBuilder.Length(), pathLength), Int(1))]), out var currentQuery),
Declare("searchPattern", typeof(string), new BinaryOperatorExpression("+", nameParameter, Literal("=")), out var searchPattern),
Declare("paramIndex", typeof(int), currentQuery.Invoke("IndexOf", searchPattern), out var paramIndex),
Declare("valueStartIndex", typeof(int), new BinaryOperatorExpression("+", paramIndex, searchPattern.Property("Length")), out var valueStartIndex),
Declare("valueEndIndex", typeof(int), currentQuery.Invoke("IndexOf", Literal('&'), valueStartIndex), out var valueEndIndex),
new IfStatement(valueEndIndex.Equal(Literal(-1)))
{
valueEndIndex.Assign(currentQuery.Property("Length")).Terminate()
},
Declare("newQuery", typeof(string),
new BinaryOperatorExpression("+",
new BinaryOperatorExpression("+",
currentQuery.Invoke("Substring", Literal(0), valueStartIndex),
valueParameter),
currentQuery.Invoke("Substring", valueEndIndex)),
out var newQuery),
// Replace the query portion in the StringBuilder
stringBuilder.Remove(new BinaryOperatorExpression("+", pathLength, Int(1)), new BinaryOperatorExpression("-", new BinaryOperatorExpression("-", stringBuilder.Length(), pathLength), Int(1))).Terminate(),
stringBuilder.Append(newQuery).Terminate()
},
// Append new parameter
new MethodBodyStatement[]
{
stringBuilder.Append(Literal('&')).Terminate(),
stringBuilder.Append(nameParameter).Terminate(),
stringBuilder.Append(Literal('=')).Terminate(),
stringBuilder.Append(valueParameter).Terminate()
}
)
};

return new(signature, body, this, XmlDocProvider.Empty);
}

private MethodProvider BuildToUriMethod()
{
var signature = new MethodSignature(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public class RestClientProvider : TypeProvider
{
private const string RepeatabilityRequestIdHeader = "Repeatability-Request-ID";
private const string RepeatabilityFirstSentHeader = "Repeatability-First-Sent";
private const string MaxPageSizeParameterName = "maxpagesize";

private static readonly Dictionary<string, ParameterProvider> _knownSpecialHeaderParams = new(StringComparer.OrdinalIgnoreCase)
{
Expand Down Expand Up @@ -222,7 +223,7 @@ private MethodBodyStatements BuildMessage(

if (reinjectedParamsMap.Count > 0)
{
statements.AddRange(AppendQueryParameters(uri, operation, reinjectedParamsMap));
statements.AddRange(AppendQueryParameters(uri, operation, reinjectedParamsMap, isNextLinkRequest: true));
}
}
else
Expand Down Expand Up @@ -417,7 +418,7 @@ private IEnumerable<MethodBodyStatement> AppendHeaderParameters(HttpRequestApi r
return statements;
}

private static List<MethodBodyStatement> AppendQueryParameters(ScopedApi uri, InputOperation operation, Dictionary<string, ParameterProvider> paramMap)
private static List<MethodBodyStatement> AppendQueryParameters(ScopedApi uri, InputOperation operation, Dictionary<string, ParameterProvider> paramMap, bool isNextLinkRequest = false)
{
List<MethodBodyStatement> statements = new(operation.Parameters.Count);

Expand All @@ -426,7 +427,7 @@ private static List<MethodBodyStatement> AppendQueryParameters(ScopedApi uri, In
if (inputParameter is not InputQueryParameter inputQueryParameter)
continue;

var queryStatement = BuildQueryParameterStatement(uri, inputQueryParameter, paramMap, operation);
var queryStatement = BuildQueryParameterStatement(uri, inputQueryParameter, paramMap, operation, isNextLinkRequest);
if (queryStatement != null)
{
statements.Add(queryStatement);
Expand All @@ -440,15 +441,23 @@ private static List<MethodBodyStatement> AppendQueryParameters(ScopedApi uri, In
ScopedApi uri,
InputQueryParameter inputQueryParameter,
Dictionary<string, ParameterProvider> paramMap,
InputOperation operation)
InputOperation operation,
bool isNextLinkRequest = false)
{
GetParamInfo(paramMap, operation, inputQueryParameter, out var paramType, out var serializationFormat, out var valueExpression);
if (valueExpression == null)
{
return null;
}

var statement = BuildAppendQueryStatement(uri, inputQueryParameter, paramType, valueExpression, serializationFormat);
// Determine if we should update existing parameters or always append
bool shouldUpdateExisting = isNextLinkRequest &&
ShouldSkipReinjectedParameter(inputQueryParameter.SerializedName) &&
paramType?.IsCollection != true;

MethodBodyStatement statement = shouldUpdateExisting
? BuildUpdateQueryStatement(uri, inputQueryParameter, paramType, valueExpression, serializationFormat)
: BuildAppendQueryStatement(uri, inputQueryParameter, paramType, valueExpression, serializationFormat);

// Apply null check if needed
if (!inputQueryParameter.IsRequired || paramType?.IsNullable == true ||
Expand All @@ -460,14 +469,29 @@ private static List<MethodBodyStatement> AppendQueryParameters(ScopedApi uri, In
return statement;
}

private static MethodBodyStatement BuildUpdateQueryStatement(
Comment thread
radhgupta marked this conversation as resolved.
Outdated
ScopedApi uri,
InputQueryParameter inputQueryParameter,
CSharpType? paramType,
ValueExpression valueExpression,
SerializationFormat? serializationFormat)
{
var toStringExpression = paramType?.Equals(typeof(string)) == true
? valueExpression
: GetParameterValueExpression(valueExpression, serializationFormat);

var parameterName = inputQueryParameter.SerializedName;

return uri.UpdateQuery(Literal(parameterName), toStringExpression).Terminate();
}

private static MethodBodyStatement BuildAppendQueryStatement(
ScopedApi uri,
InputQueryParameter inputQueryParameter,
CSharpType? paramType,
ValueExpression valueExpression,
SerializationFormat? serializationFormat)
{
// Handle non-collection parameters
if (paramType?.IsCollection != true)
{
var toStringExpression = paramType?.Equals(typeof(string)) == true
Expand Down Expand Up @@ -802,6 +826,12 @@ private static bool TryGetSpecialHeaderParam(InputParameter inputParameter, [Not
return false;
}

private static bool ShouldSkipReinjectedParameter(string parameterName)
{
return parameterName.Equals(MaxPageSizeParameterName, StringComparison.OrdinalIgnoreCase);
// In the future, we can extend this to check multiple parameters
}

private static List<int> GetSuccessStatusCodes(InputOperation operation)
{
HashSet<int> statusCodes = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ public static InvokeMethodExpression AppendQueryDelimited(this ScopedApi uriBuil
? uriBuilder.Invoke("AppendQueryDelimited", [name, value, Literal(delimiter), format, PositionalReference("escape", Literal(shouldEscape))])
: uriBuilder.Invoke("AppendQueryDelimited", [name, value, Literal(delimiter), PositionalReference("escape", Literal(shouldEscape))]);

public static InvokeMethodExpression UpdateQuery(this ScopedApi uriBuilder, ValueExpression name, ValueExpression value)
=> uriBuilder.Invoke("UpdateQuery", name, value);

public static ScopedApi<Uri> ToUri(this ScopedApi uriBuilder)
=> uriBuilder.Invoke("ToUri").As<Uri>();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,5 +481,21 @@ public void AppendQueryDelimited(string endpoint, IEnumerable<string> value, str

Assert.AreEqual(expected, builder.ToUri().ToString());
}

[TestCase("http://localhost", "param1", "value1", "http://localhost/?param1=value1")]
[TestCase("http://localhost?existing=old", "param1", "value1", "http://localhost/?existing=old&param1=value1")]
[TestCase("http://localhost?param1=old", "param1", "new", "http://localhost/?param1=new")]
[TestCase("http://localhost?param1=old&param2=value2", "param1", "new", "http://localhost/?param1=new&param2=value2")]
[TestCase("http://localhost?param2=value2&param1=old", "param1", "new", "http://localhost/?param2=value2&param1=new")]
[TestCase("http://localhost?param2=value2&param1=old&param3=value3", "param1", "new", "http://localhost/?param2=value2&param1=new&param3=value3")]
public void UpdateQuery(string endpoint, string name, string value, string expected)
Comment thread
radhgupta marked this conversation as resolved.
{
var builder = new SampleTypeSpec.ClientUriBuilder();
builder.Reset(new Uri(endpoint));

builder.UpdateQuery(name, value);

Assert.AreEqual(expected, builder.ToUri().ToString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public partial class TestClient
uri.AppendQuery("p1", p1, true);
if ((maxPageSize != null))
{
uri.AppendQuery("maxPageSize", global::Sample.TypeFormatters.ConvertToString(maxPageSize), true);
uri.UpdateQuery("maxPageSize", global::Sample.TypeFormatters.ConvertToString(maxPageSize));
}
global::System.ClientModel.Primitives.PipelineMessage message = Pipeline.CreateMessage(uri.ToUri(), "GET", PipelineMessageClassifier200);
global::System.ClientModel.Primitives.PipelineRequest request = message.Request;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,39 @@ public void AppendQueryDelimited<T>(string name, IEnumerable<T> value, string de
AppendQuery(name, string.Join(delimiter, stringValues), escape);
}

public void UpdateQuery(string name, string value)
{
if (PathAndQuery.Length == _pathLength)
Comment thread
radhgupta marked this conversation as resolved.
{
PathAndQuery.Append('?');
Comment thread
radhgupta marked this conversation as resolved.
Outdated
PathAndQuery.Append(name);
PathAndQuery.Append('=');
PathAndQuery.Append(value);
}
if (PathAndQuery.ToString().Contains(name + "="))
Comment thread
radhgupta marked this conversation as resolved.
Outdated
{
string currentQuery = PathAndQuery.ToString(_pathLength + 1, PathAndQuery.Length - _pathLength - 1);
string searchPattern = name + "=";
int paramIndex = currentQuery.IndexOf(searchPattern);
int valueStartIndex = paramIndex + searchPattern.Length;
int valueEndIndex = currentQuery.IndexOf('&', valueStartIndex);
if (valueEndIndex == -1)
{
valueEndIndex = currentQuery.Length;
}
string newQuery = currentQuery.Substring(0, valueStartIndex) + value + currentQuery.Substring(valueEndIndex);
PathAndQuery.Remove(_pathLength + 1, PathAndQuery.Length - _pathLength - 1);
PathAndQuery.Append(newQuery);
}
else
{
PathAndQuery.Append('&');
Comment thread
radhgupta marked this conversation as resolved.
Outdated
PathAndQuery.Append(name);
PathAndQuery.Append('=');
PathAndQuery.Append(value);
}
}

public Uri ToUri()
{
UriBuilder.Path = PathAndQuery.ToString(0, _pathLength);
Expand Down
Loading