@autorest/go
Version:
AutoRest Go Generator
873 lines • 83.2 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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