Skip to content

Commit

Permalink
Add ClientUriBuilder helper class implementation (#3704)
Browse files Browse the repository at this point in the history
Fixes: #3680

---------

Co-authored-by: ShivangiReja <[email protected]>
  • Loading branch information
ShivangiReja and ShivangiReja committed Jun 29, 2024
1 parent a7405e6 commit eaca7a7
Show file tree
Hide file tree
Showing 4 changed files with 933 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ protected override TypeProvider[] BuildTypeProviders()
new ModelSerializationExtensionsProvider(),
new TypeFormattersProvider(),
new ClientPipelineExtensionsProvider(),
new ErrorResultProvider()
new ErrorResultProvider(),
new ClientUriBuilderProvider(),
];
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,336 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Generator.CSharp.Expressions;
using Microsoft.Generator.CSharp.Providers;
using Microsoft.Generator.CSharp.Statements;
using Microsoft.Generator.CSharp.Snippets;
using static Microsoft.Generator.CSharp.Snippets.Snippet;
using static Microsoft.Generator.CSharp.ClientModel.Snippets.TypeFormattersSnippet;
using Microsoft.Generator.CSharp.Primitives;

namespace Microsoft.Generator.CSharp.ClientModel.Providers
{
internal sealed class ClientUriBuilderProvider : TypeProvider
{
protected override TypeSignatureModifiers GetDeclarationModifiers()
{
return TypeSignatureModifiers.Internal;
}

public override string RelativeFilePath => Path.Combine("src", "Generated", "Internal", $"{Name}.cs");

public override string Name => "ClientUriBuilder";

private readonly FieldProvider _uriBuilderField = new(FieldModifiers.Private, typeof(UriBuilder), "_uriBuilder");
private readonly FieldProvider _pathBuilderField = new(FieldModifiers.Private, typeof(StringBuilder), "_pathBuilder");
private readonly FieldProvider _queryBuilderField = new(FieldModifiers.Private, typeof(StringBuilder), "_queryBuilder");

protected override FieldProvider[] BuildFields()
{
return [_uriBuilderField, _pathBuilderField, _queryBuilderField];
}

private PropertyProvider? _uriBuilderProperty;
private PropertyProvider UriBuilderProperty => _uriBuilderProperty ??= new(
modifiers: MethodSignatureModifiers.Private,
name: "UriBuilder",
type: typeof(UriBuilder),
body: new ExpressionPropertyBody(new BinaryOperatorExpression(" ??= ", _uriBuilderField, New.Instance(typeof(UriBuilder)))),
description: null);

internal ValueExpression UriBuilderPath => new MemberExpression(UriBuilderProperty, "Path");
internal ValueExpression UriBuilderQuery => new MemberExpression(UriBuilderProperty, "Query");

private PropertyProvider? _pathBuilderProperty;
private PropertyProvider PathBuilderProperty => _pathBuilderProperty ??= new(
modifiers: MethodSignatureModifiers.Private,
name: "PathBuilder",
type: typeof(StringBuilder),
body: new ExpressionPropertyBody(new BinaryOperatorExpression(" ??= ", _pathBuilderField, New.Instance(typeof(StringBuilder), UriBuilderPath))),
description: null);

private PropertyProvider? _queryBuilderProperty;
private PropertyProvider QueryBuilderProperty => _queryBuilderProperty ??= new(
modifiers: MethodSignatureModifiers.Private,
name: "QueryBuilder",
type: typeof(StringBuilder),
body: new ExpressionPropertyBody(new BinaryOperatorExpression(" ??= ", _queryBuilderField, New.Instance(typeof(StringBuilder), UriBuilderQuery))),
description: null);

protected override PropertyProvider[] BuildProperties()
{
return [UriBuilderProperty, PathBuilderProperty, QueryBuilderProperty];
}

protected override MethodProvider[] BuildConstructors()
{
var signature = new ConstructorSignature(
Type: Type,
Modifiers: MethodSignatureModifiers.Public,
Parameters: Array.Empty<ParameterProvider>(),
Description: null);
return [new MethodProvider(signature, MethodBodyStatement.Empty, this)];
}

protected override MethodProvider[] BuildMethods()
{
var methods = new List<MethodProvider>();

methods.Add(BuildResetMethod());

methods.AddRange(BuildAppendPathMethods());
methods.AddRange(BuildAppendQueryMethods());
methods.AddRange(BuildAppendQueryDelimitedMethods());

methods.Add(BuildToUriMethod());

return methods.ToArray();
}

private const string _resetMethodName = "Reset";
private MethodProvider BuildResetMethod()
{
var uriParameter = new ParameterProvider("uri", $"The uri.", typeof(Uri));
var signature = new MethodSignature(
Name: _resetMethodName,
Modifiers: MethodSignatureModifiers.Public,
Parameters: new[]
{
uriParameter
},
ReturnType: null,
Description: null, ReturnDescription: null);

var body = new MethodBodyStatement[]
{
_uriBuilderField.Assign(New.Instance(_uriBuilderField.Type, uriParameter)).Terminate(),
_pathBuilderField.Assign(New.Instance(_pathBuilderField.Type, UriBuilderPath)).Terminate(),
_queryBuilderField.Assign(New.Instance(_queryBuilderField.Type, UriBuilderQuery)).Terminate()
};

return new(signature, body, this);
}

private const string _appendPathMethodName = "AppendPath";
private MethodProvider[] BuildAppendPathMethods()
{
var valueParameter = new ParameterProvider("value", $"The value.", typeof(string));
var escapeParameter = new ParameterProvider("escape", $"The escape", typeof(bool));
var signature = new MethodSignature(
Name: _appendPathMethodName,
Modifiers: MethodSignatureModifiers.Public,
Parameters: [valueParameter, escapeParameter],
ReturnType: null,
Description: null, ReturnDescription: null);

var value = new StringSnippet(valueParameter);
var escape = new BoolSnippet(escapeParameter);
var pathBuilder = new StringBuilderSnippet(PathBuilderProperty);
MethodBodyStatement body = new MethodBodyStatement[]
{
MethodBodyStatement.Empty,
new IfStatement(escape)
{
value.Assign(new InvokeStaticMethodExpression(typeof(Uri), nameof(Uri.EscapeDataString), new[]{ value.Expression })).Terminate()
},
MethodBodyStatement.Empty,
new IfStatement(pathBuilder.Length.Expression.GreaterThan(Int(0)).And(new IndexerExpression(pathBuilder, pathBuilder.Length - Int(1)).Equal(Literal('/'))).And(new IndexerExpression(value, Int(0)).Equal(Literal('/'))))
{
pathBuilder.Remove(pathBuilder.Length - Int(1), Int(1)).Terminate()
},
MethodBodyStatement.Empty,
pathBuilder.Append(value).Terminate(),
UriBuilderPath.Assign(pathBuilder.Expression.InvokeToString()).Terminate()
};

return
[
new(signature, body, this),
BuildAppendPathMethod(typeof(bool), false, false),
BuildAppendPathMethod(typeof(float), true, false),
BuildAppendPathMethod(typeof(double), true, false),
BuildAppendPathMethod(typeof(int), true, false),
BuildAppendPathMethod(typeof(byte[]), true, true),
BuildAppendPathMethod(typeof(IEnumerable<string>), true, false),
BuildAppendPathMethod(typeof(DateTimeOffset), true, true),
BuildAppendPathMethod(typeof(TimeSpan), true, true),
BuildAppendPathMethod(typeof(Guid), true, false),
BuildAppendPathMethod(typeof(long), true, false)
];
}

private MethodProvider BuildAppendPathMethod(CSharpType valueType, bool escapeDefaultValue, bool hasFormat)
{
var valueParameter = new ParameterProvider("value", $"The value.", valueType);
var escapeParameter = new ParameterProvider("escape", $"The escape.", typeof(bool), Bool(escapeDefaultValue));
var formatParameter = new ParameterProvider("format", $"The format", typeof(string));
var parameters = hasFormat
? new[] { valueParameter, formatParameter, escapeParameter }
: new[] { valueParameter, escapeParameter };

var signature = new MethodSignature(
Name: _appendPathMethodName,
Modifiers: MethodSignatureModifiers.Public,
Parameters: parameters,
ReturnType: null,
Description: null, ReturnDescription: null);
var convertToStringExpression = ConvertToString(valueParameter, hasFormat ? (ValueExpression)formatParameter : null);
var body = new InvokeInstanceMethodExpression(null, _appendPathMethodName, new[] { convertToStringExpression.Expression, escapeParameter }, null, false);

return new(signature, body, this);
}

private const string _appendQueryMethodName = "AppendQuery";
private MethodProvider[] BuildAppendQueryMethods()
{
var nameParameter = new ParameterProvider("name", $"The name.", typeof(string));
var valueParameter = new ParameterProvider("value", $"The value.", typeof(string));
var escapeParameter = new ParameterProvider("escape", $"The escape.", typeof(bool));

var signature = new MethodSignature(
Name: _appendQueryMethodName,
Modifiers: MethodSignatureModifiers.Public,
Parameters: new[] { nameParameter, valueParameter, escapeParameter },
ReturnType: null,
Description: null, ReturnDescription: null);

var name = new StringSnippet(nameParameter);
var value = new StringSnippet(valueParameter);
var escape = new BoolSnippet(escapeParameter);
var queryBuilder = new StringBuilderSnippet(QueryBuilderProperty);
var body = new MethodBodyStatement[]
{
MethodBodyStatement.Empty,
new IfStatement(queryBuilder.Length.Expression.GreaterThan(Int(0)))
{
queryBuilder.Append(Literal('&')).Terminate()
},
MethodBodyStatement.Empty,
new IfStatement(escape)
{
value.Assign(new InvokeStaticMethodExpression(typeof(Uri), nameof(Uri.EscapeDataString), new[] { value.Expression })).Terminate()
},
MethodBodyStatement.Empty,
queryBuilder.Append(name).Terminate(),
queryBuilder.Append(Literal('=')).Terminate(),
queryBuilder.Append(value).Terminate()
};

return
[
new MethodProvider(signature, body, this),
BuildAppendQueryMethod(typeof(bool), false, false),
BuildAppendQueryMethod(typeof(float), true, false),
BuildAppendQueryMethod(typeof(DateTimeOffset), true, true),
BuildAppendQueryMethod(typeof(TimeSpan), true, true),
BuildAppendQueryMethod(typeof(double), true, false),
BuildAppendQueryMethod(typeof(decimal), true, false),
BuildAppendQueryMethod(typeof(int), true, false),
BuildAppendQueryMethod(typeof(long), true, false),
BuildAppendQueryMethod(typeof(TimeSpan), true, false),
BuildAppendQueryMethod(typeof(byte[]), true, true),
BuildAppendQueryMethod(typeof(Guid), true, false)
];
}

private MethodProvider BuildAppendQueryMethod(CSharpType valueType, bool escapeDefaultValue, bool hasFormat)
{
var nameParameter = new ParameterProvider("name", $"The name.", typeof(string));
var valueParameter = new ParameterProvider("value", $"The value.", valueType);
var escapeParameter = new ParameterProvider("escape", $"The escape.", typeof(bool), Bool(escapeDefaultValue));
var formatParameter = new ParameterProvider("format", $"The format.", typeof(string));
var parameters = hasFormat
? new[] { nameParameter, valueParameter, formatParameter, escapeParameter }
: new[] { nameParameter, valueParameter, escapeParameter };

var signature = new MethodSignature(
Name: _appendQueryMethodName,
Modifiers: MethodSignatureModifiers.Public,
Parameters: parameters,
ReturnType: null,
Description: null, ReturnDescription: null);
var convertToStringExpression = ConvertToString(valueParameter, hasFormat ? (ValueExpression)formatParameter : null);
var body = new InvokeInstanceMethodExpression(null, _appendQueryMethodName, new[] { nameParameter, convertToStringExpression.Expression, escapeParameter }, null, false);

return new(signature, body, this);
}

private MethodProvider[] BuildAppendQueryDelimitedMethods()
{
return [ BuildAppendQueryDelimitedMethod(false), BuildAppendQueryDelimitedMethod(true)];
}

private readonly CSharpType _t = typeof(IEnumerable<>).GetGenericArguments()[0];

private const string _appendQueryDelimitedMethodName = "AppendQueryDelimited";
private MethodProvider BuildAppendQueryDelimitedMethod(bool hasFormat)
{
var nameParameter = new ParameterProvider("name", $"The name.", typeof(string));
var valueParameter = new ParameterProvider("value", $"The value.", new CSharpType(typeof(IEnumerable<>), _t));
var delimiterParameter = new ParameterProvider("delimiter", $"The delimiter.", typeof(string));
var formatParameter = new ParameterProvider("format", $"The format.", typeof(string));
var escapeParameter = new ParameterProvider("escape", $"The escape.", typeof(bool), Bool(true));

var parameters = hasFormat
? new[] { nameParameter, valueParameter, delimiterParameter, formatParameter, escapeParameter }
: new[] { nameParameter, valueParameter, delimiterParameter, escapeParameter };
var signature = new MethodSignature(
Name: _appendQueryDelimitedMethodName,
Modifiers: MethodSignatureModifiers.Public,
Parameters: parameters,
ReturnType: null,
GenericArguments: new[] { _t },
Description: null, ReturnDescription: null);

var name = new StringSnippet(nameParameter);
var value = new EnumerableSnippet(_t, valueParameter);
var delimiter = new StringSnippet(delimiterParameter);
var escape = new BoolSnippet(escapeParameter);

var v = new VariableExpression(_t, "v");
var convertToStringExpression = ConvertToString(v, hasFormat ? new StringSnippet(formatParameter).Expression : null);
var body = new[]
{
Declare("stringValues", value.Select(new StringSnippet(new FuncExpression(new[] {v.Declaration}, convertToStringExpression))), out var stringValues),
new InvokeInstanceMethodExpression(null, _appendQueryMethodName, new[] { name.Expression, StringSnippet.Join(delimiter, stringValues), escape }, null, false).Terminate()
};

return new(signature, body, this);
}

private const string _toUriMethodName = "ToUri";
private MethodProvider BuildToUriMethod()
{
var signature = new MethodSignature(
Name: _toUriMethodName,
Modifiers: MethodSignatureModifiers.Public,
Parameters: Array.Empty<ParameterProvider>(),
ReturnType: typeof(Uri),
Description: null, ReturnDescription: null);

var pathBuilder = (ValueExpression)_pathBuilderField;
var queryBuilder = (ValueExpression)_queryBuilderField;
var body = new MethodBodyStatement[]
{
new IfStatement(pathBuilder.NotEqual(Null))
{
UriBuilderPath.Assign(pathBuilder.InvokeToString()).Terminate()
},
MethodBodyStatement.Empty,
new IfStatement(queryBuilder.NotEqual(Null))
{
UriBuilderQuery.Assign(queryBuilder.InvokeToString()).Terminate()
},
MethodBodyStatement.Empty,
Return(new MemberExpression(UriBuilderProperty, nameof(UriBuilder.Uri)))
};

return new(signature, body, this);
}
}
}
Loading

0 comments on commit eaca7a7

Please sign in to comment.