Added Javascript client generator. Fixed a few bugs. Improved client generator code.
This commit is contained in:
parent
6a7f6ce096
commit
e2c5aba868
@ -7,6 +7,7 @@ using System.Net;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using MontoyaTech.Rest.Net;
|
using MontoyaTech.Rest.Net;
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
namespace MontoyaTech.Rest.Net.Example
|
namespace MontoyaTech.Rest.Net.Example
|
||||||
{
|
{
|
||||||
@ -16,6 +17,8 @@ namespace MontoyaTech.Rest.Net.Example
|
|||||||
{
|
{
|
||||||
public string Id;
|
public string Id;
|
||||||
|
|
||||||
|
public char FirstInitial;
|
||||||
|
|
||||||
public UserRole Role { get; set; }
|
public UserRole Role { get; set; }
|
||||||
|
|
||||||
public List<Permission> Permissions;
|
public List<Permission> Permissions;
|
||||||
@ -39,6 +42,8 @@ namespace MontoyaTech.Rest.Net.Example
|
|||||||
|
|
||||||
public List<string> List = null;
|
public List<string> List = null;
|
||||||
|
|
||||||
|
public string[] Array = null;
|
||||||
|
|
||||||
public ulong Property { get; set; }
|
public ulong Property { get; set; }
|
||||||
|
|
||||||
public User() { }
|
public User() { }
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
|
<AssemblyName>MontoyaTech.Rest.Net</AssemblyName>
|
||||||
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
|
<RootNamespace>MontoyaTech.Rest.Net</RootNamespace>
|
||||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
<Version>1.4.8</Version>
|
<Version>1.4.9</Version>
|
||||||
<PackageReleaseNotes></PackageReleaseNotes>
|
<PackageReleaseNotes></PackageReleaseNotes>
|
||||||
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
|
<PackageIcon>Logo_Symbol_Black_Outline.png</PackageIcon>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -33,6 +33,8 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
var writer = new CodeWriter();
|
var writer = new CodeWriter();
|
||||||
|
|
||||||
|
writer.WriteLine("//Generated using MontoyaTech.Rest.Net");
|
||||||
|
|
||||||
writer.WriteLine("using System;");
|
writer.WriteLine("using System;");
|
||||||
writer.WriteLine("using System.Net;");
|
writer.WriteLine("using System.Net;");
|
||||||
writer.WriteLine("using System.Net.Http;");
|
writer.WriteLine("using System.Net.Http;");
|
||||||
@ -233,7 +235,7 @@ namespace MontoyaTech.Rest.Net
|
|||||||
{
|
{
|
||||||
writer.WriteBreak();
|
writer.WriteBreak();
|
||||||
|
|
||||||
if (field.DeclaringType.IsEnum)
|
if (field.DeclaringType != null && field.DeclaringType.IsEnum)
|
||||||
writer.WriteLine($"{field.Name} = {field.GetRawConstantValue()},");
|
writer.WriteLine($"{field.Name} = {field.GetRawConstantValue()},");
|
||||||
else
|
else
|
||||||
writer.WriteLine($"public {this.GetTypeFullyResolvedName(field.FieldType)} {field.Name};");
|
writer.WriteLine($"public {this.GetTypeFullyResolvedName(field.FieldType)} {field.Name};");
|
||||||
@ -280,13 +282,13 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
//Output the client instance
|
//Output the client instance
|
||||||
if (!this.StaticCode)
|
if (!this.StaticCode)
|
||||||
writer.WriteBreak().WriteLine("public Client Client;");
|
writer.WriteBreak().WriteLine($"public {this.ClientName} Client;");
|
||||||
|
|
||||||
|
|
||||||
//Output the constuctor if not static.
|
//Output the constuctor if not static.
|
||||||
if (!this.StaticCode)
|
if (!this.StaticCode)
|
||||||
{
|
{
|
||||||
writer.WriteBreak().WriteLine($"public {name}Api(Client client)").WriteLine("{").Indent();
|
writer.WriteBreak().WriteLine($"public {name}Api({this.ClientName} client)").WriteLine("{").Indent();
|
||||||
|
|
||||||
writer.WriteLine("this.Client = client;");
|
writer.WriteLine("this.Client = client;");
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.Design;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
@ -22,6 +23,25 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string ClientName = "Client";
|
public string ClientName = "Client";
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether or not a given type belongs to DotNet.
|
/// Returns whether or not a given type belongs to DotNet.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -322,6 +342,25 @@ namespace MontoyaTech.Rest.Net
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the default value for a given type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected internal virtual string GetTypeDefaultValue(Type type)
|
||||||
|
{
|
||||||
|
var typeCode = Type.GetTypeCode(type);
|
||||||
|
|
||||||
|
if (type.IsEnum)
|
||||||
|
return "0";
|
||||||
|
else if (typeCode == TypeCode.String || typeCode == TypeCode.Object)
|
||||||
|
return "null";
|
||||||
|
else if (typeCode == TypeCode.Char)
|
||||||
|
return "'\0'";
|
||||||
|
else
|
||||||
|
return Activator.CreateInstance(type).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds all the route groupings from a set of routes and returns those groups.
|
/// Finds all the route groupings from a set of routes and returns those groups.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -358,22 +397,43 @@ namespace MontoyaTech.Rest.Net
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates a Rest Client from a RouteListener and returns the code.
|
/// Sorts a list of types by the order of which should be declared first. Useful
|
||||||
|
/// for languages that require classes to be dependency ordered.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="types"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public string Generate(RouteListener listener)
|
protected internal virtual List<Type> SortTypesByDependencies(List<Type> types)
|
||||||
{
|
{
|
||||||
return this.Generate(listener.Routes);
|
var dependencies = new Dictionary<Type, HashSet<Type>>();
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
//Find all the inner dependencies of the types
|
||||||
/// Generates a Rest Client from a set of routes and returns the code.
|
foreach (var type in types)
|
||||||
/// </summary>
|
if (!dependencies.ContainsKey(type))
|
||||||
/// <param name="routes"></param>
|
dependencies.Add(type, FindTypeDependencies(type).ToHashSet());
|
||||||
/// <returns></returns>
|
|
||||||
public virtual string Generate(List<Route> routes)
|
//Now begin sorting these dependencies
|
||||||
{
|
var sorted = new LinkedList<KeyValuePair<Type, HashSet<Type>>>();
|
||||||
throw new NotImplementedException();
|
foreach (var dependency in dependencies)
|
||||||
|
{
|
||||||
|
var best = sorted.Last;
|
||||||
|
var curr = sorted.Last;
|
||||||
|
while (curr != null)
|
||||||
|
{
|
||||||
|
if (dependency.Value.Contains(curr.Value.Key))
|
||||||
|
break;
|
||||||
|
else if (curr.Value.Value.Contains(dependency.Key))
|
||||||
|
best = curr.Previous;
|
||||||
|
|
||||||
|
curr = curr.Previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best == null)
|
||||||
|
sorted.AddFirst(dependency);
|
||||||
|
else
|
||||||
|
best.List.AddAfter(best, dependency);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sorted.Select(value => value.Key).ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
567
Rest.Net/RestJavascriptClientGenerator.cs
Normal file
567
Rest.Net/RestJavascriptClientGenerator.cs
Normal file
@ -0,0 +1,567 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace MontoyaTech.Rest.Net
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The outline of a rest client generator that can generate a javascript client.
|
||||||
|
/// </summary>
|
||||||
|
public class RestJavascriptClientGenerator : RestClientGenerator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not to generate static code, if true the client will be static.
|
||||||
|
/// </summary>
|
||||||
|
public bool StaticCode = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a Javascript Client from a given set of routes and returns it.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="routes"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string Generate(List<Route> routes)
|
||||||
|
{
|
||||||
|
var includedTypes = this.FindRoutesDependencies(routes);
|
||||||
|
|
||||||
|
var routeGroups = this.FindRouteGroups(routes);
|
||||||
|
|
||||||
|
var writer = new CodeWriter();
|
||||||
|
|
||||||
|
writer.WriteLine("//Generated using MontoyaTech.Rest.Net");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine($"class {this.ClientName} {{").Indent();
|
||||||
|
|
||||||
|
//Create the base url field
|
||||||
|
if (this.StaticCode)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("/** @type {string} */");
|
||||||
|
writer.WriteLine("static BaseUrl = null;");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("/** @type {string} */");
|
||||||
|
writer.WriteLine("BaseUrl = null;");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create fields foreach route group so they can be accessed.
|
||||||
|
if (!this.StaticCode)
|
||||||
|
{
|
||||||
|
foreach (var group in routeGroups)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine($"/** @type {{{group.Key}Api}} */");
|
||||||
|
writer.WriteLine($"{group.Key} = null;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Create the client constructor or init method
|
||||||
|
if (this.StaticCode)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("/** @param {string} baseUrl */");
|
||||||
|
writer.Write("static init(baseUrl) ").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
//Make sure the baseUrl isn't null or whitespace
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent();
|
||||||
|
writer.WriteLine("throw 'baseUrl cannot be null or empty.';");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//If the baseUrl ends with a /, remove it.
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl.endsWith('/')) {").Indent();
|
||||||
|
writer.WriteLine("baseUrl = baseUrl.substring(0, baseUrl.length - 1);");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//Store the baseUrl
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.BaseUrl = baseUrl;");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("/** @param {string} baseUrl */");
|
||||||
|
writer.Write("constructor(baseUrl) ").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
//Make sure the baseUrl isn't null or whitespace
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl == null || baseUrl == undefined || baseUrl.trim() == '') {").Indent();
|
||||||
|
writer.WriteLine("throw 'baseUrl cannot be null or empty.';");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//If the baseUrl ends with a /, remove it.
|
||||||
|
writer.WriteBreak().WriteLine("if (baseUrl.endsWith('/')) {").Indent();
|
||||||
|
writer.WriteLine("baseUrl = baseUrl.substring(0, baseUrl.length - 1);");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//Store the baseUrl
|
||||||
|
writer.WriteBreak().WriteLine("this.BaseUrl = baseUrl;");
|
||||||
|
|
||||||
|
//Init all the route group fields
|
||||||
|
writer.WriteBreak();
|
||||||
|
foreach (var group in routeGroups)
|
||||||
|
writer.WriteLine($"this.{group.Key} = new {group.Key}Api(this);");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
this.GenerateJavascriptRouteGroups(routeGroups, writer);
|
||||||
|
|
||||||
|
includedTypes = this.SortTypesByDependencies(includedTypes);
|
||||||
|
|
||||||
|
this.GenerateJavascriptIncludedTypes(includedTypes, writer);
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine($"window.{this.ClientName} = {this.ClientName};");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine($"export {{ {this.ClientName} }};");
|
||||||
|
|
||||||
|
return writer.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal override string GetTypeFullyResolvedName(Type type)
|
||||||
|
{
|
||||||
|
var typeCode = Type.GetTypeCode(type);
|
||||||
|
|
||||||
|
if (typeof(Array).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
return $"Array<{this.GetTypeFullyResolvedName(type.GetElementType())}>";
|
||||||
|
}
|
||||||
|
else if (typeof(IList).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
|
builder.Append("Array");
|
||||||
|
|
||||||
|
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 if (typeCode == TypeCode.String || typeCode == TypeCode.Char)
|
||||||
|
{
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.Boolean)
|
||||||
|
{
|
||||||
|
return "boolean";
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.DBNull)
|
||||||
|
{
|
||||||
|
return "object";
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.DateTime)
|
||||||
|
{
|
||||||
|
return "string";
|
||||||
|
}
|
||||||
|
else if (typeCode == TypeCode.Object)
|
||||||
|
{
|
||||||
|
if (type.DeclaringType != null && !IsTypeDotNet(type.DeclaringType))
|
||||||
|
return $"{this.GetTypeFullyResolvedName(type.DeclaringType)}{base.GetTypeFullyResolvedName(type)}";
|
||||||
|
else
|
||||||
|
return base.GetTypeFullyResolvedName(type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "number";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal override string GetTypeDefaultValue(Type type)
|
||||||
|
{
|
||||||
|
var typeCode = Type.GetTypeCode(type);
|
||||||
|
|
||||||
|
if (typeCode == TypeCode.Char || typeCode == TypeCode.DateTime)
|
||||||
|
return "null";
|
||||||
|
|
||||||
|
return base.GetTypeDefaultValue(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptIncludedTypes(List<Type> types, CodeWriter writer)
|
||||||
|
{
|
||||||
|
foreach (var type in types)
|
||||||
|
{
|
||||||
|
bool subType = false;
|
||||||
|
|
||||||
|
//See if this type belongs to another type in the list.
|
||||||
|
if (type.DeclaringType != null)
|
||||||
|
for (int i = 0; i < types.Count && !subType; i++)
|
||||||
|
if (type.DeclaringType == types[i])
|
||||||
|
subType = true;
|
||||||
|
|
||||||
|
//If not, generate the code for this type.
|
||||||
|
if (!subType)
|
||||||
|
this.GenerateJavascriptIncludedType(type, types, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptIncludedType(Type type, List<Type> types, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
var newName = type.GetCustomAttribute<RouteTypeName>();
|
||||||
|
|
||||||
|
writer.Write($"class {(type.DeclaringType != null ? type.DeclaringType.Name : "")}{(newName != null ? newName.Name : type.Name)}");
|
||||||
|
|
||||||
|
if (!type.IsEnum && !(IsTypeDotNet(type.BaseType) && type.BaseType.Name == "Object"))
|
||||||
|
writer.Write(" extends ").Write(this.GetTypeFullyResolvedName(type.BaseType));
|
||||||
|
|
||||||
|
writer.WriteLine(" {").Indent();
|
||||||
|
|
||||||
|
FieldInfo[] fields;
|
||||||
|
|
||||||
|
if (type.IsEnum)
|
||||||
|
fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Static);
|
||||||
|
else
|
||||||
|
fields = type.GetFields(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
this.GenerateJavascriptIncludedField(field, writer);
|
||||||
|
|
||||||
|
var properties = type.GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public).Where(property => property.GetSetMethod() != null && property.GetGetMethod() != null).ToArray();
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
this.GenerateJavascriptIncludedProperty(property, writer);
|
||||||
|
|
||||||
|
//Generate a helper constructor
|
||||||
|
writer.WriteBreak().WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("@method");
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(field.FieldType)}}} {field.Name}");
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(property.PropertyType)}}} {property.Name}");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
writer.Write("constructor(");
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
writer.WriteSeparator().Write(field.Name).Write(" = ").Write(this.GetTypeDefaultValue(field.FieldType));
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
writer.WriteSeparator().Write(property.Name).Write(" = ").Write(this.GetTypeDefaultValue(property.PropertyType));
|
||||||
|
|
||||||
|
writer.WriteLine(") {").Indent();
|
||||||
|
|
||||||
|
foreach (var field in fields)
|
||||||
|
writer.Write("this.").Write(field.Name).Write(" = ").Write(field.Name).WriteLine(";");
|
||||||
|
|
||||||
|
foreach (var property in properties)
|
||||||
|
writer.Write("this.").Write(property.Name).Write(" = ").Write(property.Name).WriteLine(";");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.{(type.DeclaringType != null ? type.DeclaringType.Name : "")}{(newName != null ? newName.Name : type.Name)} = {(type.DeclaringType != null ? type.DeclaringType.Name : "")}{(newName != null ? newName.Name : type.Name)};");
|
||||||
|
|
||||||
|
//Generate any types that belong to this.
|
||||||
|
for (int i = 0; i < types.Count; i++)
|
||||||
|
if (types[i].DeclaringType == type)
|
||||||
|
GenerateJavascriptIncludedType(types[i], types, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptIncludedField(FieldInfo field, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
if (field.DeclaringType != null && field.DeclaringType.IsEnum)
|
||||||
|
{
|
||||||
|
writer.WriteLine("/** @type {number} */");
|
||||||
|
writer.WriteLine($"static {field.Name} = {field.GetRawConstantValue()};");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.FieldType)}}} */");
|
||||||
|
writer.WriteLine($"{field.Name} = {GetTypeDefaultValue(field.FieldType)};");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptIncludedProperty(PropertyInfo property, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(property.PropertyType)}}} */");
|
||||||
|
writer.WriteLine($"{property.Name} = {GetTypeDefaultValue(property.PropertyType)};");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptRouteGroups(Dictionary<string, List<Route>> groups, CodeWriter writer)
|
||||||
|
{
|
||||||
|
foreach (var group in groups)
|
||||||
|
this.GenerateJavascriptRouteGroup(group.Key, group.Value, writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptRouteGroup(string name, List<Route> routes, CodeWriter writer)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
//Output the class header
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteLine($"class {name} {{").Indent();
|
||||||
|
else
|
||||||
|
writer.WriteLine($"class {name}Api {{").Indent();
|
||||||
|
|
||||||
|
//Output the client instance
|
||||||
|
if (!this.StaticCode)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine($"/** @type {{{this.ClientName}}} */");
|
||||||
|
writer.WriteLine("Client = null;");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Output the constuctor if not static.
|
||||||
|
if (!this.StaticCode)
|
||||||
|
{
|
||||||
|
writer.WriteBreak();
|
||||||
|
|
||||||
|
writer.WriteLine("/**").Indent();
|
||||||
|
writer.WriteLine("@method");
|
||||||
|
writer.WriteLine($"@param {{{this.ClientName}}} client");
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
|
||||||
|
writer.Write($"constructor(client) ").WriteLine("{").Indent();
|
||||||
|
|
||||||
|
writer.WriteLine("this.Client = client;");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Output all the route functions.
|
||||||
|
foreach (var route in routes)
|
||||||
|
this.GenerateJavascriptRouteFunction(route, writer);
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
//Expose this route group on the client.
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.{name} = {name};");
|
||||||
|
else
|
||||||
|
writer.WriteBreak().WriteLine($"{this.ClientName}.{name}Api = {name}Api;");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected internal virtual void GenerateJavascriptRouteFunction(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>();
|
||||||
|
|
||||||
|
var parameters = methodInfo.GetParameters();
|
||||||
|
|
||||||
|
//Generate the function jsdoc tags
|
||||||
|
writer.WriteLine("/**").Indent();
|
||||||
|
|
||||||
|
writer.WriteLine("@method");
|
||||||
|
writer.WriteLine("@async");
|
||||||
|
writer.WriteLine($"@name {(routeName == null ? methodInfo.Name : routeName.Name)}");
|
||||||
|
|
||||||
|
//Generate parameter docs
|
||||||
|
if (parameters != null)
|
||||||
|
for (int i = 1; i < parameters.Length; i++)
|
||||||
|
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(parameters[i].ParameterType)}}} {parameters[i].Name}");
|
||||||
|
|
||||||
|
//Generate request doc if any
|
||||||
|
if (routeRequest != null)
|
||||||
|
writer.WriteLine($"@param {{{this.GetTypeFullyResolvedName(routeRequest.RequestType)}}} request");
|
||||||
|
|
||||||
|
//Generate response doc if any
|
||||||
|
if (routeResponse != null)
|
||||||
|
writer.WriteLine($"@returns {{{this.GetTypeFullyResolvedName(routeResponse.ResponseType)}}}");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("*/");
|
||||||
|
|
||||||
|
//Generate the route function header
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.Write($"static async {(routeName == null ? methodInfo.Name : routeName.Name)}(");
|
||||||
|
else
|
||||||
|
writer.Write($"async {(routeName == null ? methodInfo.Name : routeName.Name)}(");
|
||||||
|
|
||||||
|
//Generate the functions parameters
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
writer.Write(parameters[i].Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeRequest != null)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
writer.Write("request");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routeResponse != null && routeResponse.Parameter)
|
||||||
|
{
|
||||||
|
writer.WriteSeparator();
|
||||||
|
writer.Write("input");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WriteLine(") {").Indent();
|
||||||
|
|
||||||
|
//Generate function body
|
||||||
|
|
||||||
|
writer.WriteLine("var response = await fetch(").Indent();
|
||||||
|
|
||||||
|
//Generate the request url
|
||||||
|
if (this.StaticCode)
|
||||||
|
writer.WriteSeparator().Write('`').Write($"${{{this.ClientName}.BaseUrl}}");
|
||||||
|
else
|
||||||
|
writer.WriteSeparator().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.WriteLine("`, {").Indent();
|
||||||
|
|
||||||
|
//Include credentials
|
||||||
|
writer.WriteLine("credentials: 'include',");
|
||||||
|
|
||||||
|
//Generate the method
|
||||||
|
writer.WriteLine($"method: '{route.Method.ToLower()}',");
|
||||||
|
|
||||||
|
//If we have a body, generate that
|
||||||
|
if (routeRequest != null)
|
||||||
|
{
|
||||||
|
if (routeRequest.RequestType.IsAssignableTo(typeof(Stream)))
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
else if (routeRequest.Json)
|
||||||
|
{
|
||||||
|
writer.WriteLine("body: JSON.stringify(request),");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteLine("body: request.toString(),");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine(");");
|
||||||
|
|
||||||
|
if (routeResponse != null)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("if (response.ok) {").Indent();
|
||||||
|
|
||||||
|
if (routeResponse.ResponseType.IsAssignableTo(typeof(Stream)))
|
||||||
|
{
|
||||||
|
if (routeResponse.Parameter)
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (routeResponse.Json)
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("return await response.json();");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (Type.GetTypeCode(routeResponse.ResponseType))
|
||||||
|
{
|
||||||
|
case TypeCode.Boolean:
|
||||||
|
writer.WriteBreak().WriteLine("return (await(response.text())).toLowerCase() === 'true';");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Char:
|
||||||
|
writer.WriteBreak().WriteLine("return (await(response.text()))[0];");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.DateTime:
|
||||||
|
writer.WriteBreak().WriteLine("return Date.parse((await(response.text())));");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.String:
|
||||||
|
writer.WriteBreak().WriteLine("return await response.text();");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Byte:
|
||||||
|
case TypeCode.Decimal:
|
||||||
|
case TypeCode.Double:
|
||||||
|
case TypeCode.Int16:
|
||||||
|
case TypeCode.Int32:
|
||||||
|
case TypeCode.Int64:
|
||||||
|
case TypeCode.SByte:
|
||||||
|
case TypeCode.Single:
|
||||||
|
case TypeCode.UInt16:
|
||||||
|
case TypeCode.UInt32:
|
||||||
|
case TypeCode.UInt64:
|
||||||
|
writer.WriteBreak().WriteLine("return Number((await(response.text())));");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TypeCode.Object:
|
||||||
|
throw new NotSupportedException("ResponseType isn't JSON but is an object.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Outdent().WriteLine("} else {").Indent();
|
||||||
|
writer.WriteLine("throw `Unexpected http response status: ${response.status}`;");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteBreak().WriteLine("if (!response.ok) {").Indent();
|
||||||
|
writer.WriteLine("throw `Unexpected http response status: ${response.status}`;");
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
//Close off the route function.
|
||||||
|
writer.Outdent().WriteLine("}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -240,7 +240,7 @@ namespace MontoyaTech.Rest.Net
|
|||||||
/// <param name="clientName">The name of the Client. Default is Client.</param>
|
/// <param name="clientName">The name of the Client. Default is Client.</param>
|
||||||
/// <param name="staticCode">Whether or not to generate a static client.</param>
|
/// <param name="staticCode">Whether or not to generate a static client.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public string GenerateCSharpClient(string clientName = "Client", bool staticCode =false)
|
public string GenerateCSharpClient(string clientName = "Client", bool staticCode = false)
|
||||||
{
|
{
|
||||||
var generator = new RestCSharpClientGenerator();
|
var generator = new RestCSharpClientGenerator();
|
||||||
|
|
||||||
@ -249,5 +249,21 @@ namespace MontoyaTech.Rest.Net
|
|||||||
|
|
||||||
return generator.Generate(this);
|
return generator.Generate(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a Javascript client from this Route Listener and returns the code.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="clientName"></param>
|
||||||
|
/// <param name="staticCode"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GenerateJavascriptClient(string clientName = "Client", bool staticCode = false)
|
||||||
|
{
|
||||||
|
var generator = new RestJavascriptClientGenerator();
|
||||||
|
|
||||||
|
generator.ClientName = clientName;
|
||||||
|
generator.StaticCode = staticCode;
|
||||||
|
|
||||||
|
return generator.Generate(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user