public void Uncapitalize(string before, string after) { CodeGenUtility.Uncapitalize(before).Should().Be(after); }
/// <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 = CodeGenUtility.Uncapitalize(moduleName) + "Types" + (TypeScript ? ".ts" : ".js"); var clientFileName = CodeGenUtility.Uncapitalize(moduleName) + (TypeScript ? ".ts" : ".js"); var serverFileName = CodeGenUtility.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, context?: unknown): 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); } foreach (var enumInfo in service.Enums) { typeNames.Add(enumInfo.Name); code.WriteLine(); WriteJsDoc(code, enumInfo); using (code.Block($"export enum {enumInfo.Name} {{", "}")) { foreach (var value in enumInfo.Values) { code.WriteLineSkipOnce(); WriteJsDoc(code, value); code.WriteLine($"{value.Name} = '{value.Name}',"); } } } 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, $"./{CodeGenUtility.Uncapitalize(moduleName)}Types"); code.WriteLine($"export * from './{CodeGenUtility.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") + ", context" + IfTypeScript("?: unknown") + ")" + 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" + IfTypeScript("!") + $"['{httpHeaderField.Name}'] = request.{httpHeaderField.ServiceField.Name};"); } } code.WriteLine("return fetchResponse(this._fetch, this._baseUri + uri, fetchRequest, context)"); using (code.Indent()) using (code.Block(".then(result => {", "});")) { code.WriteLine("const status = result.response.status;"); var responseValueType = $"I{capMethodName}Response"; code.WriteLine("let value" + IfTypeScript($": {responseValueType} | 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 }}" + IfTypeScript($" as {responseValueType}") + ";"); } } else { if (validResponse.NormalFields !.Count == 0) { code.WriteLine("value = {};"); }