From 2076a1d02bc941f8b9a2bf271d554186c18411d3 Mon Sep 17 00:00:00 2001 From: MattMo Date: Mon, 6 Feb 2023 10:46:38 -0800 Subject: [PATCH] Broke up RestClientGenerator so that custom generators for other languages can be created or the C# one could be modified for custom use cases. --- Rest.Net.Example/Program.cs | 2 +- Rest.Net/CodeWriter.cs | 2 +- Rest.Net/Rest.Net.csproj | 2 +- Rest.Net/RestCSharpClientGenerator.cs | 397 +++++++++++++++++++++++ Rest.Net/RestClientGenerator.cs | 434 ++------------------------ Rest.Net/RouteListener.cs | 10 +- 6 files changed, 434 insertions(+), 413 deletions(-) create mode 100644 Rest.Net/RestCSharpClientGenerator.cs diff --git a/Rest.Net.Example/Program.cs b/Rest.Net.Example/Program.cs index 0b327b5..181aca9 100644 --- a/Rest.Net.Example/Program.cs +++ b/Rest.Net.Example/Program.cs @@ -37,7 +37,7 @@ namespace MontoyaTech.Rest.Net.Example new Route(HttpRequestMethod.Get, "/auth/", Json) ); - string code = RestClientGenerator.GenerateCSharpClient(listener.Routes); + string code = listener.GenerateCSharpClient(); Console.WriteLine(code); diff --git a/Rest.Net/CodeWriter.cs b/Rest.Net/CodeWriter.cs index b28521a..35d580f 100644 --- a/Rest.Net/CodeWriter.cs +++ b/Rest.Net/CodeWriter.cs @@ -10,7 +10,7 @@ namespace MontoyaTech.Rest.Net /// /// The outline of a writer that helps with generating code. /// - internal class CodeWriter + public class CodeWriter { /// /// The internal string builder. diff --git a/Rest.Net/Rest.Net.csproj b/Rest.Net/Rest.Net.csproj index 9c2df86..c559a22 100644 --- a/Rest.Net/Rest.Net.csproj +++ b/Rest.Net/Rest.Net.csproj @@ -17,7 +17,7 @@ MontoyaTech.Rest.Net MontoyaTech.Rest.Net True - 1.2.2 + 1.2.3 Logo_Symbol_Black_Outline.png diff --git a/Rest.Net/RestCSharpClientGenerator.cs b/Rest.Net/RestCSharpClientGenerator.cs new file mode 100644 index 0000000..9fdc610 --- /dev/null +++ b/Rest.Net/RestCSharpClientGenerator.cs @@ -0,0 +1,397 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Linq; + +namespace MontoyaTech.Rest.Net +{ + /// + /// The outline of a Rest Client Generator that can generate a C# Client. + /// + public class RestCSharpClientGenerator : RestClientGenerator + { + /// + /// Generates a CSharp Client from a given set of routes and returns it. + /// + /// + /// + public override string Generate(List routes) + { + //Remove any hidden routes from code generation. + for (int i = 0; i < routes.Count; i++) + { + var methodInfo = routes[i].GetTarget().GetMethodInfo(); + + var routeHidden = methodInfo.GetCustomAttribute(); + + if (routeHidden != null) + { + routes.RemoveAt(i); + i--; + } + } + + var includedTypes = this.FindRoutesDependencies(routes); + + var routeGroups = this.FindRouteGroups(routes); + + var writer = new CodeWriter(); + + writer.WriteLine("using System;"); + writer.WriteLine("using System.Net.Http;"); + writer.WriteLine("using Newtonsoft.Json;"); + + writer.WriteBreak().WriteLine($"public class {this.ClientName}").WriteLine("{").Indent(); + + writer.WriteBreak().WriteLine("public string BaseUrl;"); + + writer.WriteBreak().WriteLine("public HttpClient HttpClient;"); + + //Create fields foreach route group so they can be accessed. + foreach (var group in routeGroups) + writer.WriteBreak().WriteLine($"public {group.Key}Api {group.Key};"); + + //Create the client constructor + writer.WriteBreak().WriteLine("public Client(string baseUrl)").WriteLine("{").Indent(); + + //Init the base url + writer.WriteLine("this.BaseUrl = baseUrl;"); + + //Init all the route group fields + foreach (var group in routeGroups) + writer.WriteLine($"this.{group.Key} = new {group.Key}Api(this);"); + + //Init the http client + writer.WriteLine("this.HttpClient = new HttpClient();"); + writer.WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Accept"", ""*/*"");"); + writer.WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Connection"", ""keep-alive"");"); + writer.WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");"); + + writer.Outdent().WriteLine("}"); + + this.GenerateCSharpRouteGroups(routeGroups, writer); + + this.GenerateCSharpIncludedTypes(includedTypes, writer); + + writer.Outdent().WriteLine("}"); + + return writer.ToString(); + } + + /// + /// Generates C# for a set of included types. + /// + /// + /// + protected virtual void GenerateCSharpIncludedTypes(List types, CodeWriter writer) + { + foreach (var type in types) + this.GenerateCSharpIncludedType(type, writer); + } + + /// + /// Generates C# for a given included type. + /// + /// + /// + protected virtual void GenerateCSharpIncludedType(Type type, CodeWriter writer) + { + writer.WriteBreak(); + + writer.WriteLine($"public class {type.Name}").WriteLine("{").Indent(); + + var fields = type.GetFields(); + + if (fields != null) + foreach (var field in fields) + if (field.IsPublic) + this.GenerateCSharpIncludedField(field, writer); + + var properties = type.GetProperties(); + + if (properties != null) + foreach (var property in properties) + if (property.GetSetMethod() != null && property.GetGetMethod() != null) + this.GenerateCSharpIncludedProperty(property, writer); + + writer.Outdent().WriteLine("}"); + } + + /// + /// Generates C# for a field inside an included type. + /// + /// + /// + protected virtual void GenerateCSharpIncludedField(FieldInfo field, CodeWriter writer) + { + writer.WriteBreak(); + + writer.WriteLine($"public {this.GetTypeFullyResolvedName(field.FieldType)} {field.Name};"); + } + + /// + /// Generates C# for a property inside an included type. + /// + /// + /// + protected virtual void GenerateCSharpIncludedProperty(PropertyInfo property, CodeWriter writer) + { + writer.WriteBreak(); + + writer.WriteLine($"public {this.GetTypeFullyResolvedName(property.PropertyType)} {property.Name} {{ get; set; }}"); + } + + /// + /// Generates C# for a set of route groups. + /// + /// + /// + protected virtual void GenerateCSharpRouteGroups(Dictionary> groups, CodeWriter writer) + { + foreach (var group in groups) + this.GenerateCSharpRouteGroup(group.Key, group.Value, writer); + } + + /// + /// Generates C# for a given route group. + /// + /// + /// + /// + protected virtual void GenerateCSharpRouteGroup(string name, List routes, CodeWriter writer) + { + writer.WriteBreak(); + + writer.WriteLine($"public class {name}Api").WriteLine("{").Indent(); + + writer.WriteBreak(); + + writer.WriteLine("public Client Client;"); + + writer.WriteBreak(); + + writer.WriteLine($"public {name}Api(Client client)").WriteLine("{").Indent(); + + writer.WriteLine("this.Client = client;"); + + writer.Outdent().WriteLine("}"); + + foreach (var route in routes) + this.GenerateCSharpRouteFunction(route, writer); + + writer.Outdent().WriteLine("}"); + } + + /// + /// Generates a C# function for a given route. + /// + /// + /// + /// + protected virtual void GenerateCSharpRouteFunction(Route route, CodeWriter writer) + { + writer.WriteBreak(); + + var methodInfo = route.GetTarget().GetMethodInfo(); + + var routeName = methodInfo.GetCustomAttribute(); + + var routeRequest = methodInfo.GetCustomAttribute(); + + var routeResponse = methodInfo.GetCustomAttribute(); + + //Generate the route function header + writer.Write($"public {(routeResponse == null ? "void" : this.GetTypeFullyResolvedName(routeResponse.ResponseType))} {(routeName == null ? methodInfo.Name : routeName.Name)}("); + + //Generate the functions parameters + var parameters = methodInfo.GetParameters(); + + if (parameters != null) + { + for (int i = 1; i < parameters.Length; i++) + { + writer.WriteSeparator(); + writer.Write(this.GetTypeFullyResolvedName(parameters[i].ParameterType)).Write(" ").Write(parameters[i].Name); + } + } + + if (routeRequest != null) + { + writer.WriteSeparator(); + writer.Write(this.GetTypeFullyResolvedName(routeRequest.RequestType)).Write(" request"); + } + + writer.WriteLine(")").WriteLine("{").Indent(); + + //Generate the message code + writer.WriteBreak().Write($"var message = new HttpRequestMessage("); + + switch (route.Method.ToLower()) + { + case "post": + writer.Write("HttpMethod.Post"); + break; + case "get": + writer.Write("HttpMethod.Get"); + break; + case "delete": + writer.Write("HttpMethod.Delete"); + break; + case "put": + writer.Write("HttpMethod.Put"); + break; + case "options": + writer.Write("HttpMethod.Options"); + break; + case "patch": + writer.Write("HttpMethod.Patch"); + break; + case "head": + writer.Write("HttpMethod.Head"); + break; + case "trace": + writer.Write("HttpMethod.Trace"); + break; + default: + throw new NotSupportedException("Unsupport route method:" + route.Method); + } + + writer.WriteSeparator().Write('$').Write('"').Write("{this.Client.BaseUrl}"); + + //Reconstruct the route syntax into a request url. + var components = route.Syntax.Split('/'); + int argumentIndex = 0; + foreach (var component in components) + { + if (!string.IsNullOrWhiteSpace(component)) + { + writer.Write('/'); + + if (component.StartsWith("{")) + { + writer.Write("{").Write(parameters[argumentIndex++ + 1].Name).Write("}"); + } + else if (component == "*") + { + writer.Write("*"); + } + else if (component == "**") + { + break; + } + else + { + writer.Write(component); + } + } + } + + writer.Write('"').WriteLine(");"); + + //Add the request content if any. + if (routeRequest != null) + { + if (routeRequest.Json) + writer.WriteBreak().WriteLine("message.Content = new StringContent(JsonConvert.SerializeObject(request));"); + else + writer.WriteBreak().WriteLine("message.Content = new StringContent(request.ToString());"); + } + + //Generate the response code + writer.WriteBreak().WriteLine("var response = this.Client.HttpClient.Send(message);"); + + //Handle the response + if (routeResponse != null) + { + writer.WriteBreak().WriteLine("if (response.StatusCode == System.Net.HttpStatusCode.OK)").Indent(); + + if (routeResponse.Json) + { + writer.WriteLine($"return JsonConvert.DeserializeObject<{this.GetTypeFullyResolvedName(routeResponse.ResponseType)}>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + } + else + { + switch (Type.GetTypeCode(routeResponse.ResponseType)) + { + case TypeCode.Boolean: + writer.WriteLine("return bool.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.Byte: + writer.WriteLine("return byte.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.Char: + writer.WriteLine("return response.Content.ReadAsStringAsync().GetAwaiter().GetResult()[0];"); + break; + + case TypeCode.DateTime: + writer.WriteLine("return DateTime.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.Decimal: + writer.WriteLine("return decimal.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.Double: + writer.WriteLine("return double.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.Int16: + writer.WriteLine("return short.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.Int32: + writer.WriteLine("return int.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.Int64: + writer.WriteLine("return long.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.SByte: + writer.WriteLine("return sbyte.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.Single: + writer.WriteLine("return float.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.String: + writer.WriteLine("return response.Content.ReadAsStringAsync().GetAwaiter().GetResult();"); + break; + + case TypeCode.UInt16: + writer.WriteLine("return ushort.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.UInt32: + writer.WriteLine("return uint.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.UInt64: + writer.WriteLine("return ulong.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); + break; + + case TypeCode.Object: + throw new NotSupportedException("ResponseType isn't JSON but is an object."); + } + } + + writer.Outdent().WriteLine("else").Indent(); + writer.WriteLine(@"throw new Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);").Outdent(); + } + else + { + writer.WriteBreak().WriteLine("if (response.StatusCode == System.Net.HttpStatusCode.OK)").Indent(); + writer.WriteLine(@"throw new Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);").Outdent(); + } + + //Close off the route function. + writer.Outdent().WriteLine("}"); + } + } +} diff --git a/Rest.Net/RestClientGenerator.cs b/Rest.Net/RestClientGenerator.cs index d05ddbf..67ee307 100644 --- a/Rest.Net/RestClientGenerator.cs +++ b/Rest.Net/RestClientGenerator.cs @@ -17,12 +17,17 @@ namespace MontoyaTech.Rest.Net /// public class RestClientGenerator { + /// + /// The name of the client to generate. + /// + public string ClientName = "Client"; + /// /// Returns whether or not a given type belongs to DotNet. /// /// /// - private static bool IsTypeDotNet(Type type) + protected virtual bool IsTypeDotNet(Type type) { if (type.Assembly.GetName().Name == "System.Private.CoreLib") return true; @@ -35,11 +40,11 @@ namespace MontoyaTech.Rest.Net /// /// /// - private static List FindTypeDependencies(Type type) + protected virtual List FindTypeDependencies(Type type) { var dependencies = new HashSet(); - if (IsTypeDotNet(type)) + if (this.IsTypeDotNet(type)) return dependencies.ToList(); dependencies.Add(type); @@ -50,7 +55,7 @@ namespace MontoyaTech.Rest.Net { foreach (var argument in arguments) { - var types = FindTypeDependencies(argument); + var types = this.FindTypeDependencies(argument); for (int i = 0; i < types.Count; i++) if (!dependencies.Contains(types[i])) @@ -66,7 +71,7 @@ namespace MontoyaTech.Rest.Net { if (field.IsPublic) { - var types = FindTypeDependencies(field.FieldType); + var types = this.FindTypeDependencies(field.FieldType); for (int i = 0; i < types.Count; i++) if (!dependencies.Contains(types[i])) @@ -83,7 +88,7 @@ namespace MontoyaTech.Rest.Net { if (property.GetSetMethod() != null && property.GetGetMethod() != null) { - var types = FindTypeDependencies(property.PropertyType); + var types = this.FindTypeDependencies(property.PropertyType); for (int i = 0; i < types.Count; i++) if (!dependencies.Contains(types[i])) @@ -100,7 +105,7 @@ namespace MontoyaTech.Rest.Net /// /// /// - private static List FindRouteDependencies(Route route) + protected virtual List FindRouteDependencies(Route route) { var dependencies = new HashSet(); @@ -114,7 +119,7 @@ namespace MontoyaTech.Rest.Net { for (int i = 1; i < parameters.Length; i++) { - var types = FindTypeDependencies(parameters[i].ParameterType); + var types = this.FindTypeDependencies(parameters[i].ParameterType); foreach (var type in types) if (!dependencies.Contains(type)) @@ -126,7 +131,7 @@ namespace MontoyaTech.Rest.Net if (routeRequest != null) { - var types = FindTypeDependencies(routeRequest.RequestType); + var types = this.FindTypeDependencies(routeRequest.RequestType); foreach (var type in types) if (!dependencies.Contains(type)) @@ -137,7 +142,7 @@ namespace MontoyaTech.Rest.Net if (routeResponse != null) { - var types = FindTypeDependencies(routeResponse.ResponseType); + var types = this.FindTypeDependencies(routeResponse.ResponseType); foreach (var type in types) if (!dependencies.Contains(type)) @@ -153,13 +158,13 @@ namespace MontoyaTech.Rest.Net /// /// /// - private static List FindRoutesDependencies(List routes) + protected virtual List FindRoutesDependencies(List routes) { var dependencies = new HashSet(); foreach (var route in routes) { - var types = FindRouteDependencies(route); + var types = this.FindRouteDependencies(route); if (types != null) for (int i = 0; i < types.Count; i++) @@ -175,9 +180,9 @@ namespace MontoyaTech.Rest.Net /// /// /// - private static string GetTypeFullyResolvedName(Type type) + protected virtual string GetTypeFullyResolvedName(Type type) { - if (IsTypeDotNet(type)) + if (this.IsTypeDotNet(type)) { var typeCode = Type.GetTypeCode(type); @@ -236,7 +241,7 @@ namespace MontoyaTech.Rest.Net if (i > 0) builder.Append(", "); - builder.Append(GetTypeFullyResolvedName(genericArguments[i])); + builder.Append(this.GetTypeFullyResolvedName(genericArguments[i])); } builder.Append(">"); @@ -267,7 +272,7 @@ namespace MontoyaTech.Rest.Net if (i > 0) builder.Append(", "); - builder.Append(GetTypeFullyResolvedName(genericArguments[i])); + builder.Append(this.GetTypeFullyResolvedName(genericArguments[i])); } builder.Append(">"); @@ -282,7 +287,7 @@ namespace MontoyaTech.Rest.Net /// /// /// - private static Dictionary> FindRouteGroups(List routes) + protected virtual Dictionary> FindRouteGroups(List routes) { var groups = new Dictionary>(); @@ -309,405 +314,20 @@ namespace MontoyaTech.Rest.Net } /// - /// Generates a CSharpClient from a RouteListener. + /// Generates a Rest Client from a RouteListener and returns the code. /// - /// - /// The name of the Client class, default is Client. - /// The generated C# code. - public static string GenerateCSharpClient(RouteListener listener, string name = "Client") - { - return GenerateCSharpClient(listener.Routes, name); - } - - /// - /// Generates a CSharpClient from a given set of Routes. - /// - /// - /// The name of the Client class, default is Client. /// - public static string GenerateCSharpClient(List routes, string name = "Client") + public string Generate(RouteListener listener) { - //Remove any hidden routes from code generation. - for (int i = 0; i < routes.Count; i++) - { - var methodInfo = routes[i].GetTarget().GetMethodInfo(); - - var routeHidden = methodInfo.GetCustomAttribute(); - - if (routeHidden != null) - { - routes.RemoveAt(i); - i--; - } - } - - var includedTypes = FindRoutesDependencies(routes); - - var routeGroups = FindRouteGroups(routes); - - var writer = new CodeWriter(); - - writer.WriteLine("using System;"); - writer.WriteLine("using System.Net.Http;"); - writer.WriteLine("using Newtonsoft.Json;"); - - writer.WriteBreak().WriteLine($"public class {name}").WriteLine("{").Indent(); - - writer.WriteBreak().WriteLine("public string BaseUrl;"); - - writer.WriteBreak().WriteLine("public HttpClient HttpClient;"); - - //Create fields foreach route group so they can be accessed. - foreach (var group in routeGroups) - writer.WriteBreak().WriteLine($"public {group.Key}Api {group.Key};"); - - //Create the client constructor - writer.WriteBreak().WriteLine("public Client(string baseUrl)").WriteLine("{").Indent(); - - //Init the base url - writer.WriteLine("this.BaseUrl = baseUrl;"); - - //Init all the route group fields - foreach (var group in routeGroups) - writer.WriteLine($"this.{group.Key} = new {group.Key}Api(this);"); - - //Init the http client - writer.WriteLine("this.HttpClient = new HttpClient();"); - writer.WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Accept"", ""*/*"");"); - writer.WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Connection"", ""keep-alive"");"); - writer.WriteLine(@"this.HttpClient.DefaultRequestHeaders.Add(""Accept-Encoding"", ""identity"");"); - - writer.Outdent().WriteLine("}"); - - GenerateCSharpRouteGroups(routeGroups, writer); - - GenerateCSharpIncludedTypes(includedTypes, writer); - - writer.Outdent().WriteLine("}"); - - return writer.ToString(); + return this.Generate(listener.Routes); } /// - /// Generates C# for a set of included types. - /// - /// - /// - private static void GenerateCSharpIncludedTypes(List types, CodeWriter writer) - { - foreach (var type in types) - GenerateCSharpIncludedType(type, writer); - } - - /// - /// Generates C# for a given included type. - /// - /// - /// - private static void GenerateCSharpIncludedType(Type type, CodeWriter writer) - { - writer.WriteBreak(); - - writer.WriteLine($"public class {type.Name}").WriteLine("{").Indent(); - - var fields = type.GetFields(); - - if (fields != null) - foreach (var field in fields) - if (field.IsPublic) - GenerateCSharpIncludedField(field, writer); - - var properties = type.GetProperties(); - - if (properties != null) - foreach (var property in properties) - if (property.GetSetMethod() != null && property.GetGetMethod() != null) - GenerateCSharpIncludedProperty(property, writer); - - writer.Outdent().WriteLine("}"); - } - - /// - /// Generates C# for a field inside an included type. - /// - /// - /// - private static void GenerateCSharpIncludedField(FieldInfo field, CodeWriter writer) - { - writer.WriteBreak(); - - writer.WriteLine($"public {GetTypeFullyResolvedName(field.FieldType)} {field.Name};"); - } - - /// - /// Generates C# for a property inside an included type. - /// - /// - /// - private static void GenerateCSharpIncludedProperty(PropertyInfo property, CodeWriter writer) - { - writer.WriteBreak(); - - writer.WriteLine($"public {GetTypeFullyResolvedName(property.PropertyType)} {property.Name} {{ get; set; }}"); - } - - /// - /// Generates C# for a set of route groups. - /// - /// - /// - private static void GenerateCSharpRouteGroups(Dictionary> groups, CodeWriter writer) - { - foreach (var group in groups) - GenerateCSharpRouteGroup(group.Key, group.Value, writer); - } - - /// - /// Generates C# for a given route group. - /// - /// - /// - /// - private static void GenerateCSharpRouteGroup(string name, List routes, CodeWriter writer) - { - writer.WriteBreak(); - - writer.WriteLine($"public class {name}Api").WriteLine("{").Indent(); - - writer.WriteBreak(); - - writer.WriteLine("public Client Client;"); - - writer.WriteBreak(); - - writer.WriteLine($"public {name}Api(Client client)").WriteLine("{").Indent(); - - writer.WriteLine("this.Client = client;"); - - writer.Outdent().WriteLine("}"); - - foreach (var route in routes) - GenerateCSharpRouteFunction(route, writer); - - writer.Outdent().WriteLine("}"); - } - - /// - /// Generates a C# function for a given route. - /// - /// - /// - /// - private static void GenerateCSharpRouteFunction(Route route, CodeWriter writer) - { - writer.WriteBreak(); - - var methodInfo = route.GetTarget().GetMethodInfo(); - - var routeName = methodInfo.GetCustomAttribute(); - - var routeRequest = methodInfo.GetCustomAttribute(); - - var routeResponse = methodInfo.GetCustomAttribute(); - - //Generate the route function header - writer.Write($"public {(routeResponse == null ? "void" : GetTypeFullyResolvedName(routeResponse.ResponseType))} {(routeName == null ? methodInfo.Name : routeName.Name)}("); - - //Generate the functions parameters - var parameters = methodInfo.GetParameters(); - - if (parameters != null) - { - for (int i = 1; i < parameters.Length; i++) - { - writer.WriteSeparator(); - writer.Write(GetTypeFullyResolvedName(parameters[i].ParameterType)).Write(" ").Write(parameters[i].Name); - } - } - - if (routeRequest != null) - { - writer.WriteSeparator(); - writer.Write(GetTypeFullyResolvedName(routeRequest.RequestType)).Write(" request"); - } - - writer.WriteLine(")").WriteLine("{").Indent(); - - //Generate the message code - writer.WriteBreak().Write($"var message = new HttpRequestMessage("); - - switch (route.Method.ToLower()) - { - case "post": - writer.Write("HttpMethod.Post"); - break; - case "get": - writer.Write("HttpMethod.Get"); - break; - case "delete": - writer.Write("HttpMethod.Delete"); - break; - case "put": - writer.Write("HttpMethod.Put"); - break; - case "options": - writer.Write("HttpMethod.Options"); - break; - case "patch": - writer.Write("HttpMethod.Patch"); - break; - case "head": - writer.Write("HttpMethod.Head"); - break; - case "trace": - writer.Write("HttpMethod.Trace"); - break; - default: - throw new NotSupportedException("Unsupport route method:" + route.Method); - } - - writer.WriteSeparator().Write('$').Write('"').Write("{this.Client.BaseUrl}"); - - //Reconstruct the route syntax into a request url. - var components = route.Syntax.Split('/'); - int argumentIndex = 0; - foreach (var component in components) - { - if (!string.IsNullOrWhiteSpace(component)) - { - writer.Write('/'); - - if (component.StartsWith("{")) - { - writer.Write("{").Write(parameters[argumentIndex++ + 1].Name).Write("}"); - } - else if (component == "*") - { - writer.Write("*"); - } - else if (component == "**") - { - break; - } - else - { - writer.Write(component); - } - } - } - - writer.Write('"').WriteLine(");"); - - //Add the request content if any. - if (routeRequest != null) - { - if (routeRequest.Json) - writer.WriteBreak().WriteLine("message.Content = new StringContent(JsonConvert.SerializeObject(request));"); - else - writer.WriteBreak().WriteLine("message.Content = new StringContent(request.ToString());"); - } - - //Generate the response code - writer.WriteBreak().WriteLine("var response = this.Client.HttpClient.Send(message);"); - - //Handle the response - if (routeResponse != null) - { - writer.WriteBreak().WriteLine("if (response.StatusCode == System.Net.HttpStatusCode.OK)").Indent(); - - if (routeResponse.Json) - { - writer.WriteLine($"return JsonConvert.DeserializeObject<{GetTypeFullyResolvedName(routeResponse.ResponseType)}>(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - } - else - { - switch (Type.GetTypeCode(routeResponse.ResponseType)) - { - case TypeCode.Boolean: - writer.WriteLine("return bool.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.Byte: - writer.WriteLine("return byte.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.Char: - writer.WriteLine("return response.Content.ReadAsStringAsync().GetAwaiter().GetResult()[0];"); - break; - - case TypeCode.DateTime: - writer.WriteLine("return DateTime.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.Decimal: - writer.WriteLine("return decimal.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.Double: - writer.WriteLine("return double.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.Int16: - writer.WriteLine("return short.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.Int32: - writer.WriteLine("return int.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.Int64: - writer.WriteLine("return long.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.SByte: - writer.WriteLine("return sbyte.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.Single: - writer.WriteLine("return float.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.String: - writer.WriteLine("return response.Content.ReadAsStringAsync().GetAwaiter().GetResult();"); - break; - - case TypeCode.UInt16: - writer.WriteLine("return ushort.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.UInt32: - writer.WriteLine("return uint.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.UInt64: - writer.WriteLine("return ulong.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult());"); - break; - - case TypeCode.Object: - throw new NotSupportedException("ResponseType isn't JSON but is an object."); - } - } - - writer.Outdent().WriteLine("else").Indent(); - writer.WriteLine(@"throw new Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);").Outdent(); - } - else - { - writer.WriteBreak().WriteLine("if (response.StatusCode == System.Net.HttpStatusCode.OK)").Indent(); - writer.WriteLine(@"throw new Exception(""Unexpected Http Response StatusCode:"" + response.StatusCode);").Outdent(); - } - - //Close off the route function. - writer.Outdent().WriteLine("}"); - } - - /// - /// Generates a Javascript client from a given set of routes. + /// Generates a Rest Client from a set of routes and returns the code. /// /// /// - /// - public static string GenerateJavascriptClient(List routes) + public virtual string Generate(List routes) { throw new NotImplementedException(); } diff --git a/Rest.Net/RouteListener.cs b/Rest.Net/RouteListener.cs index 8e47df0..2365b84 100644 --- a/Rest.Net/RouteListener.cs +++ b/Rest.Net/RouteListener.cs @@ -223,11 +223,15 @@ namespace MontoyaTech.Rest.Net /// /// Generates a C# client from this Route Listener and returns the code. /// - /// The name of the Client. Default is Client. + /// The name of the Client. Default is Client. /// - public string GenerateCSharpClient(string name = "Client") + public string GenerateCSharpClient(string clientName = "Client") { - return RestClientGenerator.GenerateCSharpClient(this, name); + var generator = new RestCSharpClientGenerator(); + + generator.ClientName = clientName; + + return generator.Generate(this); } } }