private VariableReferenceExpression CreateBodyArgument(Method method, MethodDeclaration methodDeclaration) { var bodyArguments = method.Parameters.Where(p => GetParameterLocation(method, p) == ParameterLocation.Body).ToList(); if (bodyArguments.Count == 0) { return(null); } var variable = new VariableDeclarationStatement(typeof(Dictionary <string, object>), "body"); methodDeclaration.Statements.Add(variable); variable.InitExpression = new NewObjectExpression(typeof(Dictionary <string, object>)); foreach (var arg in bodyArguments) { var argumentReference = methodDeclaration.Arguments.First(a => a.Data["Parameter"] == arg); var assign = variable.CreateInvokeMethodExpression(nameof(Dictionary <string, object> .Add), arg.Name, argumentReference); if (arg.IsOptional) { var condition = new ConditionStatement { Condition = new BinaryExpression(BinaryOperator.NotEquals, argumentReference, new LiteralExpression(null)), TrueStatements = assign }; methodDeclaration.Statements.Add(condition); } else { methodDeclaration.Statements.Add(assign); } } return(variable); }
public void CreateInvokeMethodExpression_VariableDeclaration_WithMemberName() { var v = new VariableDeclarationStatement(typeof(string), "a"); var member = v.CreateInvokeMethodExpression("Test", "Name"); var csharp = new CSharpCodeGenerator().Write(member); Assert.Equal("a.Test(\"Name\")", csharp); }
public void CreateInvokeMethodExpression_VariableDeclaration() { var v = new VariableDeclarationStatement(typeof(string), "a"); var member = v.CreateInvokeMethodExpression(LiteralExpression.Null()); var csharp = new CSharpCodeGenerator().Write(member); Assert.Equal("a(null)", csharp); }
private static MethodDeclaration GenerateMethod(ClassDeclaration clientClass, Method method, TypeReference requestType) { var m = clientClass.AddMember(new MethodDeclaration(method.MethodGroup.Name + '_' + GetMethodName(method))); m.SetData("Method", method); var buildUrlMethod = GenerateBuildUrlMethod(clientClass, method, requestType); GenerateMethodSignature(method, m, requestType, out var requestArgument, out var requestOptionsArgument, out var cancellationTokenArgument); m.Modifiers = Modifiers.Private; if (method.MethodType != MethodType.GetPaged) { m.Modifiers |= Modifiers.Async; } // Method body m.Statements = new StatementCollection(); // 1. Create url from parameters var buildUrlExpression = new MethodInvokeExpression(new MemberReferenceExpression(new TypeReference(clientClass), buildUrlMethod)); if (buildUrlMethod.Arguments.Count > 0) { buildUrlExpression.Arguments.Add(requestArgument); } var urlVariable = new VariableDeclarationStatement(typeof(string), "url", buildUrlExpression); m.Statements.Add(urlVariable); if (method.MethodType == MethodType.GetPaged) { // return new Meziantou.GitLab.PagedResponse<MergeRequest>(this, url, requestOptions); m.Statements.Add(new ReturnStatement(new NewObjectExpression(m.ReturnType !.Clone(), new ThisExpression(), urlVariable, requestOptionsArgument))); } else { // 2. Create HttpRequestMessage object var requestVariable = new VariableDeclarationStatement(typeof(HttpRequestMessage), "requestMessage", new NewObjectExpression(typeof(HttpRequestMessage))); var usingStatement = new UsingStatement() { Statement = requestVariable, Body = new StatementCollection() }; m.Statements.Add(usingStatement); var statements = usingStatement.Body; statements.Add(new AssignStatement(new MemberReferenceExpression(requestVariable, nameof(HttpRequestMessage.Method)), GetHttpMethod(method.MethodType))); statements.Add(new AssignStatement(new MemberReferenceExpression(requestVariable, nameof(HttpRequestMessage.RequestUri)), new NewObjectExpression(typeof(Uri), urlVariable, new MemberReferenceExpression(typeof(UriKind), nameof(UriKind.RelativeOrAbsolute))))); CreateBodyArgument(method, statements, requestArgument, requestVariable); // 3. Send request // var response = await SendAsync(request, options, cancellationToken).ConfigureAwait(false); var responseVariable = new VariableDeclarationStatement(WellKnownTypes.HttpResponseTypeReference.MakeNullable(), "response", LiteralExpression.Null()); statements.Add(responseVariable); var responseTry = new TryCatchFinallyStatement() { Try = new StatementCollection() }; statements.Add(responseTry); responseTry.Try.Add(new AssignStatement(responseVariable, new AwaitExpression(new MethodInvokeExpression(new MemberReferenceExpression(new ThisExpression(), "SendAsync"), requestVariable, requestOptionsArgument, cancellationTokenArgument)).ConfigureAwait(false))); // Dispose reponse in catch if Stream or in finally if not stream var disposeReponseStatements = new ConditionStatement() { Condition = new BinaryExpression(BinaryOperator.NotEquals, responseVariable, LiteralExpression.Null()), TrueStatements = responseVariable.CreateInvokeMethodExpression("Dispose"), }; if (method.ReturnType == ModelRef.File) { responseTry.Catch = new CatchClauseCollection { new CatchClause() { Body = disposeReponseStatements, }, }; responseTry.Catch[0].Body.Add(new ThrowStatement()); } else { responseTry.Finally = disposeReponseStatements; } // 4. Convert and return response object // if (response.StatusCode == HttpStatusCode.NotFound) return default; if (method.MethodType == MethodType.Get) { responseTry.Try.Add(new ConditionStatement() { Condition = new BinaryExpression(BinaryOperator.Equals, responseVariable.CreateMemberReferenceExpression("StatusCode"), new MemberReferenceExpression(typeof(HttpStatusCode), "NotFound")), TrueStatements = new ReturnStatement(new DefaultValueExpression()), }); } // await response.EnsureStatusCodeAsync(cancellationToken).ConfigureAwait(false); responseTry.Try.Add(new AwaitExpression(new MethodInvokeExpression(new MemberReferenceExpression(responseVariable, "EnsureStatusCodeAsync"), cancellationTokenArgument)).ConfigureAwait(false)); if (method.ReturnType != null) { // var result = await response.ToObjectAsync<T>(cancellationToken).ConfigureAwait(false); var resultVariable = new VariableDeclarationStatement(m.ReturnType.Parameters[0].MakeNullable(), "result"); responseTry.Try.Add(resultVariable); if (method.MethodType == MethodType.Get && method.ReturnType == ModelRef.File) { resultVariable.InitExpression = new AwaitExpression(responseVariable.CreateInvokeMethodExpression("ToStreamAsync", cancellationTokenArgument)).ConfigureAwait(false); } else if (method.MethodType == MethodType.GetCollection) { resultVariable.InitExpression = new AwaitExpression(responseVariable.CreateInvokeMethodExpression("ToCollectionAsync", new TypeReference[] { method.ReturnType.ToPropertyTypeReference() }, cancellationTokenArgument)).ConfigureAwait(false); } else { resultVariable.InitExpression = new AwaitExpression(responseVariable.CreateInvokeMethodExpression("ToObjectAsync", new TypeReference[] { method.ReturnType.ToPropertyTypeReference() }, cancellationTokenArgument)).ConfigureAwait(false); } // if (result is null) // throw new GitLabException(response.RequestMethod, response.RequestUri, response.StatusCode, $"The response cannot be converted to '{typeof(T)}' because the body is null or empty"); if (method.MethodType != MethodType.Get) { responseTry.Try.Add(new ConditionStatement() { Condition = new BinaryExpression(BinaryOperator.Equals, resultVariable, LiteralExpression.Null()), TrueStatements = new ThrowStatement(new NewObjectExpression(WellKnownTypes.GitLabExceptionTypeReference, responseVariable.CreateMemberReferenceExpression("RequestMethod"), responseVariable.CreateMemberReferenceExpression("RequestUri"), responseVariable.CreateMemberReferenceExpression("StatusCode"), new LiteralExpression($"The response cannot be converted to '{method.ReturnType.ToPropertyTypeReference().ClrFullTypeName}' because the body is null or empty"))), }); } responseTry.Try.Add(new ReturnStatement(resultVariable)); } } return(m);
private static MethodDeclaration GenerateBuildUrlMethod(ClassDeclaration clientClass, Method method, TypeReference requestType) { var m = clientClass.AddMember(new MethodDeclaration(method.MethodGroup.Name + '_' + GetMethodName(method) + "_BuildUrl")); m.Modifiers = Modifiers.Private | Modifiers.Static; m.ReturnType = typeof(string); // Method body m.Statements = new StatementCollection(); var urlVariable = new VariableDeclarationStatement(typeof(string), "url"); m.Statements.Add(urlVariable); // 1. Create url from parameters var parameters = method.Parameters.Where(p => GetParameterLocation(method, p) == ParameterLocation.Url).ToList(); if (parameters.Any()) { var requestArgument = m.AddArgument("request", requestType); // [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "<Pending>")] m.CustomAttributes.Add(new CustomAttribute(typeof(SuppressMessageAttribute)) { Arguments = { new CustomAttributeArgument(new LiteralExpression("Reliability")), new CustomAttributeArgument(new LiteralExpression("CA2000:Dispose objects before losing scope")), new CustomAttributeArgument("Justification", new LiteralExpression("The rule doesn't understand ref struct")), }, }); var segments = GetSegments(method.UrlTemplate); var urlBuilder = new VariableDeclarationStatement( WellKnownTypes.UrlBuilderTypeReference, "urlBuilder", new NewObjectExpression(WellKnownTypes.UrlBuilderTypeReference)); var urlUsingStatement = new UsingStatement() { Statement = urlBuilder, Body = new StatementCollection(), }; var usingStatements = urlUsingStatement.Body; m.Statements.Add(urlUsingStatement); foreach (var segment in segments) { if (segment[0] == ':') { var param = parameters.SingleOrDefault(p => p.Name == segment[1..]); if (param == null) { throw new InvalidOperationException($"Parameter '{segment}' is not mapped for method '{method.UrlTemplate}'"); } AddParameter(param, parameterDelimiterVariable: null, encoded: true); parameters.Remove(param); } else if (segment[0] == '*') { var param = parameters.SingleOrDefault(p => p.Name == segment[1..]); if (param == null) { throw new InvalidOperationException($"Parameter '{segment}' is not mapped for method '{method.UrlTemplate}'"); } AddParameter(param, parameterDelimiterVariable: null, encoded: false); parameters.Remove(param); } else if (segment.StartsWith("[.", StringComparison.Ordinal)) { var param = parameters.SingleOrDefault(p => p.Name == segment[2..^ 1]); if (param == null) { throw new InvalidOperationException($"Parameter '{segment}' is not mapped for method '{method.UrlTemplate}'"); } AddParameter(param, parameterDelimiterVariable: null, encoded: false, prefix: "."); parameters.Remove(param); } else { usingStatements.Add(urlBuilder.CreateInvokeMethodExpression("Append", new LiteralExpression(segment))); } } if (parameters.Any()) { var separator = new VariableDeclarationStatement(typeof(char), "separator", new LiteralExpression('?')); usingStatements.Add(separator); foreach (var param in parameters) { AddParameter(param, separator, encoded: true); } } usingStatements.Add(new AssignStatement(urlVariable, urlBuilder.CreateInvokeMethodExpression("ToString"))); void AddParameter(MethodParameter param, VariableDeclarationStatement parameterDelimiterVariable, bool encoded, string prefix = null) { var appendParameterMethodName = encoded ? "AppendParameter" : "AppendRawParameter"; void AddSeparator(StatementCollection statements) { if (parameterDelimiterVariable != null) { statements.Add(urlBuilder.CreateInvokeMethodExpression("Append", parameterDelimiterVariable)); statements.Add(new AssignStatement(parameterDelimiterVariable, new LiteralExpression('&'))); if (!param.Options.HasFlag(MethodParameterOptions.CustomMapping)) { statements.Add(urlBuilder.CreateInvokeMethodExpression("Append", new LiteralExpression(param.Name + '='))); } } } void AppendPrefix(StatementCollection statements) { if (!string.IsNullOrEmpty(prefix)) { statements.Add(urlBuilder.CreateInvokeMethodExpression("Append", prefix)); } } if (param.Type.IsParameterEntity) { var propertyName = param.Type.ParameterEntity.FinalType == ModelRef.Object ? "ValueAsString" : "Value"; var hasValueCondition = new ConditionStatement { Condition = CreatePropertyReference().CreateMemberReferenceExpression(nameof(Nullable <int> .HasValue)), TrueStatements = new StatementCollection(), }; AddSeparator(hasValueCondition.TrueStatements); AppendPrefix(hasValueCondition.TrueStatements); var parameterValue = FormatValue(param.Type, CreatePropertyReference().CreateInvokeMethodExpression("GetValueOrDefault").CreateMemberReferenceExpression(propertyName)); if (param.Options.HasFlag(MethodParameterOptions.CustomMapping)) { hasValueCondition.TrueStatements.Add(urlBuilder .CreateInvokeMethodExpression( "AppendParameter", param.Name, parameterValue)); } else { hasValueCondition.TrueStatements.Add(urlBuilder .CreateInvokeMethodExpression( appendParameterMethodName, parameterValue)); } urlUsingStatement.Body.Add(hasValueCondition); } else { var isValueType = param.Type.IsValueType && !param.Type.IsCollection; var hasValueCondition = new ConditionStatement { Condition = isValueType ? CreatePropertyReference().CreateMemberReferenceExpression("HasValue") : new UnaryExpression(UnaryOperator.Not, new MethodInvokeExpression( new MemberReferenceExpression(new TypeReference(typeof(object)), "ReferenceEquals"), CreatePropertyReference(), LiteralExpression.Null())), TrueStatements = new StatementCollection(), }; AddSeparator(hasValueCondition.TrueStatements); AppendPrefix(hasValueCondition.TrueStatements); Expression parameterValue = isValueType ? CreatePropertyReference().CreateInvokeMethodExpression("GetValueOrDefault") : CreatePropertyReference(); if (param.Options.HasFlag(MethodParameterOptions.CustomMapping)) { hasValueCondition.TrueStatements.Add(urlBuilder .CreateInvokeMethodExpression( "AppendParameter", param.Name, parameterValue)); } else { var appendMethod = new MethodInvokeExpression( new MemberReferenceExpression(urlBuilder, appendParameterMethodName), FormatValue(param.Type, parameterValue)); hasValueCondition.TrueStatements.Add(appendMethod); } urlUsingStatement.Body.Add(hasValueCondition); } MemberReferenceExpression CreatePropertyReference() { return(requestArgument.CreateMemberReferenceExpression(ToPropertyName(param.Name))); } } } else { m.Statements.Add(new AssignStatement(urlVariable, new LiteralExpression(method.UrlTemplate))); } m.Statements.Add(new ReturnStatement(urlVariable)); return(m); }
private MethodDeclaration GenerateMethod(ClassDeclaration clientClass, Method method) { var m = clientClass.AddMember(new MethodDeclaration(method.Name + "Async")); m.SetData("Method", method); GenerateMethodSignature(method, m, out var arguments, out var pageArgument, out var requestOptionsArgument, out var cancellationTokenArgument); m.Modifiers = Modifiers.Public; // Method body m.Statements = new StatementCollection(); var urlBuilder = new VariableDeclarationStatement( typeof(UrlBuilder), "urlBuilder", new TypeReference(typeof(UrlBuilder)) .CreateInvokeMethodExpression("Get", method.UrlTemplate)); m.Statements.Add(urlBuilder); foreach (var param in method.Parameters.Where(p => GetParameterLocation(method, p) == ParameterLocation.Url)) { if (param.Type.IsParameterEntity) { if (param.Type.IsNullable) { var hasValueCondition = new ConditionStatement { Condition = arguments[param].CreateMemberReferenceExpression(nameof(Nullable <int> .HasValue)), TrueStatements = urlBuilder.CreateInvokeMethodExpression(nameof(UrlBuilder.WithValue), param.Name, arguments[param].CreateMemberReferenceExpression(nameof(Nullable <int> .Value), "Value")) }; m.Statements.Add(hasValueCondition); } else { m.Statements.Add(urlBuilder.CreateInvokeMethodExpression(nameof(UrlBuilder.WithValue), param.Name, arguments[param].CreateMemberReferenceExpression("Value"))); } } else { m.Statements.Add(urlBuilder.CreateInvokeMethodExpression(nameof(UrlBuilder.WithValue), param.Name, arguments[param])); } } if (pageArgument != null) { var pageCondition = new ConditionStatement() { Condition = new BinaryExpression(BinaryOperator.NotEquals, pageArgument, new LiteralExpression(null)), TrueStatements = new StatementCollection { new ConditionStatement() { Condition = new BinaryExpression(BinaryOperator.GreaterThan, pageArgument.CreateMemberReferenceExpression(nameof(PageOptions.PageIndex)), new LiteralExpression(0)), TrueStatements = new StatementCollection { urlBuilder.CreateInvokeMethodExpression(nameof(UrlBuilder.WithValue), "page", pageArgument.CreateMemberReferenceExpression(nameof(PageOptions.PageIndex))) } }, new ConditionStatement() { Condition = new BinaryExpression(BinaryOperator.GreaterThan, pageArgument.CreateMemberReferenceExpression(nameof(PageOptions.PageSize)), new LiteralExpression(0)), TrueStatements = new StatementCollection { urlBuilder.CreateInvokeMethodExpression(nameof(UrlBuilder.WithValue), "per_page", pageArgument.CreateMemberReferenceExpression(nameof(PageOptions.PageSize))) } }, new ConditionStatement() { Condition = new BinaryExpression(BinaryOperator.Equals, pageArgument.CreateMemberReferenceExpression(nameof(PageOptions.OrderBy), nameof(OrderBy.Name)).CreateIsNullOrEmptyExpression(), new LiteralExpression(false)), TrueStatements = new StatementCollection { urlBuilder.CreateInvokeMethodExpression(nameof(UrlBuilder.WithValue), "order_by", pageArgument.CreateMemberReferenceExpression(nameof(PageOptions.OrderBy), nameof(OrderBy.Name))), urlBuilder.CreateInvokeMethodExpression(nameof(UrlBuilder.WithValue), "sort", pageArgument.CreateMemberReferenceExpression(nameof(PageOptions.OrderBy), nameof(OrderBy.Direction))) } } } }; m.Statements.Add(pageCondition); } var url = new VariableDeclarationStatement(typeof(string), "url", urlBuilder.CreateInvokeMethodExpression(nameof(UrlBuilder.Build))); m.Statements.Add(url); var bodyArgument = CreateBodyArgument(method, m); switch (method.MethodType) { case MethodType.Get: if (method.ReturnType.IsCollection) { m.Statements.Add(new ReturnStatement(new ThisExpression().CreateInvokeMethodExpression("GetCollectionAsync", new TypeReference[] { method.ReturnType }, url, requestOptionsArgument, cancellationTokenArgument))); } else { m.Statements.Add(new ReturnStatement(new ThisExpression().CreateInvokeMethodExpression("GetAsync", new TypeReference[] { method.ReturnType }, url, requestOptionsArgument, cancellationTokenArgument))); } break; case MethodType.GetPaged: m.Statements.Add(new ReturnStatement(new ThisExpression().CreateInvokeMethodExpression("GetPagedAsync", new TypeReference[] { method.ReturnType }, url, requestOptionsArgument, cancellationTokenArgument))); break; case MethodType.Put: var putArgs = new List <Expression>(); putArgs.Add(url); putArgs.Add((Expression)bodyArgument ?? new LiteralExpression(null)); putArgs.Add(requestOptionsArgument); putArgs.Add(cancellationTokenArgument); m.Statements.Add(new ReturnStatement(new ThisExpression().CreateInvokeMethodExpression("PutJsonAsync", new TypeReference[] { method.ReturnType }, putArgs.ToArray()))); break; case MethodType.Post: var postArgs = new List <Expression>(); postArgs.Add(url); postArgs.Add((Expression)bodyArgument ?? new LiteralExpression(null)); postArgs.Add(requestOptionsArgument); postArgs.Add(cancellationTokenArgument); if (method.ReturnType != null) { m.Statements.Add(new ReturnStatement(new ThisExpression().CreateInvokeMethodExpression("PostJsonAsync", new TypeReference[] { method.ReturnType }, postArgs.ToArray()))); } else { m.Statements.Add(new ReturnStatement(new ThisExpression().CreateInvokeMethodExpression("PostJsonAsync", postArgs.ToArray()))); } break; case MethodType.Delete: m.Statements.Add(new ReturnStatement(new ThisExpression().CreateInvokeMethodExpression("DeleteAsync", url, requestOptionsArgument, cancellationTokenArgument))); break; default: throw new NotSupportedException($"Method {method.MethodType} is not supported"); } return(m); }