private static string GetHttpMethodAttribute(HttpMethodInfo httpMethodInfo) { if (httpMethodInfo.Method == HttpMethod.Delete || httpMethodInfo.Method == HttpMethod.Get || httpMethodInfo.Method == HttpMethod.Head || httpMethodInfo.Method == HttpMethod.Options || httpMethodInfo.Method == new HttpMethod("PATCH") || httpMethodInfo.Method == HttpMethod.Post || httpMethodInfo.Method == HttpMethod.Put) { return("Http" + CodeGenUtility.Capitalize(httpMethodInfo.Method.ToString().ToLowerInvariant())); } else { return($"AcceptVerbs(\"{httpMethodInfo.Method}\")"); } }
/// <summary> /// Generates the ASP.NET controller. /// </summary> public override CodeGenOutput GenerateOutput(ServiceInfo serviceInfo) { string serviceName = serviceInfo.Name; string apiNamespaceName = ApiNamespaceName ?? CSharpUtility.GetNamespaceName(serviceInfo); string namespaceName = NamespaceName ?? $"{apiNamespaceName}.Controllers"; string controllerName = $"{CodeGenUtility.Capitalize(serviceName)}Controller"; var httpServiceInfo = HttpServiceInfo.Create(serviceInfo); return(new CodeGenOutput(CreateFile($"{controllerName}{CSharpUtility.FileExtension}", code => { CSharpUtility.WriteFileHeader(code, GeneratorName); List <string> usings = new List <string> { "System", "System.Net.Http", "System.Threading", "System.Threading.Tasks", "Facility.Core", TargetFramework == AspNetFramework.WebApi ? "System.Web.Http" : "Microsoft.AspNetCore.Mvc", apiNamespaceName }; CSharpUtility.WriteUsings(code, usings, namespaceName); code.WriteLine("#pragma warning disable 1591 // missing XML comment"); code.WriteLine(); code.WriteLine($"namespace {namespaceName}"); using (code.Block()) { CSharpUtility.WriteCodeGenAttribute(code, GeneratorName); code.WriteLine($"public partial class {controllerName}"); using (code.Block()) { foreach (var httpMethodInfo in httpServiceInfo.Methods) { var methodInfo = httpMethodInfo.ServiceMethod; string methodName = CodeGenUtility.Capitalize(methodInfo.Name); code.WriteLineSkipOnce(); CSharpUtility.WriteObsoleteAttribute(code, methodInfo); code.WriteLine($"[{GetHttpMethodAttribute(httpMethodInfo)}, Route(\"{httpMethodInfo.Path.Substring(1)}\")]"); code.WriteLine($"public Task<HttpResponseMessage> {methodName}(HttpRequestMessage httpRequest, CancellationToken cancellationToken = default(CancellationToken))"); using (code.Block()) code.WriteLine($"return GetServiceHttpHandler().TryHandle{methodName}Async(httpRequest, cancellationToken);"); } } } }))); }
private void WriteDto(CodeWriter code, ServiceDtoInfo dtoInfo, ServiceInfo service) { code.WriteLine(); WriteJsDoc(code, dtoInfo); using (code.Block($"export interface I{CodeGenUtility.Capitalize(dtoInfo.Name)} {{", "}")) { foreach (var fieldInfo in dtoInfo.Fields) { code.WriteLineSkipOnce(); WriteJsDoc(code, fieldInfo); code.WriteLine($"{fieldInfo.Name}?: {RenderFieldType(service.GetFieldType(fieldInfo))};"); } } }
private string RenderFieldType(ServiceTypeInfo fieldType) { switch (fieldType.Kind) { case ServiceTypeKind.String: case ServiceTypeKind.Bytes: case ServiceTypeKind.Enum: return("string"); case ServiceTypeKind.Boolean: return("boolean"); case ServiceTypeKind.Double: case ServiceTypeKind.Int32: case ServiceTypeKind.Int64: case ServiceTypeKind.Decimal: return("number"); case ServiceTypeKind.Object: return("{ [name: string]: any }"); case ServiceTypeKind.Error: return("IServiceError"); case ServiceTypeKind.Dto: return($"I{CodeGenUtility.Capitalize(fieldType.Dto.Name)}"); case ServiceTypeKind.Result: return($"IServiceResult<{RenderFieldType(fieldType.ValueType)}>"); case ServiceTypeKind.Array: return($"{RenderFieldType(fieldType.ValueType)}[]"); case ServiceTypeKind.Map: return($"{{ [name: string]: {RenderFieldType(fieldType.ValueType)} }}"); default: throw new NotSupportedException("Unknown field type " + fieldType.Kind); } }
public static string GetDtoName(ServiceDtoInfo dtoInfo) => CodeGenUtility.Capitalize(dtoInfo.Name) + "Dto";
public static string GetMethodName(ServiceMethodInfo methodInfo) => CodeGenUtility.Capitalize(methodInfo.Name);
public static string GetInterfaceName(ServiceInfo serviceInfo) => $"I{CodeGenUtility.Capitalize(serviceInfo.Name)}";
public void CapitalizeNumber() { CodeGenUtility.Capitalize("1234").ShouldBe("1234"); }
public static string GetEnumValueName(ServiceEnumValueInfo enumValue) => CodeGenUtility.Capitalize(enumValue.Name);
public static string GetNamespaceName(ServiceInfo serviceInfo) { return(serviceInfo.TryGetAttribute("javascript")?.TryGetParameterValue("namespace") ?? CodeGenUtility.Capitalize(serviceInfo.Name)); }
public static string GetRequestDtoName(ServiceMethodInfo methodInfo) { return(CodeGenUtility.Capitalize(methodInfo.Name) + "RequestDto"); }
/// <summary> /// Gets the property name for the specified field. /// </summary> public string GetFieldPropertyName(ServiceFieldInfo field) => m_fieldPropertyNames.TryGetValue(field, out var value) ? value : CodeGenUtility.Capitalize(field.Name);
public static string GetErrorSetName(ServiceErrorSetInfo errorSetInfo) { return(CodeGenUtility.Capitalize(errorSetInfo.Name)); }
/// <summary> /// Generates the C# output. /// </summary> protected override CodeGenOutput GenerateOutputCore(ServiceInfo service) { var httpServiceInfo = new HttpServiceInfo(service); string moduleName = ModuleName ?? service.Name; string capModuleName = CodeGenUtility.Capitalize(moduleName); return(new CodeGenOutput(CreateNamedText(Uncapitalize(moduleName) + (TypeScript ? ".ts" : ".js"), code => { code.WriteLine($"// DO NOT EDIT: generated by {GeneratorName}"); if (!TypeScript) { code.WriteLine("'use strict';"); } code.WriteLine(); code.WriteLine("import { HttpClientUtility" + IfTypeScript(", IServiceResult, IServiceError, IHttpClientOptions") + " } from 'facility-core';"); code.WriteLine(); WriteJSDoc(code, $"Provides access to {capModuleName} over HTTP via fetch."); using (code.Block("export function createHttpClient({ fetch, baseUri }" + IfTypeScript(": IHttpClientOptions") + ")" + IfTypeScript($": I{capModuleName}") + " {", "}")) code.WriteLine($"return new {capModuleName}HttpClient(fetch, baseUri);"); if (TypeScript) { code.WriteLine(); WriteJSDoc(code, service); using (code.Block($"export interface I{capModuleName} {{", "}")) { foreach (var httpMethodInfo in httpServiceInfo.Methods) { string methodName = httpMethodInfo.ServiceMethod.Name; string capMethodName = CodeGenUtility.Capitalize(methodName); code.WriteLineSkipOnce(); WriteJSDoc(code, httpMethodInfo.ServiceMethod); code.WriteLine($"{methodName}(request: I{capMethodName}Request): Promise<IServiceResult<I{capMethodName}Response>>;"); } } foreach (var methodInfo in service.Methods) { WriteDto(code, new ServiceDtoInfo( name: $"{CodeGenUtility.Capitalize(methodInfo.Name)}Request", fields: methodInfo.RequestFields, summary: $"Request for {CodeGenUtility.Capitalize(methodInfo.Name)}.", position: methodInfo.Position), service); WriteDto(code, new ServiceDtoInfo( name: $"{CodeGenUtility.Capitalize(methodInfo.Name)}Response", fields: methodInfo.ResponseFields, summary: $"Response for {CodeGenUtility.Capitalize(methodInfo.Name)}.", position: methodInfo.Position), service); } foreach (var dtoInfo in service.Dtos) { WriteDto(code, dtoInfo, service); } } code.WriteLine(); code.WriteLine("const { fetchResponse, createResponseError, createRequiredRequestFieldError } = HttpClientUtility;"); if (TypeScript) { code.WriteLine("type IFetch = HttpClientUtility.IFetch;"); code.WriteLine("type IFetchRequest = HttpClientUtility.IFetchRequest;"); } code.WriteLine(); using (code.Block($"class {capModuleName}HttpClient" + IfTypeScript($" implements I{capModuleName}") + " {", "}")) { using (code.Block("constructor(fetch" + IfTypeScript(": IFetch") + ", baseUri" + IfTypeScript(": string") + ") {", "}")) { using (code.Block("if (typeof fetch !== 'function') {", "}")) code.WriteLine("throw new TypeError('fetch must be a function.');"); using (code.Block("if (typeof baseUri === 'undefined') {", "}")) code.WriteLine($"baseUri = '{httpServiceInfo.Url ?? ""}';"); using (code.Block(@"if (/[^\/]$/.test(baseUri)) {", "}")) code.WriteLine("baseUri += '/';"); code.WriteLine("this._fetch = fetch;"); code.WriteLine("this._baseUri = baseUri;"); } foreach (var httpMethodInfo in httpServiceInfo.Methods) { string methodName = httpMethodInfo.ServiceMethod.Name; string capMethodName = CodeGenUtility.Capitalize(methodName); code.WriteLine(); WriteJSDoc(code, httpMethodInfo.ServiceMethod); using (code.Block(IfTypeScript("public ") + $"{methodName}(request" + IfTypeScript($": I{capMethodName}Request") + ")" + IfTypeScript($": Promise<IServiceResult<I{capMethodName}Response>>") + " {", "}")) { bool hasPathFields = httpMethodInfo.PathFields.Count != 0; string jsUriDelim = hasPathFields ? "`" : "'"; string jsUri = jsUriDelim + httpMethodInfo.Path.Substring(1) + jsUriDelim; if (hasPathFields) { foreach (var httpPathField in httpMethodInfo.PathFields) { code.WriteLine($"const uriPart{CodeGenUtility.Capitalize(httpPathField.ServiceField.Name)} = request.{httpPathField.ServiceField.Name} != null && {RenderUriComponent(httpPathField.ServiceField, service)};"); using (code.Block($"if (!uriPart{CodeGenUtility.Capitalize(httpPathField.ServiceField.Name)}) {{", "}")) code.WriteLine($"return Promise.resolve(createRequiredRequestFieldError('{httpPathField.ServiceField.Name}'));"); } foreach (var httpPathField in httpMethodInfo.PathFields) { jsUri = jsUri.Replace("{" + httpPathField.Name + "}", $"${{uriPart{CodeGenUtility.Capitalize(httpPathField.ServiceField.Name)}}}"); } } bool hasQueryFields = httpMethodInfo.QueryFields.Count != 0; code.WriteLine((hasQueryFields ? "let" : "const") + $" uri = {jsUri};"); if (hasQueryFields) { code.WriteLine("const query" + IfTypeScript(": string[]") + " = [];"); foreach (var httpQueryField in httpMethodInfo.QueryFields) { code.WriteLine($"request.{httpQueryField.ServiceField.Name} == null || query.push('{httpQueryField.Name}=' + {RenderUriComponent(httpQueryField.ServiceField, service)});"); } using (code.Block("if (query.length) {", "}")) code.WriteLine("uri = uri + '?' + query.join('&');"); } using (code.Block("const fetchRequest" + IfTypeScript(": IFetchRequest") + " = {", "};")) { if (httpMethodInfo.RequestBodyField == null && httpMethodInfo.RequestNormalFields.Count == 0) { code.WriteLine($"method: '{httpMethodInfo.Method}'"); } else { code.WriteLine($"method: '{httpMethodInfo.Method}',"); code.WriteLine("headers: { 'Content-Type': 'application/json' },"); if (httpMethodInfo.RequestBodyField != null) { code.WriteLine($"body: JSON.stringify(request.{httpMethodInfo.RequestBodyField.ServiceField.Name})"); } else if (httpMethodInfo.ServiceMethod.RequestFields.Count == httpMethodInfo.RequestNormalFields.Count) { code.WriteLine("body: JSON.stringify(request)"); } else { using (code.Block("body: JSON.stringify({", "})")) { for (int httpFieldIndex = 0; httpFieldIndex < httpMethodInfo.RequestNormalFields.Count; httpFieldIndex++) { var httpFieldInfo = httpMethodInfo.RequestNormalFields[httpFieldIndex]; bool isLastField = httpFieldIndex == httpMethodInfo.RequestNormalFields.Count - 1; string fieldName = httpFieldInfo.ServiceField.Name; code.WriteLine(fieldName + ": request." + fieldName + (isLastField ? "" : ",")); } } } } } if (httpMethodInfo.RequestHeaderFields.Count != 0) { foreach (var httpHeaderField in httpMethodInfo.RequestHeaderFields) { using (code.Block($"if (request.{httpHeaderField.ServiceField.Name} != null) {{", "}")) code.WriteLine($"fetchRequest.headers['{httpHeaderField.Name}'] = request.{httpHeaderField.ServiceField.Name};"); } } code.WriteLine("return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest)"); using (code.Indent()) using (code.Block(".then(result => {", "});")) { code.WriteLine("const status = result.response.status;"); code.WriteLine("let value" + IfTypeScript($": I{capMethodName}Response") + " = null;"); using (code.Block($"if (result.json) {{", "}")) { var validResponses = httpMethodInfo.ValidResponses; string elsePrefix = ""; foreach (var validResponse in validResponses) { string statusCodeAsString = ((int)validResponse.StatusCode).ToString(CultureInfo.InvariantCulture); code.WriteLine($"{elsePrefix}if (status === {statusCodeAsString}) {{"); elsePrefix = "else "; using (code.Indent()) { var bodyField = validResponse.BodyField; if (bodyField != null) { string responseBodyFieldName = bodyField.ServiceField.Name; var bodyFieldType = service.GetFieldType(bodyField.ServiceField); if (bodyFieldType.Kind == ServiceTypeKind.Boolean) { code.WriteLine($"value = {{ {responseBodyFieldName}: true }};"); } else { code.WriteLine($"value = {{ {responseBodyFieldName}: result.json }};"); } } else { if (validResponse.NormalFields.Count == 0) { code.WriteLine("value = {};"); } else { code.WriteLine("value = result.json;"); } } } code.WriteLine("}"); } } using (code.Block("if (!value) {", "}")) code.WriteLine("return createResponseError(status, result.json)" + IfTypeScript($" as IServiceResult<I{capMethodName}Response>") + ";"); if (httpMethodInfo.ResponseHeaderFields.Count != 0) { code.WriteLine("let headerValue" + IfTypeScript(": string") + ";"); foreach (var httpHeaderField in httpMethodInfo.ResponseHeaderFields) { code.WriteLine($"headerValue = result.response.headers.get('{httpHeaderField.Name}');"); using (code.Block("if (headerValue != null) {", "}")) code.WriteLine($"value.{httpHeaderField.Name} = headerValue;"); } } code.WriteLine("return { value: value };"); } } } if (TypeScript) { code.WriteLine(); code.WriteLine("private _fetch: IFetch;"); code.WriteLine("private _baseUri: string;"); } } }))); }
public static string GetFieldPropertyName(ServiceFieldInfo fieldInfo) { return(TryGetJavaScriptName(fieldInfo) ?? CodeGenUtility.Capitalize(fieldInfo.Name)); }
public static string GetResponseDtoName(ServiceMethodInfo methodInfo) => CodeGenUtility.Capitalize(methodInfo.Name) + "ResponseDto";
private static string GetHttpMethodAttribute(HttpMethodInfo httpMethodInfo) { return("Http" + CodeGenUtility.Capitalize(httpMethodInfo.Method.ToLowerInvariant())); }
public static string GetEnumName(ServiceEnumInfo enumInfo) => CodeGenUtility.Capitalize(enumInfo.Name);
public void CapitalizeLowerCase() { CodeGenUtility.Capitalize("xyzzy").ShouldBe("Xyzzy"); }
public static string GetErrorName(ServiceErrorInfo errorInfo) => CodeGenUtility.Capitalize(errorInfo.Name);
public void Capitalize(string before, string after) { CodeGenUtility.Capitalize(before).Should().Be(after); }
/// <summary> /// Generates the C# output. /// </summary> protected override CodeGenOutput GenerateOutputCore(ServiceInfo service) { var httpServiceInfo = HttpServiceInfo.Create(service); string moduleName = ModuleName ?? service.Name; string capModuleName = CodeGenUtility.Capitalize(moduleName); string typesFileName = Uncapitalize(moduleName) + "Types" + (TypeScript ? ".ts" : ".js"); string clientFileName = Uncapitalize(moduleName) + (TypeScript ? ".ts" : ".js"); string serverFileName = Uncapitalize(moduleName) + "Server" + (TypeScript ? ".ts" : ".js"); var namedTexts = new List <CodeGenFile>(); var typeNames = new List <string>(); if (TypeScript) { namedTexts.Add(CreateFile(typesFileName, code => { code.WriteLine($"// DO NOT EDIT: generated by {GeneratorName}"); code.WriteLine(); code.WriteLine("import { IServiceResult, IServiceError } from 'facility-core';"); code.WriteLine(); WriteJsDoc(code, service); typeNames.Add($"I{capModuleName}"); using (code.Block($"export interface I{capModuleName} {{", "}")) { foreach (var httpMethodInfo in httpServiceInfo.Methods) { string methodName = httpMethodInfo.ServiceMethod.Name; string capMethodName = CodeGenUtility.Capitalize(methodName); code.WriteLineSkipOnce(); WriteJsDoc(code, httpMethodInfo.ServiceMethod); code.WriteLine($"{methodName}(request: I{capMethodName}Request): Promise<IServiceResult<I{capMethodName}Response>>;"); } } foreach (var methodInfo in service.Methods) { var requestDtoName = $"{CodeGenUtility.Capitalize(methodInfo.Name)}Request"; typeNames.Add($"I{requestDtoName}"); WriteDto(code, new ServiceDtoInfo( name: requestDtoName, fields: methodInfo.RequestFields, summary: $"Request for {CodeGenUtility.Capitalize(methodInfo.Name)}."), service); var responseDtoName = $"{CodeGenUtility.Capitalize(methodInfo.Name)}Response"; typeNames.Add($"I{responseDtoName}"); WriteDto(code, new ServiceDtoInfo( name: responseDtoName, fields: methodInfo.ResponseFields, summary: $"Response for {CodeGenUtility.Capitalize(methodInfo.Name)}."), service); } foreach (var dtoInfo in service.Dtos) { typeNames.Add($"I{dtoInfo.Name}"); WriteDto(code, dtoInfo, service); } code.WriteLine(); })); } namedTexts.Add(CreateFile(clientFileName, code => { code.WriteLine($"// DO NOT EDIT: generated by {GeneratorName}"); if (!TypeScript) { code.WriteLine("'use strict';"); } code.WriteLine(); code.WriteLine("import { HttpClientUtility" + IfTypeScript(", IServiceResult, IServiceError, IHttpClientOptions") + " } from 'facility-core';"); if (TypeScript) { code.WriteLine($"import {{ {string.Join(", ", typeNames)} }} from './{Uncapitalize(moduleName)}Types';"); code.WriteLine($"export * from './{Uncapitalize(moduleName)}Types';"); } code.WriteLine(); WriteJsDoc(code, $"Provides access to {capModuleName} over HTTP via fetch."); using (code.Block("export function createHttpClient({ fetch, baseUri }" + IfTypeScript(": IHttpClientOptions") + ")" + IfTypeScript($": I{capModuleName}") + " {", "}")) code.WriteLine($"return new {capModuleName}HttpClient(fetch, baseUri);"); code.WriteLine(); code.WriteLine("const { fetchResponse, createResponseError, createRequiredRequestFieldError } = HttpClientUtility;"); if (TypeScript) { code.WriteLine("type IFetch = HttpClientUtility.IFetch;"); code.WriteLine("type IFetchRequest = HttpClientUtility.IFetchRequest;"); } code.WriteLine(); using (code.Block($"class {capModuleName}HttpClient" + IfTypeScript($" implements I{capModuleName}") + " {", "}")) { using (code.Block("constructor(fetch" + IfTypeScript(": IFetch") + ", baseUri" + IfTypeScript("?: string") + ") {", "}")) { using (code.Block("if (typeof fetch !== 'function') {", "}")) code.WriteLine("throw new TypeError('fetch must be a function.');"); using (code.Block("if (typeof baseUri === 'undefined') {", "}")) code.WriteLine($"baseUri = '{httpServiceInfo.Url ?? ""}';"); using (code.Block(@"if (/[^\/]$/.test(baseUri)) {", "}")) code.WriteLine("baseUri += '/';"); code.WriteLine("this._fetch = fetch;"); code.WriteLine("this._baseUri = baseUri;"); } foreach (var httpMethodInfo in httpServiceInfo.Methods) { string methodName = httpMethodInfo.ServiceMethod.Name; string capMethodName = CodeGenUtility.Capitalize(methodName); code.WriteLine(); WriteJsDoc(code, httpMethodInfo.ServiceMethod); using (code.Block(IfTypeScript("public ") + $"{methodName}(request" + IfTypeScript($": I{capMethodName}Request") + ")" + IfTypeScript($": Promise<IServiceResult<I{capMethodName}Response>>") + " {", "}")) { bool hasPathFields = httpMethodInfo.PathFields.Count != 0; string jsUriDelim = hasPathFields ? "`" : "'"; string jsUri = jsUriDelim + httpMethodInfo.Path.Substring(1) + jsUriDelim; if (hasPathFields) { foreach (var httpPathField in httpMethodInfo.PathFields) { code.WriteLine($"const uriPart{CodeGenUtility.Capitalize(httpPathField.ServiceField.Name)} = request.{httpPathField.ServiceField.Name} != null && {RenderUriComponent(httpPathField.ServiceField, service)};"); using (code.Block($"if (!uriPart{CodeGenUtility.Capitalize(httpPathField.ServiceField.Name)}) {{", "}")) code.WriteLine($"return Promise.resolve(createRequiredRequestFieldError('{httpPathField.ServiceField.Name}'));"); } foreach (var httpPathField in httpMethodInfo.PathFields) { jsUri = jsUri.Replace("{" + httpPathField.Name + "}", $"${{uriPart{CodeGenUtility.Capitalize(httpPathField.ServiceField.Name)}}}"); } } bool hasQueryFields = httpMethodInfo.QueryFields.Count != 0; code.WriteLine((hasQueryFields ? "let" : "const") + $" uri = {jsUri};"); if (hasQueryFields) { code.WriteLine("const query" + IfTypeScript(": string[]") + " = [];"); foreach (var httpQueryField in httpMethodInfo.QueryFields) { code.WriteLine($"request.{httpQueryField.ServiceField.Name} == null || query.push('{httpQueryField.Name}=' + {RenderUriComponent(httpQueryField.ServiceField, service)});"); } using (code.Block("if (query.length) {", "}")) code.WriteLine("uri = uri + '?' + query.join('&');"); } using (code.Block("const fetchRequest" + IfTypeScript(": IFetchRequest") + " = {", "};")) { if (httpMethodInfo.RequestBodyField == null && httpMethodInfo.RequestNormalFields.Count == 0) { code.WriteLine($"method: '{httpMethodInfo.Method}',"); if (httpMethodInfo.RequestHeaderFields.Count != 0) { code.WriteLine("headers: {},"); } } else { code.WriteLine($"method: '{httpMethodInfo.Method}',"); code.WriteLine("headers: { 'Content-Type': 'application/json' },"); if (httpMethodInfo.RequestBodyField != null) { code.WriteLine($"body: JSON.stringify(request.{httpMethodInfo.RequestBodyField.ServiceField.Name})"); } else if (httpMethodInfo.ServiceMethod.RequestFields.Count == httpMethodInfo.RequestNormalFields.Count) { code.WriteLine("body: JSON.stringify(request)"); } else { using (code.Block("body: JSON.stringify({", "})")) { for (int httpFieldIndex = 0; httpFieldIndex < httpMethodInfo.RequestNormalFields.Count; httpFieldIndex++) { var httpFieldInfo = httpMethodInfo.RequestNormalFields[httpFieldIndex]; bool isLastField = httpFieldIndex == httpMethodInfo.RequestNormalFields.Count - 1; string fieldName = httpFieldInfo.ServiceField.Name; code.WriteLine(fieldName + ": request." + fieldName + (isLastField ? "" : ",")); } } } } } if (httpMethodInfo.RequestHeaderFields.Count != 0) { foreach (var httpHeaderField in httpMethodInfo.RequestHeaderFields) { using (code.Block($"if (request.{httpHeaderField.ServiceField.Name} != null) {{", "}")) code.WriteLine($"fetchRequest.headers['{httpHeaderField.Name}'] = request.{httpHeaderField.ServiceField.Name};"); } } code.WriteLine("return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest)"); using (code.Indent()) using (code.Block(".then(result => {", "});")) { code.WriteLine("const status = result.response.status;"); code.WriteLine("let value" + IfTypeScript($": I{capMethodName}Response | null") + " = null;"); using (code.Block("if (result.json) {", "}")) { var validResponses = httpMethodInfo.ValidResponses; string elsePrefix = ""; foreach (var validResponse in validResponses) { string statusCodeAsString = ((int)validResponse.StatusCode).ToString(CultureInfo.InvariantCulture); code.WriteLine($"{elsePrefix}if (status === {statusCodeAsString}) {{"); elsePrefix = "else "; using (code.Indent()) { var bodyField = validResponse.BodyField; if (bodyField != null) { string responseBodyFieldName = bodyField.ServiceField.Name; var bodyFieldType = service.GetFieldType(bodyField.ServiceField); if (bodyFieldType.Kind == ServiceTypeKind.Boolean) { code.WriteLine($"value = {{ {responseBodyFieldName}: true }};"); } else { code.WriteLine($"value = {{ {responseBodyFieldName}: result.json }};"); } } else { if (validResponse.NormalFields.Count == 0) { code.WriteLine("value = {};"); } else { code.WriteLine("value = result.json;"); } } } code.WriteLine("}"); } } using (code.Block("if (!value) {", "}")) code.WriteLine("return createResponseError(status, result.json)" + IfTypeScript($" as IServiceResult<I{capMethodName}Response>") + ";"); if (httpMethodInfo.ResponseHeaderFields.Count != 0) { code.WriteLine("let headerValue" + IfTypeScript(": string | null | undefined") + ";"); foreach (var httpHeaderField in httpMethodInfo.ResponseHeaderFields) { code.WriteLine($"headerValue = result.response.headers.get('{httpHeaderField.Name}');"); using (code.Block("if (headerValue != null) {", "}")) code.WriteLine($"value.{httpHeaderField.Name} = headerValue;"); } } code.WriteLine("return { value: value };"); } } } if (TypeScript) { code.WriteLine(); code.WriteLine("private _fetch: IFetch;"); code.WriteLine("private _baseUri: string;"); } } })); if (Express) { namedTexts.Add(CreateFile(serverFileName, code => { code.WriteLine($"// DO NOT EDIT: generated by {GeneratorName}"); if (!TypeScript) { code.WriteLine("'use strict';"); } code.WriteLine(); code.WriteLine("import * as bodyParser from 'body-parser';"); code.WriteLine("import * as express from 'express';"); code.WriteLine("import {" + IfTypeScript(" IServiceResult, IServiceError") + " } from 'facility-core';"); if (TypeScript) { code.WriteLine($"import {{ {string.Join(", ", typeNames)} }} from './{Uncapitalize(moduleName)}Types';"); code.WriteLine($"export * from './{Uncapitalize(moduleName)}Types';"); } // TODO: export this from facility-core code.WriteLine(); using (code.Block("const standardErrorCodes" + IfTypeScript(": { [code: string]: number }") + " = {", "};")) { code.WriteLine("'notModified': 304,"); code.WriteLine("'invalidRequest': 400,"); code.WriteLine("'notAuthenticated': 401,"); code.WriteLine("'notAuthorized': 403,"); code.WriteLine("'notFound': 404,"); code.WriteLine("'conflict': 409,"); code.WriteLine("'requestTooLarge': 413,"); code.WriteLine("'tooManyRequests': 429,"); code.WriteLine("'internalError': 500,"); code.WriteLine("'serviceUnavailable': 503,"); } code.WriteLine(); using (code.Block("function parseBoolean(value" + IfTypeScript(": string | undefined") + ") {", "}")) { using (code.Block("if (typeof value === 'string') {", "}")) { code.WriteLine("const lowerValue = value.toLowerCase();"); using (code.Block("if (lowerValue === 'true') {", "}")) code.WriteLine("return true;"); using (code.Block("if (lowerValue === 'false') {", "}")) code.WriteLine("return false;"); } code.WriteLine("return undefined;"); } code.WriteLine(); using (code.Block("export function createApp(service" + IfTypeScript($": I{capModuleName}") + ", middleware" + IfTypeScript(": ((...args: any[]) => void)[]") + ")" + IfTypeScript(": express.Application") + " {", "}")) { code.WriteLine("const app = express();"); code.WriteLine("app.use(bodyParser.json());"); code.WriteLine("app.use(bodyParser.urlencoded({ extended: true }));"); code.WriteLine("middleware.forEach(func => app.use(func));"); foreach (var httpMethodInfo in httpServiceInfo.Methods) { string methodName = httpMethodInfo.ServiceMethod.Name; string capMethodName = CodeGenUtility.Capitalize(methodName); string expressMethod = httpMethodInfo.Method.ToLowerInvariant(); string expressPath = httpMethodInfo.Path; foreach (var httpPathField in httpMethodInfo.PathFields) { expressPath = expressPath.Replace("{" + httpPathField.Name + "}", $":{httpPathField.Name}"); } code.WriteLine(); WriteJsDoc(code, httpMethodInfo.ServiceMethod); using (code.Block($"app.{expressMethod}('{expressPath}', function (req, res, next) {{", "});")) { code.WriteLine("const request" + IfTypeScript($": I{capMethodName}Request") + " = {};"); foreach (var httpPathField in httpMethodInfo.PathFields) { code.WriteLine($"request.{httpPathField.ServiceField.Name} = {RenderJsConversion(httpPathField.ServiceField, service, $"req.params.{httpPathField.Name}")};"); } foreach (var httpQueryField in httpMethodInfo.QueryFields) { using (code.Block($"if (req.query['{httpQueryField.Name}'] != null) {{", "}")) code.WriteLine($"request.{httpQueryField.ServiceField.Name} = {RenderJsConversion(httpQueryField.ServiceField, service, $"req.query['{httpQueryField.Name}']")};"); } if (httpMethodInfo.RequestBodyField != null) { code.WriteLine($"request.{httpMethodInfo.RequestBodyField.ServiceField.Name} = req.body;"); } else if (httpMethodInfo.RequestNormalFields != null) { foreach (var field in httpMethodInfo.RequestNormalFields) { code.WriteLine($"request.{field.ServiceField.Name} = req.body.{field.ServiceField.Name};"); } } if (httpMethodInfo.RequestHeaderFields != null) { foreach (var field in httpMethodInfo.RequestHeaderFields) { code.WriteLine($"request.{field.ServiceField.Name} = req.header('{field.Name}');"); } } code.WriteLine(); code.WriteLine($"return service.{methodName}(request)"); using (code.Indent()) { using (code.Block(".then(result => {", "})")) { using (code.Block("if (result.error) {", "}")) { code.WriteLine("const status = result.error.code && standardErrorCodes[result.error.code] || 500;"); code.WriteLine("res.status(status).send(result.error);"); code.WriteLine("return;"); } using (code.Block("if (result.value) {", "}")) { if (httpMethodInfo.ResponseHeaderFields != null) { foreach (var field in httpMethodInfo.ResponseHeaderFields) { using (code.Block($"if (result.value.{field.ServiceField.Name} != null) {{", "}")) code.WriteLine($"res.setHeader('{field.Name}', result.value.{field.ServiceField.Name});"); } } foreach (var validResponse in httpMethodInfo.ValidResponses.Where(x => x.NormalFields == null || x.NormalFields.Count == 0)) { var bodyField = validResponse.BodyField; if (bodyField != null) { string responseBodyFieldName = bodyField.ServiceField.Name; using (code.Block($"if (result.value.{responseBodyFieldName}) {{", "}")) { var bodyFieldType = service.GetFieldType(bodyField.ServiceField); if (bodyFieldType.Kind == ServiceTypeKind.Boolean) { code.WriteLine($"res.sendStatus({(int) validResponse.StatusCode});"); code.WriteLine("return;"); } else { code.WriteLine($"res.status({(int) validResponse.StatusCode}).send(result.value.{responseBodyFieldName});"); code.WriteLine("return;"); } } } else { if (validResponse.NormalFields.Count == 0) { code.WriteLine($"res.sendStatus({(int) validResponse.StatusCode});"); code.WriteLine("return;"); } } } foreach (var validResponse in httpMethodInfo.ValidResponses.Where(x => x.NormalFields != null && x.NormalFields.Count != 0)) { code.WriteLine($"res.status({(int) validResponse.StatusCode}).send({{"); using (code.Indent()) { foreach (var field in validResponse.NormalFields) { code.WriteLine($"{field.ServiceField.Name}: result.value.{field.ServiceField.Name},"); } } code.WriteLine("});"); code.WriteLine("return;"); } } code.WriteLine("throw new Error('Result must have an error or value.');"); } code.WriteLine(".catch(next);"); } } } code.WriteLine(); code.WriteLine("return app;"); } })); } return(new CodeGenOutput(namedTexts, new List <CodeGenPattern>())); }
/// <summary> /// Generates the JavaScript/TypeScript output. /// </summary> public override CodeGenOutput GenerateOutput(ServiceInfo service) { var httpServiceInfo = HttpServiceInfo.Create(service); var moduleName = ModuleName ?? service.Name; var capModuleName = CodeGenUtility.Capitalize(moduleName); var typesFileName = Uncapitalize(moduleName) + "Types" + (TypeScript ? ".ts" : ".js"); var clientFileName = Uncapitalize(moduleName) + (TypeScript ? ".ts" : ".js"); var serverFileName = Uncapitalize(moduleName) + "Server" + (TypeScript ? ".ts" : ".js"); var namedTexts = new List <CodeGenFile>(); var typeNames = new List <string>(); if (TypeScript) { namedTexts.Add(CreateFile(typesFileName, code => { WriteFileHeader(code); var allFields = service.Dtos.SelectMany(x => x.Fields).Concat(service.Methods.SelectMany(x => x.RequestFields.Concat(x.ResponseFields))).ToList(); code.WriteLine(); var facilityImports = new List <string>(); if (httpServiceInfo.Methods.Any() || allFields.Any(x => FieldUsesKind(service, x, ServiceTypeKind.Result))) { facilityImports.Add("IServiceResult"); } if (allFields.Any(x => FieldUsesKind(service, x, ServiceTypeKind.Error))) { facilityImports.Add("IServiceError"); } WriteImports(code, facilityImports, "facility-core"); code.WriteLine(); WriteJsDoc(code, service); typeNames.Add($"I{capModuleName}"); using (code.Block($"export interface I{capModuleName} {{", "}")) { foreach (var httpMethodInfo in httpServiceInfo.Methods) { var methodName = httpMethodInfo.ServiceMethod.Name; var capMethodName = CodeGenUtility.Capitalize(methodName); code.WriteLineSkipOnce(); WriteJsDoc(code, httpMethodInfo.ServiceMethod); code.WriteLine($"{methodName}(request: I{capMethodName}Request): Promise<IServiceResult<I{capMethodName}Response>>;"); } } foreach (var methodInfo in service.Methods) { var requestDtoName = $"{CodeGenUtility.Capitalize(methodInfo.Name)}Request"; typeNames.Add($"I{requestDtoName}"); WriteDto(code, new ServiceDtoInfo( name: requestDtoName, fields: methodInfo.RequestFields, summary: $"Request for {CodeGenUtility.Capitalize(methodInfo.Name)}."), service); var responseDtoName = $"{CodeGenUtility.Capitalize(methodInfo.Name)}Response"; typeNames.Add($"I{responseDtoName}"); WriteDto(code, new ServiceDtoInfo( name: responseDtoName, fields: methodInfo.ResponseFields, summary: $"Response for {CodeGenUtility.Capitalize(methodInfo.Name)}."), service); } foreach (var dtoInfo in service.Dtos) { typeNames.Add($"I{dtoInfo.Name}"); WriteDto(code, dtoInfo, service); } code.WriteLine(); })); } namedTexts.Add(CreateFile(clientFileName, code => { WriteFileHeader(code); if (!TypeScript) { code.WriteLine("'use strict';"); } code.WriteLine(); var facilityImports = new List <string> { "HttpClientUtility" }; if (TypeScript) { if (httpServiceInfo.Methods.Any()) { facilityImports.Add("IServiceResult"); } facilityImports.Add("IHttpClientOptions"); } WriteImports(code, facilityImports, "facility-core"); if (TypeScript) { WriteImports(code, typeNames, $"./{Uncapitalize(moduleName)}Types"); code.WriteLine($"export * from './{Uncapitalize(moduleName)}Types';"); } code.WriteLine(); WriteJsDoc(code, $"Provides access to {capModuleName} over HTTP via fetch."); using (code.Block("export function createHttpClient({ fetch, baseUri }" + IfTypeScript(": IHttpClientOptions") + ")" + IfTypeScript($": I{capModuleName}") + " {", "}")) code.WriteLine($"return new {capModuleName}HttpClient(fetch, baseUri);"); code.WriteLine(); code.WriteLine("const { fetchResponse, createResponseError, createRequiredRequestFieldError } = HttpClientUtility;"); if (TypeScript) { code.WriteLine("type IFetch = HttpClientUtility.IFetch;"); code.WriteLine("type IFetchRequest = HttpClientUtility.IFetchRequest;"); } code.WriteLine(); using (code.Block($"class {capModuleName}HttpClient" + IfTypeScript($" implements I{capModuleName}") + " {", "}")) { using (code.Block("constructor(fetch" + IfTypeScript(": IFetch") + ", baseUri" + IfTypeScript("?: string") + ") {", "}")) { using (code.Block("if (typeof fetch !== 'function') {", "}")) code.WriteLine("throw new TypeError('fetch must be a function.');"); using (code.Block("if (typeof baseUri === 'undefined') {", "}")) code.WriteLine($"baseUri = '{httpServiceInfo.Url ?? ""}';"); using (code.Block(@"if (/[^\/]$/.test(baseUri)) {", "}")) code.WriteLine("baseUri += '/';"); code.WriteLine("this._fetch = fetch;"); code.WriteLine("this._baseUri = baseUri;"); } foreach (var httpMethodInfo in httpServiceInfo.Methods) { var methodName = httpMethodInfo.ServiceMethod.Name; var capMethodName = CodeGenUtility.Capitalize(methodName); code.WriteLine(); WriteJsDoc(code, httpMethodInfo.ServiceMethod); using (code.Block(IfTypeScript("public ") + $"{methodName}(request" + IfTypeScript($": I{capMethodName}Request") + ")" + IfTypeScript($": Promise<IServiceResult<I{capMethodName}Response>>") + " {", "}")) { var hasPathFields = httpMethodInfo.PathFields.Count != 0; var jsUriDelim = hasPathFields ? "`" : "'"; var jsUri = jsUriDelim + httpMethodInfo.Path.Substring(1) + jsUriDelim; if (hasPathFields) { foreach (var httpPathField in httpMethodInfo.PathFields) { code.WriteLine($"const uriPart{CodeGenUtility.Capitalize(httpPathField.ServiceField.Name)} = request.{httpPathField.ServiceField.Name} != null && {RenderUriComponent(httpPathField.ServiceField, service)};"); using (code.Block($"if (!uriPart{CodeGenUtility.Capitalize(httpPathField.ServiceField.Name)}) {{", "}")) code.WriteLine($"return Promise.resolve(createRequiredRequestFieldError('{httpPathField.ServiceField.Name}'));"); } foreach (var httpPathField in httpMethodInfo.PathFields) { jsUri = jsUri.Replace("{" + httpPathField.Name + "}", $"${{uriPart{CodeGenUtility.Capitalize(httpPathField.ServiceField.Name)}}}"); } } var hasQueryFields = httpMethodInfo.QueryFields.Count != 0; code.WriteLine((hasQueryFields ? "let" : "const") + $" uri = {jsUri};"); if (hasQueryFields) { code.WriteLine("const query" + IfTypeScript(": string[]") + " = [];"); foreach (var httpQueryField in httpMethodInfo.QueryFields) { code.WriteLine($"request.{httpQueryField.ServiceField.Name} == null || query.push('{httpQueryField.Name}=' + {RenderUriComponent(httpQueryField.ServiceField, service)});"); } using (code.Block("if (query.length) {", "}")) code.WriteLine("uri = uri + '?' + query.join('&');"); } using (code.Block("const fetchRequest" + IfTypeScript(": IFetchRequest") + " = {", "};")) { if (httpMethodInfo.RequestBodyField == null && httpMethodInfo.RequestNormalFields.Count == 0) { code.WriteLine($"method: '{httpMethodInfo.Method}',"); if (httpMethodInfo.RequestHeaderFields.Count != 0) { code.WriteLine("headers: {},"); } } else { code.WriteLine($"method: '{httpMethodInfo.Method}',"); code.WriteLine("headers: { 'Content-Type': 'application/json' },"); if (httpMethodInfo.RequestBodyField != null) { code.WriteLine($"body: JSON.stringify(request.{httpMethodInfo.RequestBodyField.ServiceField.Name})"); } else if (httpMethodInfo.ServiceMethod.RequestFields.Count == httpMethodInfo.RequestNormalFields.Count) { code.WriteLine("body: JSON.stringify(request)"); } else { using (code.Block("body: JSON.stringify({", "})")) { for (var httpFieldIndex = 0; httpFieldIndex < httpMethodInfo.RequestNormalFields.Count; httpFieldIndex++) { var httpFieldInfo = httpMethodInfo.RequestNormalFields[httpFieldIndex]; var isLastField = httpFieldIndex == httpMethodInfo.RequestNormalFields.Count - 1; var fieldName = httpFieldInfo.ServiceField.Name; code.WriteLine(fieldName + ": request." + fieldName + (isLastField ? "" : ",")); } } } } } if (httpMethodInfo.RequestHeaderFields.Count != 0) { foreach (var httpHeaderField in httpMethodInfo.RequestHeaderFields) { using (code.Block($"if (request.{httpHeaderField.ServiceField.Name} != null) {{", "}")) code.WriteLine($"fetchRequest.headers['{httpHeaderField.Name}'] = request.{httpHeaderField.ServiceField.Name};"); } } code.WriteLine("return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest)"); using (code.Indent()) using (code.Block(".then(result => {", "});")) { code.WriteLine("const status = result.response.status;"); code.WriteLine("let value" + IfTypeScript($": I{capMethodName}Response | null") + " = null;"); using (code.Block("if (result.json) {", "}")) { var validResponses = httpMethodInfo.ValidResponses; var elsePrefix = ""; foreach (var validResponse in validResponses) { var statusCodeAsString = ((int)validResponse.StatusCode).ToString(CultureInfo.InvariantCulture); code.WriteLine($"{elsePrefix}if (status === {statusCodeAsString}) {{"); elsePrefix = "else "; using (code.Indent()) { var bodyField = validResponse.BodyField; if (bodyField != null) { var responseBodyFieldName = bodyField.ServiceField.Name; var bodyFieldType = service.GetFieldType(bodyField.ServiceField) !; if (bodyFieldType.Kind == ServiceTypeKind.Boolean) { code.WriteLine($"value = {{ {responseBodyFieldName}: true }};"); } else { code.WriteLine($"value = {{ {responseBodyFieldName}: result.json }};"); } } else { if (validResponse.NormalFields !.Count == 0) { code.WriteLine("value = {};"); }