Beispiel #1
0
 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}\")");
     }
 }
Beispiel #2
0
        /// <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);");
                        }
                    }
                }
            })));
        }
Beispiel #3
0
 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))};");
         }
     }
 }
Beispiel #4
0
        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);
            }
        }
Beispiel #5
0
 public static string GetDtoName(ServiceDtoInfo dtoInfo) => CodeGenUtility.Capitalize(dtoInfo.Name) + "Dto";
Beispiel #6
0
 public static string GetMethodName(ServiceMethodInfo methodInfo) => CodeGenUtility.Capitalize(methodInfo.Name);
Beispiel #7
0
 public static string GetInterfaceName(ServiceInfo serviceInfo) => $"I{CodeGenUtility.Capitalize(serviceInfo.Name)}";
 public void CapitalizeNumber()
 {
     CodeGenUtility.Capitalize("1234").ShouldBe("1234");
 }
Beispiel #9
0
 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));
 }
Beispiel #11
0
 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);
Beispiel #13
0
 public static string GetErrorSetName(ServiceErrorSetInfo errorSetInfo)
 {
     return(CodeGenUtility.Capitalize(errorSetInfo.Name));
 }
Beispiel #14
0
        /// <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));
 }
Beispiel #16
0
 public static string GetResponseDtoName(ServiceMethodInfo methodInfo) => CodeGenUtility.Capitalize(methodInfo.Name) + "ResponseDto";
Beispiel #17
0
 private static string GetHttpMethodAttribute(HttpMethodInfo httpMethodInfo)
 {
     return("Http" + CodeGenUtility.Capitalize(httpMethodInfo.Method.ToLowerInvariant()));
 }
Beispiel #18
0
 public static string GetEnumName(ServiceEnumInfo enumInfo) => CodeGenUtility.Capitalize(enumInfo.Name);
Beispiel #19
0
 public void CapitalizeLowerCase()
 {
     CodeGenUtility.Capitalize("xyzzy").ShouldBe("Xyzzy");
 }
Beispiel #20
0
 public static string GetErrorName(ServiceErrorInfo errorInfo) => CodeGenUtility.Capitalize(errorInfo.Name);
 public void Capitalize(string before, string after)
 {
     CodeGenUtility.Capitalize(before).Should().Be(after);
 }
Beispiel #22
0
        /// <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 = {};");
                                                    }