private async Task ParseAsync(Uri documentUri, string text)
        {
            var diagnostics = new List <Diagnostic>();

            ServiceInfo service;
            IReadOnlyList <ServiceDefinitionError> errors;

            if (!m_parser.TryParseDefinition(new ServiceDefinitionText(documentUri.AbsoluteUri, text), out service, out errors))
            {
                diagnostics.AddRange(errors.Select(ToDiagnostic));
            }

            if (service != null && !HttpServiceInfo.TryCreate(service, out _, out errors))
            {
                diagnostics.AddRange(errors.Select(ToDiagnostic));
            }

            SetService(documentUri, service);

            Router.PublishDiagnostics(new PublishDiagnosticsParams
            {
                Uri         = documentUri,
                Diagnostics = diagnostics
            });
        }
示例#2
0
 protected static ServiceDefinitionError ParseInvalidHttpApi(string text)
 {
     HttpServiceInfo.TryCreate(TestUtility.ParseTestApi(text), out _, out var errors);
     if (errors.Count == 0)
     {
         throw new InvalidOperationException("Parse did not fail.");
     }
     return(errors[0]);
 }
示例#3
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);");
                        }
                    }
                }
            })));
        }
    /// <summary>
    /// Generates the Markdown.
    /// </summary>
    public override CodeGenOutput GenerateOutput(ServiceInfo service)
    {
        var httpServiceInfo = NoHttp ? null : HttpServiceInfo.Create(service);

        var template = CodeGenTemplate.Parse(TemplateText ?? GetEmbeddedResourceText("Facility.CodeGen.Markdown.template.scriban-txt"));
        var globals  = CodeGenGlobals.Create(new MarkdownGeneratorGlobals(this, service, httpServiceInfo));
        var files    = template.Generate(globals, new CodeGenSettings {
            NewLine = NewLine, IndentText = IndentText
        });

        return(new CodeGenOutput(
                   files: files.Select(x => new CodeGenFile(x.Name, x.Text)).ToList(),
                   patternsToClean: new[] { new CodeGenPattern($"{service.Name}/*.md", CodeGenUtility.GetCodeGenComment(GeneratorName ?? "")) }));
    }
示例#5
0
        public void ByRouteComparer(string leftHttp, string rightHttp, int expected)
        {
            string fsdText = "service TestApi { [left] method left { id: string; }: {} [right] method right { id: string; }: {} }".Replace("[left]", leftHttp).Replace("[right]", rightHttp);
            var    service = new HttpServiceInfo(new FsdParser().ParseDefinition(new NamedText("", fsdText)));
            var    left    = service.Methods.Single(x => x.ServiceMethod.Name == "left");
            var    right   = service.Methods.Single(x => x.ServiceMethod.Name == "right");
            int    actual  = HttpMethodInfo.ByRouteComparer.Compare(left, right);

            if (expected < 0)
            {
                actual.ShouldBeLessThan(0);
            }
            else
            {
                actual.ShouldBeGreaterThan(0);
            }
        }
示例#6
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>()));
        }
示例#7
0
 public void MultipleErrors()
 {
     HttpServiceInfo.TryCreate(TestUtility.ParseTestApi("[http(xyzzy: true)] service TestApi { [http] data Hey {} }"), out _, out var errors);
     errors.Count.Should().Be(2);
 }
示例#8
0
 protected static HttpServiceInfo ParseHttpApi(string text)
 {
     return(HttpServiceInfo.Create(TestUtility.ParseTestApi(text)));
 }
