867 lines
36 KiB
C#
867 lines
36 KiB
C#
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. Default is false.
|
|
/// </summary>
|
|
public bool StaticCode = false;
|
|
|
|
/// <summary>
|
|
/// Whether or not to use Json Property names instead of the Field/Property names. Default is false.
|
|
/// </summary>
|
|
public bool UseJsonNames = false;
|
|
|
|
/// <summary>
|
|
/// Whether or not to generate route functions to accept named parameters instead
|
|
/// of positional ones. Default is false.
|
|
/// </summary>
|
|
public bool NamedParameters = false;
|
|
|
|
/// <summary>
|
|
/// The minimum number of parameters before using named parameters. Default is 0.
|
|
/// </summary>
|
|
public int NamedParameterMin = 0;
|
|
|
|
/// <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 - {DateTime.Now.ToShortDateString()}");
|
|
|
|
writer.WriteBreak().WriteLine($"class {this.ClientName} {{").Indent();
|
|
|
|
//Create the base url field
|
|
writer.WriteBreak().WriteLine("/** @type {string} */");
|
|
writer.WriteAssert(this.StaticCode, "static ").WriteLine("BaseUrl = null;");
|
|
|
|
//Create the url handler field
|
|
writer.WriteBreak().WriteLine("/** @type {Function} */");
|
|
writer.WriteAssert(this.StaticCode, "static ").WriteLine("UrlHandler = null;");
|
|
|
|
//Create the request handler field.
|
|
writer.WriteBreak().WriteLine("/** @type {Function} */");
|
|
writer.WriteAssert(this.StaticCode, "static ").WriteLine("RequestHandler = 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("/**").Indent();
|
|
writer.WriteLine("Initializes this api client with a given baseUrl of where to send requests.");
|
|
writer.WriteLine("@param {string} baseUrl Base url of the server to make requests against.");
|
|
writer.WriteLine("@param {Function} urlHandler An optional function to process request urls before they are sent. This must return the url. Default is null.");
|
|
writer.WriteLine("@param {Function} requestHandler An optional function to process requests before they are sent. This must return the request. Default is null.");
|
|
writer.Outdent().WriteLine("*/");
|
|
if (this.NamedParameters)
|
|
writer.Write("static Init({ baseUrl, urlHandler = null, requestHandler = null } = {}) ");
|
|
else
|
|
writer.Write("static Init(baseUrl, urlHandler = null, requestHandler = null) ");
|
|
writer.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;");
|
|
|
|
//Store the urlHandler
|
|
writer.WriteBreak().WriteLine($"{this.ClientName}.UrlHandler = urlHandler;");
|
|
|
|
//Store the requestHandler
|
|
writer.WriteBreak().WriteLine($"{this.ClientName}.RequestHandler = requestHandler;");
|
|
|
|
writer.Outdent().WriteLine("}");
|
|
}
|
|
else
|
|
{
|
|
writer.WriteBreak().WriteLine("/**").Indent();
|
|
writer.WriteLine("Initializes this api client with a given baseUrl of where to send requests.");
|
|
writer.WriteLine("@param {string} baseUrl Base url of the server to make requests against.");
|
|
writer.WriteLine("@param {Function} urlHandler An optional function to process request urls before they are sent. This must return the url. Default is null.");
|
|
writer.WriteLine("@param {Function} requestHandler An optional function to process requests before they are sent. This must return the request. Default is null.");
|
|
writer.Outdent().WriteLine("*/");
|
|
if (this.NamedParameters)
|
|
writer.Write("constructor({ baseUrl, urlHandler = null, requestHandler = null } = {}) ");
|
|
else
|
|
writer.Write("constructor(baseUrl, urlHandler = null, requestHandler = null) ");
|
|
writer.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;");
|
|
|
|
//Store the urlHandler
|
|
writer.WriteBreak().WriteLine($"this.UrlHandler = urlHandler;");
|
|
|
|
//Store the request handler
|
|
writer.WriteBreak().WriteLine("this.RequestHandler = requestHandler;");
|
|
|
|
//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};");
|
|
|
|
//Export the Client and all the Types
|
|
writer.WriteBreak().WriteLine("export {").Indent();
|
|
|
|
writer.WriteLine($"{this.ClientName},");
|
|
|
|
foreach (var type in includedTypes)
|
|
{
|
|
var newName = type.GetCustomAttribute<RouteTypeName>();
|
|
|
|
writer.WriteLine($"{(type.DeclaringType != null ? type.DeclaringType.Name : "")}{(newName != null ? newName.Name : type.Name)},");
|
|
}
|
|
|
|
writer.Outdent().WriteLine("};");
|
|
|
|
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 (type.IsAssignableTo(typeof(Stream)))
|
|
{
|
|
return "Blob";
|
|
}
|
|
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 || type.IsEnum)
|
|
{
|
|
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 string EscapeName(string name)
|
|
{
|
|
if (name != null)
|
|
{
|
|
switch (name)
|
|
{
|
|
case "arguments":
|
|
case "await":
|
|
case "break":
|
|
case "case":
|
|
case "catch":
|
|
case "class":
|
|
case "const":
|
|
case "continue":
|
|
case "debugger":
|
|
case "default":
|
|
case "delete":
|
|
case "do":
|
|
case "double":
|
|
case "else":
|
|
case "enum":
|
|
case "eval":
|
|
case "export":
|
|
case "extends":
|
|
case "false":
|
|
case "finally":
|
|
case "for":
|
|
case "function":
|
|
case "goto":
|
|
case "if":
|
|
case "import":
|
|
case "in":
|
|
case "instanceof":
|
|
case "interface":
|
|
case "let":
|
|
case "new":
|
|
case "null":
|
|
case "return":
|
|
case "static":
|
|
case "super":
|
|
case "switch":
|
|
case "this":
|
|
case "throw":
|
|
case "true":
|
|
case "try":
|
|
case "typeof":
|
|
case "var":
|
|
case "void":
|
|
case "while":
|
|
case "with":
|
|
case "yield":
|
|
return "_" + name;
|
|
}
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
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
|
|
if (!type.IsEnum)
|
|
{
|
|
writer.WriteBreak().WriteLine("/**").Indent();
|
|
writer.WriteLine("@function");
|
|
|
|
//Docuemnt the fields
|
|
foreach (var field in fields)
|
|
{
|
|
if (this.UseJsonNames)
|
|
writer.Write($"@param {{{this.GetTypeFullyResolvedName(field.FieldType)}}} {EscapeName(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)}");
|
|
else
|
|
writer.Write($"@param {{{this.GetTypeFullyResolvedName(field.FieldType)}}} {EscapeName(field.Name)}");
|
|
|
|
writer.WriteSpacer().Write("Default is ").Write(this.GetTypeDefaultValue(field.FieldType)).WriteLine('.');
|
|
}
|
|
|
|
//Document the properties
|
|
foreach (var property in properties)
|
|
{
|
|
if (this.UseJsonNames)
|
|
writer.Write($"@param {{{this.GetTypeFullyResolvedName(property.PropertyType)}}} {EscapeName(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name)}");
|
|
else
|
|
writer.Write($"@param {{{this.GetTypeFullyResolvedName(property.PropertyType)}}} {EscapeName(property.Name)}");
|
|
|
|
writer.WriteSpacer().Write("Default is ").Write(this.GetTypeDefaultValue(property.PropertyType)).WriteLine('.');
|
|
}
|
|
|
|
writer.Outdent().WriteLine("*/");
|
|
writer.Write("constructor(");
|
|
|
|
var parameterCount = fields.Length + properties.Length;
|
|
|
|
writer.WriteAssert(this.UseJsonNames && parameterCount > this.NamedParameterMin, "{ ");
|
|
|
|
//Write the default fields
|
|
foreach (var field in fields)
|
|
{
|
|
if (this.UseJsonNames)
|
|
writer.WriteSeparator().Write(EscapeName(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)).Write(" = ").Write(this.GetTypeDefaultValue(field.FieldType));
|
|
else
|
|
writer.WriteSeparator().Write(EscapeName(field.Name)).Write(" = ").Write(this.GetTypeDefaultValue(field.FieldType));
|
|
}
|
|
|
|
//Write the default properties
|
|
foreach (var property in properties)
|
|
{
|
|
if (this.UseJsonNames)
|
|
writer.WriteSeparator().Write(EscapeName(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name)).Write(" = ").Write(this.GetTypeDefaultValue(property.PropertyType));
|
|
else
|
|
writer.WriteSeparator().Write(EscapeName(property.Name)).Write(" = ").Write(this.GetTypeDefaultValue(property.PropertyType));
|
|
}
|
|
|
|
writer.WriteAssert(this.UseJsonNames && parameterCount > this.NamedParameterMin, " } = {}");
|
|
writer.WriteLine(") {").Indent();
|
|
|
|
//Init the default fields
|
|
foreach (var field in fields)
|
|
{
|
|
if (this.UseJsonNames)
|
|
writer.Write("this.").Write(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name).Write(" = ").Write(EscapeName(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)).WriteLine(";");
|
|
else
|
|
writer.Write("this.").Write(field.Name).Write(" = ").Write(EscapeName(field.Name)).WriteLine(";");
|
|
}
|
|
|
|
//Init the default properties
|
|
foreach (var property in properties)
|
|
{
|
|
if (this.UseJsonNames)
|
|
writer.Write("this.").Write(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name).Write(" = ").Write(EscapeName(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name)).WriteLine(";");
|
|
else
|
|
writer.Write("this.").Write(property.Name).Write(" = ").Write(EscapeName(property.Name)).WriteLine(";");
|
|
}
|
|
|
|
writer.Outdent().WriteLine("}");
|
|
}
|
|
|
|
//If this is an enum, generate a GetNames/GetValues/GetName function.
|
|
if (type.IsEnum)
|
|
{
|
|
var names = Enum.GetNames(type);
|
|
|
|
var values = Enum.GetValues(type);
|
|
|
|
//GetName function
|
|
writer.WriteBreak().WriteLine("/**").Indent();
|
|
writer.WriteLine("Returns the name of a value in this enum. Returns null if the value is invalid.");
|
|
writer.WriteLine("@function");
|
|
writer.WriteLine("@param {number} value The value to get the name of in this enum.");
|
|
writer.WriteLine("@returns {string} The name for the given value.");
|
|
writer.Outdent().WriteLine("*/");
|
|
writer.WriteLine("static GetName(value) {").Indent();
|
|
writer.WriteLine("switch (value) {").Indent();
|
|
|
|
for (int i = 0; i < values.Length; i++)
|
|
writer.WriteLine($"case {Convert.ToInt32(values.GetValue(i))}: return `{names.GetValue(i)}`;");
|
|
|
|
writer.Outdent().WriteLine("}");
|
|
writer.WriteLine("return null;");
|
|
writer.Outdent().WriteLine("}");
|
|
|
|
//GetValue function
|
|
writer.WriteBreak().WriteLine("/**").Indent();
|
|
writer.WriteLine("Returns the value for a name in this enum. Returns null if the name is invalid.");
|
|
writer.WriteLine("@function");
|
|
writer.WriteLine("@param {string} name The name of the item in this enum to get the value of.");
|
|
writer.WriteLine("@returns {number} The value associated with this name in this enum.");
|
|
writer.Outdent().WriteLine("*/");
|
|
writer.WriteLine("static GetValue(name) {").Indent();
|
|
writer.WriteLine("if (!name) {").Indent();
|
|
writer.WriteLine("return null;");
|
|
writer.Outdent().WriteLine("}");
|
|
writer.WriteLine("switch (name.toLowerCase().trim()) {").Indent();
|
|
|
|
for (int i = 0; i < names.Length; i++)
|
|
writer.WriteLine($"case '{names.GetValue(i).ToString().ToLower()}': return {Convert.ToInt32(values.GetValue(i))};");
|
|
|
|
writer.Outdent().WriteLine("}");
|
|
writer.WriteLine("return null;");
|
|
writer.Outdent().WriteLine("}");
|
|
|
|
//GetNames function
|
|
writer.WriteBreak().WriteLine("/**").Indent();
|
|
writer.WriteLine("Returns the names of this enums values as an array of strings.");
|
|
writer.WriteLine("@function");
|
|
writer.WriteLine("@returns {Array<string>}");
|
|
writer.Outdent().WriteLine("*/");
|
|
writer.WriteLine("static GetNames() {").Indent();
|
|
writer.WriteLine("return [").Indent();
|
|
|
|
foreach (var name in names)
|
|
writer.WriteLine($"'{name}',");
|
|
|
|
writer.Outdent().WriteLine("];");
|
|
writer.Outdent().WriteLine("}");
|
|
|
|
//GetValues function
|
|
writer.WriteBreak().WriteLine("/**").Indent();
|
|
writer.WriteLine("Returns the values of this enum as an arrray of numbers.");
|
|
writer.WriteLine("@function");
|
|
writer.WriteLine("@returns {Array<number>}");
|
|
writer.Outdent().WriteLine("*/");
|
|
writer.WriteLine("static GetValues() {").Indent();
|
|
writer.WriteLine("return [").Indent();
|
|
|
|
foreach (var value in values)
|
|
writer.WriteLine($"{Convert.ToInt32(value)},");
|
|
|
|
writer.Outdent().WriteLine("];");
|
|
writer.Outdent().WriteLine("}");
|
|
|
|
writer.WriteBreak().WriteLine("/**").Indent();
|
|
writer.WriteLine("Returns the names and values of this enum in an array.");
|
|
writer.WriteLine("@function");
|
|
writer.WriteLine("@returns {Array<object>} Where each element is an object and has a name and value field. Ex: { name: '', value: 0 }");
|
|
writer.Outdent().WriteLine("*/");
|
|
writer.WriteLine("static GetNamesValues() {").Indent();
|
|
writer.WriteLine("return [").Indent();
|
|
|
|
for (int i = 0; i < names.Length; i++)
|
|
writer.WriteLine($"{{ name: `{names[i]}`, value: {Convert.ToInt32(values.GetValue(i))} }},");
|
|
|
|
writer.Outdent().WriteLine("];");
|
|
writer.Outdent().WriteLine("}");
|
|
}
|
|
|
|
//Close off the class
|
|
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 {{{GetTypeFullyResolvedName(field.DeclaringType)}}} */");
|
|
|
|
if (this.UseJsonNames)
|
|
writer.WriteLine($"static {(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)} = {field.GetRawConstantValue()};");
|
|
else
|
|
writer.WriteLine($"static {field.Name} = {field.GetRawConstantValue()};");
|
|
}
|
|
else
|
|
{
|
|
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(field.FieldType)}}} */");
|
|
|
|
if (this.UseJsonNames)
|
|
writer.WriteLine($"{(field.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? field.Name)} = {GetTypeDefaultValue(field.FieldType)};");
|
|
else
|
|
writer.WriteLine($"{field.Name} = {GetTypeDefaultValue(field.FieldType)};");
|
|
}
|
|
}
|
|
|
|
protected internal virtual void GenerateJavascriptIncludedProperty(PropertyInfo property, CodeWriter writer)
|
|
{
|
|
writer.WriteBreak();
|
|
|
|
writer.WriteLine($"/** @type {{{GetTypeFullyResolvedName(property.PropertyType)}}} */");
|
|
|
|
if (this.UseJsonNames)
|
|
writer.WriteLine($"{(property.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>()?.PropertyName ?? property.Name)} = {GetTypeDefaultValue(property.PropertyType)};");
|
|
else
|
|
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("@function");
|
|
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("@function");
|
|
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 {{{(routeRequest.Dynamic ? "Any" : this.GetTypeFullyResolvedName(routeRequest.RequestType))}}} body");
|
|
|
|
//Generate response doc if any
|
|
if (routeResponse != null)
|
|
writer.WriteLine($"@returns {{{(routeResponse.Dynamic ? "Any" : this.GetTypeFullyResolvedName(routeResponse.ResponseType))}}}");
|
|
|
|
writer.WriteLine("@throws {Response} If response status was not ok.");
|
|
|
|
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)}(");
|
|
|
|
int parameterCount = (parameters.Length - 1) + (routeRequest != null ? 1 : 0) + (routeResponse != null && routeResponse.Parameter ? 1 : 0);
|
|
|
|
writer.WriteAssert(this.NamedParameters && parameterCount > this.NamedParameterMin, "{ ");
|
|
|
|
//Generate the functions parameters, skip the default context parameter.
|
|
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("body");
|
|
}
|
|
|
|
if (routeResponse != null && routeResponse.Parameter)
|
|
{
|
|
writer.WriteSeparator();
|
|
writer.Write("input");
|
|
}
|
|
|
|
writer.WriteAssert(this.NamedParameters && parameterCount > this.NamedParameterMin, " } = {}");
|
|
writer.WriteLine(") {").Indent();
|
|
|
|
//Generate the url
|
|
writer.WriteBreak().Write("var url = ");
|
|
|
|
//Generate the request url
|
|
if (this.StaticCode)
|
|
writer.Write('`').Write($"${{{this.ClientName}.BaseUrl}}");
|
|
else
|
|
writer.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 (writer.Peek() != '/')
|
|
writer.Write('/');
|
|
|
|
if (!string.IsNullOrWhiteSpace(component))
|
|
{
|
|
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("`;");
|
|
|
|
//Generate the request
|
|
writer.WriteBreak().WriteLine("var request = {").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)))
|
|
{
|
|
writer.WriteLine("headers: new Headers({ 'Content-Type': 'application/octet-stream' }),");
|
|
writer.WriteLine("body: body,");
|
|
}
|
|
else if (routeRequest.Json)
|
|
{
|
|
|
|
writer.WriteLine("body: JSON.stringify(body),");
|
|
}
|
|
else
|
|
{
|
|
writer.WriteLine("body: body.toString(),");
|
|
}
|
|
}
|
|
|
|
writer.Outdent().WriteLine("};");
|
|
|
|
//Generate the response
|
|
writer.WriteBreak().Write("var response = await fetch(");
|
|
|
|
if (this.StaticCode)
|
|
writer.Write($"{this.ClientName}.UrlHandler ? {this.ClientName}.UrlHandler(url) : url");
|
|
else
|
|
writer.Write($"this.Client.UrlHandler ? this.Client.UrlHandler(url) : url");
|
|
|
|
writer.WriteSeparator();
|
|
|
|
if (this.StaticCode)
|
|
writer.Write($"{this.ClientName}.RequestHandler ? {this.ClientName}.RequestHandler(request) : request");
|
|
else
|
|
writer.Write("this.Client.RequestHandler ? this.Client.RequestHandler(request) : request");
|
|
|
|
writer.WriteLine(");");
|
|
|
|
//Generate code to handle the response
|
|
if (routeResponse != null)
|
|
{
|
|
writer.WriteBreak().WriteLine("if (response.ok) {").Indent();
|
|
|
|
if (routeResponse.ResponseType != null && 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("}");
|
|
|
|
writer.WriteBreak().WriteLine("throw response;");
|
|
}
|
|
else
|
|
{
|
|
writer.WriteBreak().WriteLine("if (!response.ok) {").Indent();
|
|
writer.WriteLine("throw response;");
|
|
writer.Outdent().WriteLine("}");
|
|
}
|
|
|
|
//Close off the route function.
|
|
writer.Outdent().WriteLine("}");
|
|
}
|
|
}
|
|
}
|