Rest.Net/Rest.Net/RestClientGenerator.cs

355 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Data;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace MontoyaTech.Rest.Net
{
/// <summary>
/// A class that can take a set of routes and generate code
/// for a client that can be used to interact with them.
/// </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>
protected virtual bool IsTypeDotNet(Type type)
{
if (type.Assembly.GetName().Name == "System.Private.CoreLib")
return true;
return false;
}
/// <summary>
/// Finds all the dependencies for a given type and returns them.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
protected virtual List<Type> FindTypeDependencies(Type type)
{
var dependencies = new HashSet<Type>();
if (this.IsTypeDotNet(type))
return dependencies.ToList();
dependencies.Add(type);
if (type.IsEnum)
return dependencies.ToList();
{
var types = this.FindTypeDependencies(type.BaseType);
for (int i = 0; i < types.Count; i++)
if (!dependencies.Contains(types[i]))
dependencies.Add(types[i]);
}
var arguments = type.GetGenericArguments();
if (arguments != null)
{
foreach (var argument in arguments)
{
var types = this.FindTypeDependencies(argument);
for (int i = 0; i < types.Count; i++)
if (!dependencies.Contains(types[i]))
dependencies.Add(types[i]);
}
}
var fields = type.GetFields();
if (fields != null)
{
foreach (var field in fields)
{
if (field.IsPublic && !field.IsSpecialName)
{
var types = this.FindTypeDependencies(field.FieldType);
for (int i = 0; i < types.Count; i++)
if (!dependencies.Contains(types[i]))
dependencies.Add(types[i]);
}
}
}
var properties = type.GetProperties();
if (properties != null)
{
foreach (var property in properties)
{
if (!property.IsSpecialName && property.GetSetMethod() != null && property.GetGetMethod() != null)
{
var types = this.FindTypeDependencies(property.PropertyType);
for (int i = 0; i < types.Count; i++)
if (!dependencies.Contains(types[i]))
dependencies.Add(types[i]);
}
}
}
return dependencies.ToList();
}
/// <summary>
/// Finds all the types that a given route depends on to function.
/// </summary>
/// <param name="route"></param>
/// <returns></returns>
protected virtual List<Type> FindRouteDependencies(Route route)
{
var dependencies = new HashSet<Type>();
var methodInfo = route.GetTarget().GetMethodInfo();
//If this route is hidden, return nothing.
if (methodInfo.GetCustomAttribute<RouteHidden>() != null)
return dependencies.ToList();
if (methodInfo != null)
{
var parameters = methodInfo.GetParameters();
if (parameters != null)
{
for (int i = 1; i < parameters.Length; i++)
{
var types = this.FindTypeDependencies(parameters[i].ParameterType);
foreach (var type in types)
if (!dependencies.Contains(type))
dependencies.Add(type);
}
}
var routeRequest = methodInfo.GetCustomAttribute<RouteRequest>();
if (routeRequest != null)
{
var types = this.FindTypeDependencies(routeRequest.RequestType);
foreach (var type in types)
if (!dependencies.Contains(type))
dependencies.Add(type);
}
var routeResponse = methodInfo.GetCustomAttribute<RouteResponse>();
if (routeResponse != null)
{
var types = this.FindTypeDependencies(routeResponse.ResponseType);
foreach (var type in types)
if (!dependencies.Contains(type))
dependencies.Add(type);
}
}
return dependencies.ToList();
}
/// <summary>
/// Finds all the types that a given set of routes depend on to function.
/// </summary>
/// <param name="routes"></param>
/// <returns></returns>
protected virtual List<Type> FindRoutesDependencies(List<Route> routes)
{
var dependencies = new HashSet<Type>();
foreach (var route in routes)
{
var types = this.FindRouteDependencies(route);
if (types != null)
for (int i = 0; i < types.Count; i++)
if (!dependencies.Contains(types[i]))
dependencies.Add(types[i]);
}
return dependencies.ToList();
}
/// <summary>
/// Returns the fully resolve name for a given type.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
protected virtual string GetTypeFullyResolvedName(Type type)
{
if (this.IsTypeDotNet(type))
{
var typeCode = Type.GetTypeCode(type);
switch (typeCode)
{
case TypeCode.Boolean:
return "bool";
case TypeCode.Byte:
return "byte";
case TypeCode.Char:
return "char";
case TypeCode.DateTime:
return "DateTime";
case TypeCode.Decimal:
return "decimal";
case TypeCode.Double:
return "double";
case TypeCode.Int16:
return "short";
case TypeCode.Int32:
return "int";
case TypeCode.Int64:
return "long";
case TypeCode.SByte:
return "sbyte";
case TypeCode.Single:
return "float";
case TypeCode.String:
return "string";
case TypeCode.UInt16:
return "ushort";
case TypeCode.UInt32:
return "uint";
case TypeCode.UInt64:
return "ulong";
default:
var builder = new StringBuilder();
builder.Append(type.Namespace);
int genericSymbol = type.Name.IndexOf('`');
if (genericSymbol == -1)
builder.Append(".").Append(type.Name);
else
builder.Append(".").Append(type.Name.Substring(0, genericSymbol));
var genericArguments = type.GetGenericArguments();
if (genericArguments != null && genericArguments.Length > 0)
{
builder.Append("<");
for (int i = 0; i < genericArguments.Length; i++)
{
if (i > 0)
builder.Append(", ");
builder.Append(this.GetTypeFullyResolvedName(genericArguments[i]));
}
builder.Append(">");
}
return builder.ToString();
}
}
else
{
var builder = new StringBuilder();
int genericSymbol = type.Name.IndexOf('`');
if (genericSymbol == -1)
builder.Append(type.Name);
else
builder.Append(type.Name.Substring(0, genericSymbol));
var genericArguments = type.GetGenericArguments();
if (genericArguments != null && genericArguments.Length > 0)
{
builder.Append("<");
for (int i = 0; i < genericArguments.Length; i++)
{
if (i > 0)
builder.Append(", ");
builder.Append(this.GetTypeFullyResolvedName(genericArguments[i]));
}
builder.Append(">");
}
return builder.ToString();
}
}
/// <summary>
/// Finds all the route groupings from a set of routes and returns those groups.
/// </summary>
/// <param name="routes"></param>
/// <returns></returns>
protected virtual Dictionary<string, List<Route>> FindRouteGroups(List<Route> routes)
{
var groups = new Dictionary<string, List<Route>>();
foreach (var route in routes)
{
var methodInfo = route.GetTarget().GetMethodInfo();
//Skip any routes that are hidden.
if (methodInfo.GetCustomAttribute<RouteHidden>() != null)
continue;
var routeGroup = methodInfo.GetCustomAttribute<RouteGroup>();
//If there wasn't a route group on the method, see if there is one on the declaring type.
if (routeGroup == null)
routeGroup = methodInfo.DeclaringType.GetCustomAttribute<RouteGroup>();
//Group this route.
string group = (routeGroup != null ? routeGroup.Name : methodInfo.DeclaringType.Name);
if (groups.ContainsKey(group))
groups[group].Add(route);
else
groups.Add(group, new List<Route>() { route });
}
return groups;
}
/// <summary>
/// Generates a Rest Client from a RouteListener and returns the code.
/// </summary>
/// <returns></returns>
public string Generate(RouteListener listener)
{
return this.Generate(listener.Routes);
}
/// <summary>
/// Generates a Rest Client from a set of routes and returns the code.
/// </summary>
/// <param name="routes"></param>
/// <returns></returns>
public virtual string Generate(List<Route> routes)
{
throw new NotImplementedException();
}
}
}