Files
Rest.Net/Rest.Net/RestJavascriptClientGenerator.cs

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("}");
}
}
}