示例#9
0
        /// <summary>
        /// Generates the Python.
        /// </summary>
        public override CodeGenOutput GenerateOutput(ServiceInfo serviceInfo)
        {
            var outputFiles = new List <CodeGenFile>();

            var httpServiceInfo = HttpServiceInfo.Create(serviceInfo);

            var templateText = GetEmbeddedResourceText("Facility.CodeGen.Python.template.scriban-txt");
            var outputText   = CodeTemplateUtility.Render(templateText, new CodeTemplateGlobals(this, serviceInfo, httpServiceInfo));

            using var stringReader = new StringReader(outputText);

            var fileStart = "";

            string?line;

            while ((line = stringReader.ReadLine()) != null)
            {
                var match = Regex.Match(line, @"^==+>");
                if (match.Success)
                {
                    fileStart = match.Value;
                    break;
                }
            }

            while (line != null)
            {
                var fileName = line.Substring(fileStart.Length);

                var fileLines = new List <string>();
                while ((line = stringReader.ReadLine()) != null && !line.StartsWith(fileStart, StringComparison.Ordinal))
                {
                    fileLines.Add(line);
                }

                // skip exactly one blank line to allow file start to stand out
                if (fileLines.Count != 0 && string.IsNullOrWhiteSpace(fileLines[0]))
                {
                    fileLines.RemoveAt(0);
                }

                // remove all blank lines at the end
                while (fileLines.Count != 0 && string.IsNullOrWhiteSpace(fileLines[fileLines.Count - 1]))
                {
                    fileLines.RemoveAt(fileLines.Count - 1);
                }

                outputFiles.Add(CreateFile(fileName.Trim(), code =>
                {
                    foreach (var fileLine in fileLines)
                    {
                        code.WriteLine(fileLine);
                    }
                }));
            }

            var codeGenComment  = CodeGenUtility.GetCodeGenComment(GeneratorName ?? "");
            var patternsToClean = new[]
            {
                new CodeGenPattern("*.py", codeGenComment),
            };

            return(new CodeGenOutput(outputFiles, patternsToClean));
        }
示例#10
0
        /// <summary>
        /// Generates Swagger (OpenAPI 2.0) for a service definition.
        /// </summary>
        public SwaggerService GenerateSwaggerService(ServiceInfo service)
        {
            var httpServiceInfo = HttpServiceInfo.Create(service);

            var swaggerService = new SwaggerService
            {
                Swagger = SwaggerUtility.SwaggerVersion,
                Info    = new SwaggerInfo
                {
                    Identifier  = service.Name,
                    Title       = GetSummaryOrNull(service) ?? service.Name,
                    Description = GetRemarksOrNull(service),
                    Version     = service.TryGetAttribute("info")?.TryGetParameterValue("version") ?? "0.0.0",
                    CodeGen     = CodeGenUtility.GetCodeGenComment(GeneratorName ?? ""),
                },
            };

            var defaultBaseUri = httpServiceInfo.Url;

            if (defaultBaseUri != null)
            {
                var baseUri = new Uri(defaultBaseUri);
                swaggerService.Host    = baseUri.Host;
                swaggerService.Schemes = new[] { baseUri.Scheme };

                string basePath = baseUri.PathAndQuery;
                if (!string.IsNullOrEmpty(basePath) && basePath != "/")
                {
                    swaggerService.BasePath = baseUri.PathAndQuery;
                }
            }

            var paths = new OurDictionary <string, SwaggerOperations>();

            foreach (var httpMethodInfo in httpServiceInfo.Methods)
            {
                AddMethodToPaths(paths, service, httpMethodInfo);
            }
            swaggerService.Paths = paths;

            var dtoInfos = new OurDictionary <string, ServiceDtoInfo>();

            foreach (var httpMethodInfo in httpServiceInfo.Methods)
            {
                if (httpMethodInfo.RequestBodyField != null)
                {
                    AddDtos(dtoInfos, GetDtosForType(service.GetFieldType(httpMethodInfo.RequestBodyField.ServiceField) !));
                }

                AddDto(dtoInfos, TryCreateMethodRequestBodyType(httpMethodInfo)?.Dto);

                foreach (var httpResponseInfo in httpMethodInfo.ValidResponses)
                {
                    if (httpResponseInfo.BodyField != null)
                    {
                        AddDtos(dtoInfos, GetDtosForType(service.GetFieldType(httpResponseInfo.BodyField.ServiceField) !));
                    }

                    AddDto(dtoInfos, TryCreateMethodResponseBodyType(httpMethodInfo, httpResponseInfo)?.Dto);
                }
            }

            while (true)
            {
                var dtoCount = dtoInfos.Count;
                foreach (var field in dtoInfos.Values.SelectMany(x => x.Fields).ToList())
                {
                    AddDtos(dtoInfos, GetDtosForType(service.GetFieldType(field) !));
                }
                if (dtoCount == dtoInfos.Count)
                {
                    break;
                }
            }

            var definitions = new OurDictionary <string, SwaggerSchema>();

            foreach (var dtoInfo in dtoInfos.Values)
            {
                definitions[dtoInfo.Name] = GetDtoSchema(service, dtoInfo);
            }
            swaggerService.Definitions = definitions.Count == 0 ? null : definitions;

            return(swaggerService);
        }
示例#11
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;");
                    }
                }
            })));
        }
        /// <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 = {};");
                                                    }