UNPKG

@autorest/go

Version:
873 lines 83.2 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as go from '../../../codemodel.go/src/index.js'; import * as naming from '../../../naming.go/src/naming.js'; import * as helpers from '../core/helpers.js'; import { ImportManager } from '../core/imports.js'; import { fixUpMethodName } from '../core/operations.js'; import { generateServerInternal, RequiredHelpers } from './internal.js'; import { CodegenError } from '../core/errors.js'; // contains the generated content for all servers and the required helpers export class ServerContent { servers; internals; constructor(servers, internals) { this.servers = servers; this.internals = internals; } } // represents the generated content for an operation group export class OperationGroupContent { name; content; constructor(name, content) { this.name = name; this.content = content; } } // used to track the helpers we need to emit. they're all false by default. const requiredHelpers = new RequiredHelpers(); export function getServerName(client) { // for the fake server, we use the suffix Server instead of Client return naming.capitalize(client.name.replace(/[C|c]lient$/, 'Server')); } /** * Generates the contents for the *_server.go files. * * @param pkg contains the package content * @param target the codegen target for the module * @returns the contents to generate or an empty object */ export function generateServers(pkg, target) { const operations = new Array(); for (const client of pkg.parent.clients) { if (client.clientAccessors.length === 0 && helpers.clientHasNoExportedMethods(client)) { // client has no client accessors and no exported methods, skip it continue; } // the list of packages to import const imports = new ImportManager(pkg); // add standard imports imports.add('errors'); imports.add('fmt'); imports.add('net/http'); imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime'); const serverName = getServerName(client); let content; content = `// ${serverName} is a fake server for instances of the ${go.getTypeDeclaration(client, pkg)} type.\n`; content += `type ${serverName} struct{\n`; const indent = new helpers.Indentation(); // we might remove some operations from the list const finalMethods = new Array(); let countLROs = 0; let countPagers = 0; // add server transports for client accessors // we might remove some clients from the list const finalSubClients = new Array(); for (const clientAccessor of client.clientAccessors) { if (helpers.clientHasNoExportedMethods(clientAccessor.returns)) { // client has no exported methods, skip it continue; } const serverName = getServerName(clientAccessor.returns); content += `${indent.get()}// ${serverName} contains the fakes for client ${clientAccessor.returns.name}\n`; content += `${indent.get()}${serverName} ${serverName}\n\n`; finalSubClients.push(clientAccessor.returns); } for (const method of client.methods) { if (helpers.isMethodInternal(method)) { // method isn't exported, don't create a fake for it continue; } let serverResponse; switch (method.kind) { case 'lroMethod': case 'lroPageableMethod': let respType = go.getTypeDeclaration(method.returns, pkg); if (method.kind === 'lroPageableMethod') { respType = `azfake.PagerResponder[${respType}]`; } serverResponse = `resp azfake.PollerResponder[${respType}], errResp azfake.ErrorResponder`; break; case 'method': serverResponse = `resp azfake.Responder[${go.getTypeDeclaration(method.returns, pkg)}], errResp azfake.ErrorResponder`; break; case 'pageableMethod': serverResponse = `resp azfake.PagerResponder[${go.getTypeDeclaration(method.returns, pkg)}]`; break; } const operationName = fixUpMethodName(method); content += `${indent.get()}// ${operationName} is the fake for method ${client.name}.${operationName}\n`; const successCodes = new Array(); if (method.returns.result?.kind === 'anyResult') { for (const httpStatus of getMethodStatusCodes(method)) { const result = method.returns.result.httpStatusCodeType[httpStatus]; if (!result) { // the operation contains a mix of schemas and non-schema responses successCodes.push(`${helpers.formatStatusCode(httpStatus)} (no return type)`); continue; } successCodes.push(`${helpers.formatStatusCode(httpStatus)} (returns ${go.getTypeDeclaration(result, pkg)})`); } content += `${indent.get()}// HTTP status codes to indicate success:\n`; for (const successCode of successCodes) { content += `${indent.get()}// - ${successCode}\n`; } } else { for (const statusCode of getMethodStatusCodes(method)) { successCodes.push(`${helpers.formatStatusCode(statusCode)}`); } content += `${indent.get()}// HTTP status codes to indicate success: ${successCodes.join(', ')}\n`; } content += `${indent.get()}${operationName} func(${getAPIParametersSig(pkg, method, imports)}) (${serverResponse})\n\n`; finalMethods.push(method); switch (method.kind) { case 'lroMethod': case 'lroPageableMethod': ++countLROs; break; case 'pageableMethod': ++countPagers; break; } } content += '}\n\n'; /////////////////////////////////////////////////////////////////////////// const serverTransport = `${serverName}Transport`; content += `// New${serverTransport} creates a new instance of ${serverTransport} with the provided implementation.\n`; content += `// The returned ${serverTransport} instance is connected to an instance of ${go.getTypeDeclaration(client, pkg)} via the\n`; content += "// azcore.ClientOptions.Transporter field in the client's constructor parameters.\n"; content += `func New${serverTransport}(srv *${serverName}) *${serverTransport} {\n`; if (countLROs === 0 && countPagers === 0) { content += `${indent.get()}return &${serverTransport}{srv: srv}\n}\n\n`; } else { content += `${indent.get()}return &${serverTransport}{\n`; indent.push(); content += `${indent.get()}srv: srv,\n`; for (const method of finalMethods) { let respType = go.getTypeDeclaration(method.returns, pkg); switch (method.kind) { case 'lroMethod': case 'lroPageableMethod': if (method.kind === 'lroPageableMethod') { respType = `azfake.PagerResponder[${go.getTypeDeclaration(method.returns, pkg)}]`; } requiredHelpers.tracker = true; content += `${indent.get()}${naming.uncapitalize(fixUpMethodName(method))}: newTracker[azfake.PollerResponder[${respType}]](),\n`; break; case 'pageableMethod': requiredHelpers.tracker = true; content += `${indent.get()}${naming.uncapitalize(fixUpMethodName(method))}: newTracker[azfake.PagerResponder[${respType}]](),\n`; break; } } indent.pop(); content += `${indent.get()}}\n}\n\n`; } content += `// ${serverTransport} connects instances of ${go.getTypeDeclaration(client, pkg)} to instances of ${serverName}.\n`; content += `// Don't use this type directly, use New${serverTransport} instead.\n`; content += `type ${serverTransport} struct {\n`; content += `${indent.get()}srv *${serverName}\n`; // add server transports for client accessors if (finalSubClients.length > 0) { requiredHelpers.initServer = true; imports.add('sync'); content += `${indent.get()}trMu sync.Mutex\n`; for (const subClient of finalSubClients) { const serverName = getServerName(subClient); content += `${indent.get()}tr${serverName} *${serverName}Transport\n`; } } for (const method of finalMethods) { // create state machines for any pager/poller operations let respType = go.getTypeDeclaration(method.returns, pkg); switch (method.kind) { case 'lroMethod': case 'lroPageableMethod': if (method.kind === 'lroPageableMethod') { respType = `azfake.PagerResponder[${go.getTypeDeclaration(method.returns, pkg)}]`; } requiredHelpers.tracker = true; content += `${indent.get()}${naming.uncapitalize(fixUpMethodName(method))} *tracker[azfake.PollerResponder[${respType}]]\n`; break; case 'pageableMethod': requiredHelpers.tracker = true; content += `${indent.get()}${naming.uncapitalize(fixUpMethodName(method))} *tracker[azfake.PagerResponder[${go.getTypeDeclaration(method.returns, pkg)}]]\n`; break; } } content += '}\n\n'; content += generateServerTransportDo(serverTransport, client, finalSubClients, finalMethods, indent); content += generateServerTransportClientDispatch(serverTransport, finalSubClients, imports, indent); content += generateServerTransportMethodDispatch(serverTransport, client, finalMethods, indent); content += generateServerTransportMethods(pkg, serverTransport, finalMethods, imports, indent); content += `// set this to conditionally intercept incoming requests to ${serverTransport}\n`; content += `var ${getTransportInterceptorVarName(client)} interface {\n`; content += `${indent.get()}// Do returns true if the server transport should use the returned response/error\n`; content += `${indent.get()}Do(*http.Request) (*http.Response, error, bool)\n}\n`; /////////////////////////////////////////////////////////////////////////// // stitch everything together let text = helpers.contentPreamble(pkg); text += imports.text(); text += content; operations.push(new OperationGroupContent(serverName, text)); } if (target === 'azure-arm' && pkg.parent.clients.length > 0) { // ARM server factory uses the initServer func requiredHelpers.initServer = true; } return new ServerContent(operations, generateServerInternal(pkg, requiredHelpers)); } function getTransportInterceptorVarName(client) { return `${helpers.camelCase(getServerName(client))}TransportInterceptor`; } // method names for fakes dispatching const dispatchMethodFake = 'dispatchToMethodFake'; const dispatchToClientFake = 'dispatchToClientFake'; function generateServerTransportDo(serverTransport, client, finalSubClients, finalMethods, indent) { const receiverName = serverTransport[0].toLowerCase(); let content = `// Do implements the policy.Transporter interface for ${serverTransport}.\n`; content += `func (${receiverName} *${serverTransport}) Do(req *http.Request) (*http.Response, error) {\n`; content += `${indent.get()}rawMethod := req.Context().Value(runtime.CtxAPINameKey{})\n`; content += `${indent.get()}method, ok := rawMethod.(string)\n`; content += `${indent.get()}if !ok {\n`; content += `${indent.push().get()}return nil, nonRetriableError{errors.New("unable to dispatch request, missing value for CtxAPINameKey")}\n`; content += `${indent.pop().get()}}\n\n`; if (finalSubClients.length > 0 && finalMethods.length > 0) { // client contains client accessors and methods. // if the method isn't for this client, dispatch to the correct client content += `${indent.get()}if client := method[:strings.Index(method, ".")]; client != "${client.name}" {\n`; content += `${indent.push().get()}return ${receiverName}.${dispatchToClientFake}(req, client)\n`; content += `${indent.pop().get()}}\n`; // else dispatch to our method fakes content += `${indent.get()}return ${receiverName}.${dispatchMethodFake}(req, method)\n`; } else if (finalSubClients.length > 0) { content += `${indent.get()}return ${receiverName}.${dispatchToClientFake}(req, method[:strings.Index(method, ".")])\n`; } else { content += `${indent.get()}return ${receiverName}.${dispatchMethodFake}(req, method)\n`; } content += '}\n\n'; // end Do return content; } function generateServerTransportClientDispatch(serverTransport, subClients, imports, indent) { if (subClients.length === 0) { return ''; } /** gathers all children, not just immediate children, in breadth first order */ const getAllSubClientsForCase = function (client) { const result = new Array(); const visited = new Set(); const queue = new Array(); for (const clientAccessor of client.clientAccessors) { if (helpers.clientHasNoExportedMethods(clientAccessor.returns)) { continue; } if (!visited.has(clientAccessor.returns)) { visited.add(clientAccessor.returns); queue.push(clientAccessor.returns); result.push(clientAccessor.returns); } } while (queue.length > 0) { const current = queue.shift(); for (const clientAccessor of current.clientAccessors) { if (helpers.clientHasNoExportedMethods(clientAccessor.returns)) { continue; } if (!visited.has(clientAccessor.returns)) { visited.add(clientAccessor.returns); queue.push(clientAccessor.returns); result.push(clientAccessor.returns); } } } return result; }; const receiverName = serverTransport[0].toLowerCase(); imports.add('strings'); let content = `func (${receiverName} *${serverTransport}) ${dispatchToClientFake}(req *http.Request, client string) (*http.Response, error) {\n`; content += `${indent.get()}var resp *http.Response\n${indent.get()}var err error\n\n`; content += `${indent.get()}switch client {\n`; for (const subClient of subClients) { // we must include all child clients, not just the immediate children const subClientsForCase = getAllSubClientsForCase(subClient); const allClientNamesForCase = new Array(`"${subClient.name}"`); allClientNamesForCase.push(...subClientsForCase.map((each) => `"${each.name}"`)); content += `${indent.get()}case ${allClientNamesForCase.join(', ')}:\n`; const serverName = getServerName(subClient); indent.push(); content += `${indent.get()}initServer(&${receiverName}.trMu, &${receiverName}.tr${serverName}, func() *${serverName}Transport {\n`; content += `${indent.get()}return New${serverName}Transport(&${receiverName}.srv.${serverName}) })\n`; content += `${indent.get()}resp, err = ${receiverName}.tr${serverName}.Do(req)\n`; indent.pop(); } content += `${indent.get()}default:\n`; content += `${indent.push().get()}err = fmt.Errorf("unhandled client %s", client)\n`; indent.pop(); content += `${indent.get()}}\n\n`; // end switch content += `${indent.get()}return resp, err\n}\n\n`; return content; } function generateServerTransportMethodDispatch(serverTransport, client, finalMethods, indent) { if (finalMethods.length === 0) { return ''; } const receiverName = serverTransport[0].toLowerCase(); let content = `func (${receiverName} *${serverTransport}) ${dispatchMethodFake}(req *http.Request, method string) (*http.Response, error) {\n`; content += `${indent.get()}resultChan := make(chan result, 1)\n`; content += `${indent.get()}go func() {\n`; indent.push(); content += `${indent.get()}var intercepted bool\n${indent.get()}var res result\n`; const interceptorVarName = getTransportInterceptorVarName(client); content += `${indent.get()} if ${interceptorVarName} != nil {\n`; content += `${indent.push().get()} res.resp, res.err, intercepted = ${interceptorVarName}.Do(req)\n`; content += `${indent.pop().get()}}\n`; content += `${indent.get()}if !intercepted {\n`; indent.push(); content += `${indent.get()}switch method {\n`; for (const method of finalMethods) { const operationName = fixUpMethodName(method); content += `${indent.get()}case "${client.name}.${operationName}":\n`; content += `${indent.push().get()}res.resp, res.err = ${receiverName}.dispatch${operationName}(req)\n`; indent.pop(); } content += `${indent.push().get()}default:\n`; content += `${indent.get()}res.err = fmt.Errorf("unhandled API %s", method)\n`; content += `${indent.pop().get()}}\n\n`; // end switch content += `${indent.pop().get()}}\n`; // end if !intercepted content += `${indent.get()}resultChan <- res\n`; content += `${indent.pop().get()}}()\n\n`; // end goroutine content += `${indent.get()}select {\n`; content += `${indent.get()}case <-req.Context().Done():\n`; content += `${indent.push().get()}return nil, req.Context().Err()\n`; content += `${indent.pop().get()}case res := <-resultChan:\n`; content += `${indent.push().get()}return res.resp, res.err\n`; content += `${indent.pop().get()}}\n}\n\n`; return content; } /** * generates the server transport methods for a fake server transport * * @param pkg contains the package content * @param serverTransport the name of the server transport type * @param finalMethods the array of methods for which to generate the fake transports * @param imports the import manager currently in scope * @param indent the indentation helper currently in scope * @returns the text for the server transport methods */ function generateServerTransportMethods(pkg, serverTransport, finalMethods, imports, indent) { if (finalMethods.length === 0) { return ''; } imports.addForPkg(pkg.parent); imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/fake', 'azfake'); imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/fake/server'); imports.add('slices'); const receiverName = serverTransport[0].toLowerCase(); let content = ''; for (const method of finalMethods) { content += `func (${receiverName} *${serverTransport}) dispatch${fixUpMethodName(method)}(req *http.Request) (*http.Response, error) {\n`; content += `${indent.get()}if ${receiverName}.srv.${fixUpMethodName(method)} == nil {\n`; content += `${indent.push().get()}return nil, &nonRetriableError{errors.New("fake for method ${fixUpMethodName(method)} not implemented")}\n`; content += `${indent.pop().get()}}\n`; switch (method.kind) { case 'lroMethod': case 'lroPageableMethod': // must check LRO before pager as you can have paged LROs content += dispatchForLROBody(pkg, receiverName, method, imports, indent); break; case 'method': { content += dispatchForOperationBody(pkg, receiverName, method, imports, indent); content += `${indent.get()}respContent := server.GetResponseContent(respr)\n`; const formattedStatusCodes = helpers.formatStatusCodes(method.httpStatusCodes); content += `${indent.get()}if !slices.Contains([]int{${formattedStatusCodes}}, respContent.HTTPStatus) {\n`; content += `${indent.push().get()}return nil, &nonRetriableError{fmt.Errorf("unexpected status code %d. acceptable values are ${formattedStatusCodes}", respContent.HTTPStatus)}\n`; content += `${indent.pop().get()}}\n`; if (!method.returns.result || method.returns.result.kind === 'headAsBooleanResult') { content += `${indent.get()}resp, err := server.NewResponse(respContent, req, nil)\n`; } else if (method.returns.result.kind === 'anyResult') { content += `${indent.get()}resp, err := server.MarshalResponseAs${method.returns.result.format}(respContent, server.GetResponse(respr).${getResultFieldName(method.returns.result)}, req)\n`; } else if (method.returns.result.kind === 'binaryResult') { content += `${indent.get()}resp, err := server.NewResponse(respContent, req, &server.ResponseOptions{\n`; indent.push(); content += `${indent.get()}Body: server.GetResponse(respr).${getResultFieldName(method.returns.result)},\n`; content += `${indent.get()}ContentType: req.Header.Get("Content-Type"),\n`; content += `${indent.pop().get()}})\n`; } else if (method.returns.result.kind === 'monomorphicResult') { if (method.returns.result.monomorphicType.kind === 'encodedBytes') { const encoding = method.returns.result.monomorphicType.encoding; content += `${indent.get()}resp, err := server.MarshalResponseAsByteArray(respContent, server.GetResponse(respr).${getResultFieldName(method.returns.result)}, runtime.Base64${encoding}Format, req)\n`; } else if (method.returns.result.monomorphicType.kind === 'rawJSON') { imports.add('bytes'); imports.add('io'); content += `${indent.get()}resp, err := server.NewResponse(respContent, req, &server.ResponseOptions{\n`; indent.push(); content += `${indent.get()}Body: io.NopCloser(bytes.NewReader(server.GetResponse(respr).RawJSON)),\n`; content += `${indent.get()}ContentType: "application/json",\n`; content += `${indent.pop().get()}})\n`; } else if (method.returns.result.format === 'Text') { let contentToMarshal; const respField = getResultFieldName(method.returns.result); const getResponseField = `server.GetResponse(respr).${respField}`; switch (method.returns.result.monomorphicType.kind) { case 'scalar': { imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/to'); // create a local var that will hold the string-formatted scalar contentToMarshal = `formatted${respField}`; content += `${indent.get()}var ${contentToMarshal} *string\n`; const localVar = naming.uncapitalize(respField); const resultType = method.returns.result.monomorphicType; // if value := server.GetResponse(respr).Value; value != nil {...format as string...} content += `${indent.get()}${helpers.buildIfBlock(indent, { condition: `${localVar} := ${getResponseField}; ${localVar} != nil`, body: (indent) => `${indent.get()}${contentToMarshal} = to.Ptr(${helpers.formatValue(localVar, resultType, imports, true)})\n`, })}\n`; break; } case 'string': contentToMarshal = getResponseField; break; default: throw new CodegenError('UnsupportedTsp', `unsupported text return kind ${method.returns.result.monomorphicType.kind} for method ${method.receiver.type.name}.${method.name}`); } content += `${indent.get()}resp, err := server.MarshalResponseAsText(respContent, ${contentToMarshal}, req)\n`; } else { let respField = `.${getResultFieldName(method.returns.result)}`; if (method.returns.result.format === 'XML' && method.returns.result.monomorphicType.kind === 'slice') { respField = ''; } let responseField = `server.GetResponse(respr)${respField}`; if (method.returns.result.monomorphicType.kind === 'time') { imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/datetime'); responseField = `(*datetime.${method.returns.result.monomorphicType.format})(${responseField})`; } content += `${indent.get()}resp, err := server.MarshalResponseAs${method.returns.result.format}(respContent, ${responseField}, req)\n`; } } else if (method.returns.result.kind === 'modelResult' || method.returns.result.kind === 'polymorphicResult') { const respField = `.${getResultFieldName(method.returns.result)}`; const responseField = `server.GetResponse(respr)${respField}`; content += `${indent.get()}resp, err := server.MarshalResponseAs${method.returns.result.format}(respContent, ${responseField}, req)\n`; } content += `${indent.get()}if err != nil {\n`; content += `${indent.push().get()}return nil, err\n`; content += `${indent.pop().get()}}\n`; // propagate any header response values into the *http.Response for (const header of method.returns.headers) { if (header.kind === 'headerMapResponse') { content += `${indent.get()}for k, v := range server.GetResponse(respr).${header.fieldName} {\n`; content += `${indent.push().get()}if v != nil {\n`; content += `${indent.push().get()}resp.Header.Set("${header.headerName}"+k, *v)\n`; content += `${indent.pop().get()}}\n`; content += `${indent.pop().get()}}\n`; } else { content += `${indent.get()}if val := server.GetResponse(respr).${header.fieldName}; val != nil {\n`; content += `${indent.push().get()}resp.Header.Set("${header.headerName}", ${helpers.formatValue('val', header.type, imports, true)})\n`; content += `${indent.pop().get()}}\n`; } } content += `${indent.get()}return resp, nil\n`; break; } case 'pageableMethod': content += dispatchForPagerBody(pkg, receiverName, method, imports, indent); break; default: method; } content += '}\n\n'; } return content; } /** * generates the core dispatching logic for a server dispatch method. * this code is common to all method types. * * @param pkg contains the package content * @param receiverName the name of the receiver for the dispatch method * @param method the method for which to emit dispatching logic * @param imports the import manager currently in scope * @param indent the indentation helper currently in scope * @returns the text for dispatching logic */ function dispatchForOperationBody(pkg, receiverName, method, imports, indent) { const methodParamGroups = helpers.getMethodParamGroups(method); const numPathParams = methodParamGroups.pathParams.filter((each) => !go.isLiteralParameter(each.style)).length; let content = ''; if (numPathParams > 0) { imports.add('regexp'); content += `${indent.get()}const regexStr = \`${createPathParamsRegex(method, methodParamGroups.pathParams)}\`\n`; content += `${indent.get()}regex := regexp.MustCompile(regexStr)\n`; content += `${indent.get()}matches := regex.FindStringSubmatch(req.URL.EscapedPath())\n`; // the total number of matches is the number of capture groups // plus the full match. so we add + 1 to include the full match. content += `${indent.get()}if len(matches) < ${numPathParams + 1} {\n`; content += `${indent.push().get()}return nil, fmt.Errorf("failed to parse path %s", req.URL.Path)\n${indent.pop().get()}}\n`; } const allQueryParams = methodParamGroups.encodedQueryParams.concat(methodParamGroups.unencodedQueryParams); if (allQueryParams.find((each) => each.location === 'method' && !go.isLiteralParameter(each.style))) { content += `${indent.get()}qp := req.URL.Query()\n`; } // note that these are mutually exclusive const bodyParam = methodParamGroups.bodyParam; const formBodyParams = methodParamGroups.formBodyParams; const multipartBodyParams = methodParamGroups.multipartBodyParams; const partialBodyParams = methodParamGroups.partialBodyParams; if (bodyParam) { switch (bodyParam.bodyFormat) { case 'JSON': case 'XML': if (bodyParam && !go.isLiteralParameter(bodyParam.style)) { imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/fake', 'azfake'); switch (bodyParam.type.kind) { case 'encodedBytes': content += `${indent.get()}body, err := server.UnmarshalRequestAsByteArray(req, runtime.Base64${bodyParam.type.encoding}Format)\n`; content += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; break; case 'interface': requiredHelpers.readRequestBody = true; content += `${indent.get()}raw, err := readRequestBody(req)\n`; content += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; content += `${indent.get()}body, err := unmarshal${bodyParam.type.name}(raw)\n`; content += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; break; case 'rawJSON': imports.add('io'); content += `${indent.get()}body, err := io.ReadAll(req.Body)\n`; content += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; content += `${indent.get()}req.Body.Close()\n`; break; default: { let bodyTypeName = go.getTypeDeclaration(bodyParam.type, pkg); if (bodyParam.type.kind === 'time') { imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/datetime'); bodyTypeName = `datetime.${bodyParam.type.format}`; } content += `${indent.get()}body, err := server.UnmarshalRequestAs${bodyParam.bodyFormat}[${bodyTypeName}](req)\n`; content += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; } } } break; case 'Text': if (bodyParam && !go.isLiteralParameter(bodyParam.style)) { imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/fake', 'azfake'); content += `${indent.get()}body, err := server.UnmarshalRequestAsText(req)\n`; content += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; } break; } // nothing to do for binary media type } else if (multipartBodyParams.length > 0) { imports.add('io'); imports.add('mime'); imports.add('mime/multipart'); content += `${indent.get()}_, params, err := mime.ParseMediaType(req.Header.Get("Content-Type"))\n`; content += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; content += `${indent.get()}reader := multipart.NewReader(req.Body, params["boundary"])\n`; for (const param of multipartBodyParams) { content += `${indent.get()}var ${param.name} ${go.getTypeDeclaration(param.type, pkg)}\n`; } content += `${indent.get()}for {\n`; indent.push(); content += `${indent.get()}var part *multipart.Part\n`; content += `${indent.get()}part, err = reader.NextPart()\n`; content += `${indent.get()}if errors.Is(err, io.EOF) {\n${indent.push().get()}break\n`; content += `${indent.pop().get()}} else if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; content += `${indent.get()}var content []byte\n`; content += `${indent.get()}switch fn := part.FormName(); fn {\n`; // specify boolTarget if parsing bools happens in place. // i.e. the result from the parsing doesn't require further conversion (e.g. casting) // otherwise the parsed value is in a local var named parsed. const parsePrimitiveType = function (typeName, boolTarget) { let parseErr = 'parseErr'; const parseResults = `parsed, ${parseErr}`; let parsingCode = ''; imports.add('strconv'); switch (typeName) { case 'bool': if (boolTarget) { // we reuse the err var declared earlier when calling reader.NextPart() parsingCode = `${indent.get()}${boolTarget}, err = strconv.ParseBool(string(content))\n`; parseErr = 'err'; } else { parsingCode = `${indent.get()}${parseResults} := strconv.ParseBool(string(content))\n`; } break; case 'float32': case 'float64': parsingCode = `${indent.get()}${parseResults} := strconv.ParseFloat(string(content), ${helpers.getBitSizeForNumber(typeName)})\n`; break; case 'int8': case 'int16': case 'int32': case 'int64': parsingCode = `${indent.get()}${parseResults} := strconv.ParseInt(string(content), 10, ${helpers.getBitSizeForNumber(typeName)})\n`; break; default: throw new CodegenError('InternalError', `unhandled multipart parameter primitive type ${typeName}`); } parsingCode += `${indent.get()}if ${parseErr} != nil {\n${indent.push().get()}return nil, ${parseErr}\n${indent.pop().get()}}\n`; return parsingCode; }; const isModelType = function (type) { return type.kind === 'model' || type.kind === 'polymorphicModel'; }; const emitCase = function (caseValue, paramVar, type, destIsByValue) { let caseContent = `${indent.get()}case "${caseValue}":\n`; indent.push(); caseContent += `${indent.get()}content, err = io.ReadAll(part)\n`; caseContent += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; let assignedValue; if (isModelType(helpers.recursiveUnwrapMapSlice(type))) { imports.add('encoding/json'); caseContent += `${indent.get()}if err = json.Unmarshal(content, &${paramVar}); err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; } else if (type.kind === 'readSeekCloser') { imports.add('bytes'); imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming'); assignedValue = 'streaming.NopCloser(bytes.NewReader(content))'; } else if (type.kind === 'constant') { let from; switch (type.type) { case 'bool': case 'float32': case 'float64': case 'int32': case 'int64': caseContent += parsePrimitiveType(type.type); from = 'parsed'; break; case 'string': from = 'content'; break; } assignedValue = `${go.getTypeDeclaration(type, pkg)}(${from})`; } else if (type.kind === 'scalar') { switch (type.type) { case 'bool': imports.add('strconv'); // ParseBool happens in place, so no need to set assignedValue caseContent += parsePrimitiveType(type.type, paramVar); break; case 'float32': case 'float64': case 'int8': case 'int16': case 'int32': case 'int64': caseContent += parsePrimitiveType(type.type); assignedValue = `${type.type}(parsed)`; break; default: throw new CodegenError('InternalError', `unhandled multipart parameter primitive type ${type.type}`); } } else if (type.kind === 'string') { assignedValue = 'string(content)'; } else if (helpers.recursiveUnwrapMapSlice(type).kind === 'multipartContent') { imports.add('bytes'); imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming'); const bodyContent = 'streaming.NopCloser(bytes.NewReader(content))'; const contentType = 'part.Header.Get("Content-Type")'; const filename = 'part.FileName()'; if (type.kind === 'slice') { caseContent += `${indent.get()}${paramVar} = append(${paramVar}, streaming.MultipartContent{\n`; indent.push(); caseContent += `${indent.get()}Body: ${bodyContent},\n`; caseContent += `${indent.get()}ContentType: ${contentType},\n`; caseContent += `${indent.get()}Filename: ${filename},\n`; caseContent += `${indent.pop().get()}})\n`; } else { caseContent += `${indent.get()}${paramVar}.Body = ${bodyContent}\n`; caseContent += `${indent.get()}${paramVar}.ContentType = ${contentType}\n`; caseContent += `${indent.get()}${paramVar}.Filename = ${filename}\n`; } } else if (type.kind === 'slice') { if (type.elementType.kind === 'readSeekCloser') { imports.add('bytes'); imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming'); assignedValue = `append(${paramVar}, streaming.NopCloser(bytes.NewReader(content)))`; } else { throw new CodegenError('InternalError', `uhandled multipart parameter array element kind ${type.elementType.kind}`); } } else if (type.kind === 'encodedBytes') { imports.add('encoding/base64'); caseContent += `${indent.get()}${paramVar}, err = base64.${type.encoding}Encoding.DecodeString(string(content))\n`; caseContent += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; } else { throw new CodegenError('InternalError', `uhandled multipart parameter kind ${type.kind}`); } if (assignedValue) { if (!destIsByValue) { imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/to'); assignedValue = `to.Ptr(${assignedValue})`; } caseContent += `${indent.get()}${paramVar} = ${assignedValue}\n`; } indent.pop(); return caseContent; }; for (const param of multipartBodyParams) { if (isModelType(param.type)) { for (const field of param.type.fields) { content += emitCase(field.serializedName, `${param.name}.${field.name}`, field.type, field.byValue); } } else { // for this case we've emitted local vars of the underlying // type which is why we pass true for param destIsByValue content += emitCase(param.name, param.name, param.type, true); } } content += `${indent.get()}default:\n`; content += `${indent.push().get()}return nil, fmt.Errorf("unexpected part %s", fn)\n`; content += `${indent.pop().get()}}\n`; // end switch content += `${indent.pop().get()}}\n`; // end for } else if (formBodyParams.length > 0) { for (const param of formBodyParams) { content += `${indent.get()}var ${param.name} ${go.getTypeDeclaration(param.type, pkg)}\n`; } content += `${indent.get()}if err := req.ParseForm(); err != nil {\n${indent.push().get()}return nil, &nonRetriableError{fmt.Errorf("failed parsing form data: %v", err)}\n${indent.pop().get()}}\n`; content += `${indent.get()}for key := range req.Form {\n`; content += `${indent.push().get()}switch key {\n`; for (const param of formBodyParams) { content += `${indent.get()}case "${param.formDataName}":\n`; let assignedValue; switch (param.type.kind) { case 'constant': assignedValue = `${go.getTypeDeclaration(param.type, pkg)}(req.FormValue(key))`; break; case 'string': assignedValue = 'req.FormValue(key)'; break; default: throw new CodegenError('InternalError', `uhandled form parameter kind ${param.type.kind}`); } content += `${indent.push().get()}${param.name} = ${assignedValue}\n`; indent.pop(); } content += `${indent.pop().get()}}\n`; // end switch content += `${indent.get()}}\n`; // end for } else if (partialBodyParams.length > 0) { // construct the partial body params type and unmarshal it content += `${indent.get()}type partialBodyParams struct {\n`; indent.push(); for (const partialBodyParam of partialBodyParams) { content += `${indent.get()}${naming.capitalize(partialBodyParam.name)} ${helpers.star(partialBodyParam.byValue)}${go.getTypeDeclaration(partialBodyParam.type, pkg)} \`json:"${partialBodyParam.serializedName}"\`\n`; } content += `${indent.pop().get()}}\n`; content += `${indent.get()}body, err := server.UnmarshalRequestAs${partialBodyParams[0].format}[partialBodyParams](req)\n`; content += `${indent.get()}if err != nil {\n${indent.push().get()}return nil, err\n${indent.pop().get()}}\n`; } const result = parseHeaderPathQueryParams(pkg, method, imports, indent); content += result.content; // translate each partial body param to its field within the unmarshalled body for (const partialBodyParam of partialBodyParams) { result.params.set(partialBodyParam.name, `${helpers.star(partialBodyParam.byValue)}body.${naming.capitalize(partialBodyParam.name)}`); } const apiCall = `:= ${receiverName}.srv.${fixUpMethodName(method)}(${populateApiParams(pkg, method, result.params, imports)})`; if (method.kind === 'pageableMethod') { content += `resp ${apiCall}\n`; return content; } content += `${indent.get()}respr, errRespr ${apiCall}\n`; content += `${indent.get()}if respErr := server.GetError(errRespr, req); respErr != nil {\n`; content += `${indent.push().get()}return nil, respErr\n${indent.pop().get()}}\n`; return content; } function getMethodStatusCodes(method) { // NOTE: don't modify the original array! const statusCodes = Array.from(method.httpStatusCodes); switch (method.kind) { case 'lroMethod': case 'lroPageableMethod': if (!statusCodes.includes(200)) { // pollers always include 200 as an acceptible status code so we emulate that here statusCodes.unshift(200); } if (!method.returns.result && !statusCodes.includes(204)) { // also include 204 if the LRO doesn't return a body statusCodes.push(204); } break; } return statusCodes; } /** * generates the dispatching logic for an LRO server dispatch method * * @param pkg contains the package contents * @param receiverName the name of the receiver for the dispatch method * @param method the LRO method for which to emit the dispatch logic * @param imports the import manager currently in scope * @param indent the indentation helper currently in scope * @returns the text for the LRO dispatch logic */ function dispatchForLROBody(pkg, receiverName, method, imports, indent) { const operationName = fixUpMethodName(method); const localVarName = naming.uncapitalize(operationName); const operationStateMachine = `${receiverName}.${naming.uncapitalize(operationName)}`; let content = `${indent.get()}${localVarName} := ${operationStateMachine}.get(req)\n`; content += `${indent.get()}if ${localVarName} == nil {\n`; content += dispatchForOperationBody(pkg, receiverName, method, imports, indent); indent.push(); content += `${indent.get()}${localVarName} = &respr\n`; content += `${indent.get()}${operationStateMachine}.add(req, ${localVarName})\n`; content += `${indent.pop().get()}}\n\n`; content += `${indent.get()}resp, err := server.PollerResponderNext(${localVarName}, req)\n`; content += `${indent.get()}if err != nil {\n`; content += `${indent.push().get()}return nil, err\n`; content += `${indent.pop().get()}}\n\n`; const formattedStatusCodes = helpers.formatStatusCodes(getMethodStatusCodes(method)); content += `${indent.get()}if !slices.Contains([]int{${formattedStatusCodes}}, resp.StatusCode) {\n`; indent.push(); content += `${indent.get()}${operationStateMachine}.remove(req)\n`; content += `${indent.get()}return nil, &nonRetriableError{fmt.Errorf("unexpected status code %d. acceptable values are ${formattedStatusCodes}", resp.StatusCode)}\n`; content += `${indent.pop().get()}}\n`; content += `${indent.get()}if !server.PollerResponderMore(${localVarName}) {\n`; content += `${indent.push().get()}${operationStateMachine}.remove(req)\n`; content += `${indent.pop().get()}}\n\n`; content += `${indent.get()}return resp, nil\n`; return content; } /** * generates the dispatching logic for a paged server dispatch method * * @param pkg contains the package contents * @param receiverName the name of the receiver for the dispatch method * @param method the pageable method for which to emit the dispatch logic * @param imports the import manager currently in scope * @param indent the indentation helper currently in scope * @returns the text for the pageable dispatch logic */ function dispatchForPagerBody(pkg, receiverName, method, imports, indent) { const operationName = fixUpMethodName(method); const localVarName = naming.uncapitalize(operationName); const operationStateMachine = `${receiverName}.${naming.uncapitalize(operationName)}`; let content = `${in