Broke up RestClientGenerator so that custom generators for other languages can be created or the C# one could be modified for custom use cases.
This commit is contained in:
parent
ed1d10ba9d
commit
2076a1d02b
@ -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);
|
||||
|
||||
|
@ -10,7 +10,7 @@ namespace MontoyaTech.Rest.Net
|
||||
/// <summary>
|
||||
/// The outline of a writer that helps with generating code.
|
||||
/// </summary>
|
||||
internal class CodeWriter
|
||||
public class CodeWriter
|
||||
{
|
||||
/// <summary>
|
||||
/// The internal string builder.
|
||||
|
@ -17,7 +17,7 @@
|
||||
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
|
||||
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<Version>1.2.2</Version>
|
||||
<Version>1.2.3</Version>
|
||||
<PackageReleaseNotes></PackageReleaseNotes>
|
||||
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
|
||||
</PropertyGroup>
|
||||
|
397
Rest.Net/RestCSharpClientGenerator.cs
Normal file
397
Rest.Net/RestCSharpClientGenerator.cs
Normal file
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// The outline of a Rest Client Generator that can generate a C# Client.
|
||||
/// </summary>
|
||||
public class RestCSharpClientGenerator : RestClientGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates a CSharp Client from a given set of routes and returns it.
|
||||
/// </summary>
|
||||
/// <param name="routes"></param>
|
||||
/// <returns></returns>
|
||||
public override string Generate(List<Route> 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<RouteHidden>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a set of included types.
|
||||
/// </summary>
|
||||
/// <param name="types"></param>
|
||||
/// <param name="writer"></param>
|
||||
protected virtual void GenerateCSharpIncludedTypes(List<Type> types, CodeWriter writer)
|
||||
{
|
||||
foreach (var type in types)
|
||||
this.GenerateCSharpIncludedType(type, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a given included type.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="writer"></param>
|
||||
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("}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a field inside an included type.
|
||||
/// </summary>
|
||||
/// <param name="field"></param>
|
||||
/// <param name="writer"></param>
|
||||
protected virtual void GenerateCSharpIncludedField(FieldInfo field, CodeWriter writer)
|
||||
{
|
||||
writer.WriteBreak();
|
||||
|
||||
writer.WriteLine($"public {this.GetTypeFullyResolvedName(field.FieldType)} {field.Name};");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a property inside an included type.
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="writer"></param>
|
||||
protected virtual void GenerateCSharpIncludedProperty(PropertyInfo property, CodeWriter writer)
|
||||
{
|
||||
writer.WriteBreak();
|
||||
|
||||
writer.WriteLine($"public {this.GetTypeFullyResolvedName(property.PropertyType)} {property.Name} {{ get; set; }}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a set of route groups.
|
||||
/// </summary>
|
||||
/// <param name="groups"></param>
|
||||
/// <param name="writer"></param>
|
||||
protected virtual void GenerateCSharpRouteGroups(Dictionary<string, List<Route>> groups, CodeWriter writer)
|
||||
{
|
||||
foreach (var group in groups)
|
||||
this.GenerateCSharpRouteGroup(group.Key, group.Value, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a given route group.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="routes"></param>
|
||||
/// <param name="writer"></param>
|
||||
protected virtual void GenerateCSharpRouteGroup(string name, List<Route> 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("}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a C# function for a given route.
|
||||
/// </summary>
|
||||
/// <param name="route"></param>
|
||||
/// <param name="writer"></param>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
protected virtual void GenerateCSharpRouteFunction(Route route, CodeWriter writer)
|
||||
{
|
||||
writer.WriteBreak();
|
||||
|
||||
var methodInfo = route.GetTarget().GetMethodInfo();
|
||||
|
||||
var routeName = methodInfo.GetCustomAttribute<RouteName>();
|
||||
|
||||
var routeRequest = methodInfo.GetCustomAttribute<RouteRequest>();
|
||||
|
||||
var routeResponse = methodInfo.GetCustomAttribute<RouteResponse>();
|
||||
|
||||
//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("}");
|
||||
}
|
||||
}
|
||||
}
|
@ -17,12 +17,17 @@ namespace MontoyaTech.Rest.Net
|
||||
/// </summary>
|
||||
public class RestClientGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the client to generate.
|
||||
/// </summary>
|
||||
public string ClientName = "Client";
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether or not a given type belongs to DotNet.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
private static List<Type> FindTypeDependencies(Type type)
|
||||
protected virtual List<Type> FindTypeDependencies(Type type)
|
||||
{
|
||||
var dependencies = new HashSet<Type>();
|
||||
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="route"></param>
|
||||
/// <returns></returns>
|
||||
private static List<Type> FindRouteDependencies(Route route)
|
||||
protected virtual List<Type> FindRouteDependencies(Route route)
|
||||
{
|
||||
var dependencies = new HashSet<Type>();
|
||||
|
||||
@ -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
|
||||
/// </summary>
|
||||
/// <param name="routes"></param>
|
||||
/// <returns></returns>
|
||||
private static List<Type> FindRoutesDependencies(List<Route> routes)
|
||||
protected virtual List<Type> FindRoutesDependencies(List<Route> routes)
|
||||
{
|
||||
var dependencies = new HashSet<Type>();
|
||||
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
/// </summary>
|
||||
/// <param name="routes"></param>
|
||||
/// <returns></returns>
|
||||
private static Dictionary<string, List<Route>> FindRouteGroups(List<Route> routes)
|
||||
protected virtual Dictionary<string, List<Route>> FindRouteGroups(List<Route> routes)
|
||||
{
|
||||
var groups = new Dictionary<string, List<Route>>();
|
||||
|
||||
@ -309,405 +314,20 @@ namespace MontoyaTech.Rest.Net
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a CSharpClient from a RouteListener.
|
||||
/// Generates a Rest Client from a RouteListener and returns the code.
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
/// <param name="name">The name of the Client class, default is Client.</param>
|
||||
/// <returns>The generated C# code.</returns>
|
||||
public static string GenerateCSharpClient(RouteListener listener, string name = "Client")
|
||||
{
|
||||
return GenerateCSharpClient(listener.Routes, name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a CSharpClient from a given set of Routes.
|
||||
/// </summary>
|
||||
/// <param name="routes"></param>
|
||||
/// <param name="name">The name of the Client class, default is Client.</param>
|
||||
/// <returns></returns>
|
||||
public static string GenerateCSharpClient(List<Route> 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<RouteHidden>();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a set of included types.
|
||||
/// </summary>
|
||||
/// <param name="types"></param>
|
||||
/// <param name="writer"></param>
|
||||
private static void GenerateCSharpIncludedTypes(List<Type> types, CodeWriter writer)
|
||||
{
|
||||
foreach (var type in types)
|
||||
GenerateCSharpIncludedType(type, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a given included type.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="writer"></param>
|
||||
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("}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a field inside an included type.
|
||||
/// </summary>
|
||||
/// <param name="field"></param>
|
||||
/// <param name="writer"></param>
|
||||
private static void GenerateCSharpIncludedField(FieldInfo field, CodeWriter writer)
|
||||
{
|
||||
writer.WriteBreak();
|
||||
|
||||
writer.WriteLine($"public {GetTypeFullyResolvedName(field.FieldType)} {field.Name};");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a property inside an included type.
|
||||
/// </summary>
|
||||
/// <param name="property"></param>
|
||||
/// <param name="writer"></param>
|
||||
private static void GenerateCSharpIncludedProperty(PropertyInfo property, CodeWriter writer)
|
||||
{
|
||||
writer.WriteBreak();
|
||||
|
||||
writer.WriteLine($"public {GetTypeFullyResolvedName(property.PropertyType)} {property.Name} {{ get; set; }}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a set of route groups.
|
||||
/// </summary>
|
||||
/// <param name="groups"></param>
|
||||
/// <param name="writer"></param>
|
||||
private static void GenerateCSharpRouteGroups(Dictionary<string, List<Route>> groups, CodeWriter writer)
|
||||
{
|
||||
foreach (var group in groups)
|
||||
GenerateCSharpRouteGroup(group.Key, group.Value, writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates C# for a given route group.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="routes"></param>
|
||||
/// <param name="writer"></param>
|
||||
private static void GenerateCSharpRouteGroup(string name, List<Route> 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("}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a C# function for a given route.
|
||||
/// </summary>
|
||||
/// <param name="route"></param>
|
||||
/// <param name="writer"></param>
|
||||
/// <exception cref="NotSupportedException"></exception>
|
||||
private static void GenerateCSharpRouteFunction(Route route, CodeWriter writer)
|
||||
{
|
||||
writer.WriteBreak();
|
||||
|
||||
var methodInfo = route.GetTarget().GetMethodInfo();
|
||||
|
||||
var routeName = methodInfo.GetCustomAttribute<RouteName>();
|
||||
|
||||
var routeRequest = methodInfo.GetCustomAttribute<RouteRequest>();
|
||||
|
||||
var routeResponse = methodInfo.GetCustomAttribute<RouteResponse>();
|
||||
|
||||
//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("}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a Javascript client from a given set of routes.
|
||||
/// Generates a Rest Client from a set of routes and returns the code.
|
||||
/// </summary>
|
||||
/// <param name="routes"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static string GenerateJavascriptClient(List<Route> routes)
|
||||
public virtual string Generate(List<Route> routes)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
@ -223,11 +223,15 @@ namespace MontoyaTech.Rest.Net
|
||||
/// <summary>
|
||||
/// Generates a C# client from this Route Listener and returns the code.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the Client. Default is Client.</param>
|
||||
/// <param name="clientName">The name of the Client. Default is Client.</param>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user