UNPKG

@autorest/go

Version:
756 lines 28.8 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 { values } from '@azure-tools/linq'; import { capitalize, comment, uncapitalize } from '@azure-tools/codegen'; import { CodegenError } from './errors.js'; // variable to be used to determine comment length when calling comment from @azure-tools export const commentLength = 120; export const dateFormat = '2006-01-02'; export const datetimeRFC3339Format = 'time.RFC3339Nano'; export const datetimeRFC1123Format = 'time.RFC1123'; export const timeRFC3339Format = '15:04:05.999999999Z07:00'; // returns the common source-file preamble (license comment, package name etc) export function contentPreamble(codeModel, packageName) { if (!packageName) { packageName = codeModel.packageName; } const headerText = comment(codeModel.options.headerText, '// '); // ensure tools recognize the file as generated according to // https://pkg.go.dev/cmd/go#hdr-Generate_Go_files_by_processing_source let text = headerText.replace(/^\/\/ Code generated .*\.$/m, '$& DO NOT EDIT.'); if (!text.match(/^\/\/ Code generated .* DO NOT EDIT\.$/m)) { text += '\n// Code generated by @autorest/go. DO NOT EDIT.'; } text += `\n\npackage ${packageName}\n\n`; return text; } // used to sort strings in ascending order export function sortAscending(a, b) { return a < b ? -1 : a > b ? 1 : 0; } export function isParameter(param) { return param.groupName === undefined; } export function isParameterGroup(param) { return param.groupName !== undefined; } // returns the type name with possible * prefix export function formatParameterTypeName(param, pkgName) { let typeName; if (isParameterGroup(param)) { typeName = param.groupName; if (pkgName) { typeName = `${pkgName}.${typeName}`; } if (param.required) { return typeName; } } else { typeName = go.getTypeDeclaration(param.type, pkgName); if (parameterByValue(param)) { // client parameters with default values aren't emitted as pointer-to-type return typeName; } } return `*${typeName}`; } export function parameterByValue(param) { return go.isRequiredParameter(param) || (param.location === 'client' && go.isClientSideDefault(param.kind)); } // sorts parameters by their required state, ordering required before optional export function sortParametersByRequired(a, b) { let aRequired = false; let bRequired = false; if (isParameter(a)) { aRequired = go.isRequiredParameter(a); } else { aRequired = a.required; } if (isParameter(b)) { bRequired = go.isRequiredParameter(b); } else { bRequired = b.required; } if (aRequired === bRequired) { return 0; } else if (aRequired && !bRequired) { return -1; } return 1; } // returns the parameters for the internal request creator method. // e.g. "i int, s string" export function getCreateRequestParametersSig(method) { const methodParams = getMethodParameters(method); const params = new Array(); params.push('ctx context.Context'); for (const methodParam of values(methodParams)) { let paramName = uncapitalize(methodParam.name); // when creating the method sig for fooCreateRequest, if the options type is empty // or only contains the ResumeToken param use _ for the param name to quiet the linter if (isParameterGroup(methodParam) && (methodParam.params.length === 0 || (methodParam.params.length === 1 && go.isResumeTokenParameter(methodParam.params[0])))) { paramName = '_'; } params.push(`${paramName} ${formatParameterTypeName(methodParam)}`); } return params.join(', '); } // returns the parameter names for an operation (excludes the param types). // e.g. "i, s" export function getCreateRequestParameters(method) { // NOTE: keep in sync with getCreateRequestParametersSig const methodParams = getMethodParameters(method); const params = new Array(); params.push('ctx'); for (const methodParam of values(methodParams)) { params.push(uncapitalize(methodParam.name)); } return params.join(', '); } // returns the complete collection of method parameters export function getMethodParameters(method, paramsFilter) { const params = new Array(); const paramGroups = new Array(); let methodParams = method.parameters; if (paramsFilter) { methodParams = paramsFilter(methodParams); } for (const param of values(methodParams)) { if (param.location === 'client') { // client params are passed via the receiver // must check before param group as client params can be grouped continue; } else if (param.group) { // param groups will be added after individual params if (!paramGroups.includes(param.group)) { paramGroups.push(param.group); } } else if (go.isLiteralValue(param.type)) { // don't generate a parameter for a constant // NOTE: this check must come last as non-required optional constants // in header/query params get dumped into the optional params group continue; } else { params.push(param); } } // move global optional params to the end of the slice params.sort(sortParametersByRequired); // add any parameter groups. optional groups go last paramGroups.sort((a, b) => { if (a.required === b.required) { return 0; } if (a.required && !b.required) { return -1; } return 1; }); // add the optional param group last if it's not already in the list. if (go.isMethod(method)) { if (!values(paramGroups).any(gp => { return gp.groupName === method.optionalParamsGroup.groupName; })) { paramGroups.push(method.optionalParamsGroup); } } const combined = new Array(); for (const param of params) { combined.push(param); } for (const paramGroup of paramGroups) { combined.push(paramGroup); } return combined; } // returns the fully-qualified parameter name. this is usually just the name // but will include the client or optional param group name prefix as required. export function getParamName(param) { let paramName = param.name; // must check paramGroup first as client params can also be grouped if (param.group) { paramName = `${uncapitalize(param.group.name)}.${capitalize(paramName)}`; } if (param.location === 'client') { paramName = `client.${paramName}`; } // client parameters with default values aren't emitted as pointer-to-type if (!go.isRequiredParameter(param) && !(param.location === 'client' && go.isClientSideDefault(param.kind)) && !(isParameter(param) && param.byValue)) { paramName = `*${paramName}`; } return paramName; } // converts the Go code model encoding type to the type name in the standard library export function formatBytesEncoding(enc) { if (enc === 'URL') { return 'RawURL'; } return 'Std'; } export function formatParamValue(param, imports) { let paramName = getParamName(param); if (go.isFormBodyCollectionParameter(param) || go.isHeaderCollectionParameter(param) || go.isPathCollectionParameter(param) || go.isQueryCollectionParameter(param)) { if (param.collectionFormat === 'multi') { throw new CodegenError('InternalError', 'multi collection format should have been previously handled'); } const emitConvertOver = function (paramName, format) { const encodedVar = `encoded${capitalize(paramName)}`; let content = 'strings.Join(func() []string {\n'; content += `\t\t${encodedVar} := make([]string, len(${paramName}))\n`; content += `\t\tfor i := 0; i < len(${paramName}); i++ {\n`; content += `\t\t\t${encodedVar}[i] = ${format}\n\t\t}\n`; content += `\t\treturn ${encodedVar}\n`; content += `\t}(), "${separator}")`; return content; }; const separator = getDelimiterForCollectionFormat(param.collectionFormat); if (go.isPrimitiveType(param.type.elementType) && param.type.elementType.typeName === 'string') { imports.add('strings'); return `strings.Join(${paramName}, "${separator}")`; } else if (go.isBytesType(param.type.elementType)) { imports.add('encoding/base64'); imports.add('strings'); return emitConvertOver(param.name, `base64.${formatBytesEncoding(param.type.elementType.encoding)}Encoding.EncodeToString(${param.name}[i])`); } else if (go.isTimeType(param.type.elementType)) { imports.add('strings'); return emitConvertOver(param.name, `${param.type.elementType.dateTimeFormat}(${param.name}[i]).String()`); } else { imports.add('fmt'); imports.add('strings'); return `strings.Join(strings.Fields(strings.Trim(fmt.Sprint(${paramName}), "[]")), "${separator}")`; } } else if (go.isTimeType(param.type) && param.type.dateTimeFormat !== 'timeUnix') { // for most time types we call methods on time.Time which is why we remove the dereference. // however, for unix time, we cast to our unixTime helper first so we must keep the dereference. if (!go.isRequiredParameter(param) && paramName[0] === '*') { // remove the dereference paramName = paramName.substring(1); } } return formatValue(paramName, param.type, imports); } export function getDelimiterForCollectionFormat(cf) { switch (cf) { case 'csv': return ','; case 'pipes': return '|'; case 'ssv': return ' '; case 'tsv': return '\\t'; default: throw new CodegenError('InternalError', `unhandled CollectionFormat ${cf}`); } } export function formatValue(paramName, type, imports, defef) { // callers don't have enough context to know if paramName needs to be // deferenced so we track that here when specified. note that not all // cases will require paramName to be dereferenced. let star = ''; if (defef === true) { star = '*'; } if (go.isLiteralValue(type)) { // cannot use formatLiteralValue() since all values are treated as strings return `"${type.literal}"`; } else if (go.isBytesType(type)) { // ByteArray is a base-64 encoded value in string format imports.add('encoding/base64'); return `base64.${formatBytesEncoding(type.encoding)}Encoding.EncodeToString(${paramName})`; } else if (go.isPrimitiveType(type)) { if (type.typeName === 'bool') { imports.add('strconv'); return `strconv.FormatBool(${star}${paramName})`; } else if (type.typeName === 'int32') { imports.add('strconv'); return `strconv.FormatInt(int64(${star}${paramName}), 10)`; } else if (type.typeName === 'int64') { imports.add('strconv'); return `strconv.FormatInt(${star}${paramName}, 10)`; } else if (type.typeName === 'float32') { imports.add('strconv'); return `strconv.FormatFloat(float64(${star}${paramName}), 'f', -1, 32)`; } else if (type.typeName === 'float64') { imports.add('strconv'); return `strconv.FormatFloat(${star}${paramName}, 'f', -1, 64)`; } } else if (go.isTimeType(type)) { if (type.dateTimeFormat === 'dateType') { return `${paramName}.Format("${dateFormat}")`; } else if (type.dateTimeFormat === 'timeUnix') { return `timeUnix(${star}${paramName}).String()`; } else if (type.dateTimeFormat === 'timeRFC3339') { return `timeRFC3339(${star}${paramName}).String()`; } else { imports.add('time'); let format = datetimeRFC3339Format; if (type.dateTimeFormat === 'dateTimeRFC1123') { format = datetimeRFC1123Format; } return `${paramName}.Format(${format})`; } } else if (go.isConstantType(type)) { if (type.type === 'string') { return `string(${star}${paramName})`; } imports.add('fmt'); return `fmt.Sprintf("%v", ${star}${paramName})`; } return `${star}${paramName}`; } // returns the clientDefaultValue of the specified param. // this is usually the value in quotes (i.e. a string) however // it could also be a constant. export function formatLiteralValue(value, withCast) { if (go.isConstantType(value.type)) { return value.literal.name; } else if (go.isPrimitiveType(value.type)) { // if it's a string, we want the uncasted version to include quotes if (!withCast && value.type.typeName !== 'string') { return `${value.literal}`; } switch (value.type.typeName) { case 'float32': return `float32(${value.literal})`; case 'float64': return `float64(${value.literal})`; case 'int32': return `int32(${value.literal})`; case 'int64': return `int64(${value.literal})`; case 'string': if (value.literal[0] === '"') { // string is already quoted return value.literal; } return `"${value.literal}"`; default: return value.literal; } } else if (go.isTimeType(value.type)) { return `"${value.literal}"`; } return value.literal; } // returns true if at least one of the responses has a schema export function hasSchemaResponse(method) { const result = method.responseEnvelope.result; if (!result) { return false; } return go.isAnyResult(result) || go.isMonomorphicResult(result) || go.isPolymorphicResult(result) || go.isModelResult(result); } // returns the name of the response field within the response envelope export function getResultFieldName(method) { const result = method.responseEnvelope.result; if (!result) { throw new CodegenError('InternalError', `missing result for method ${method.name}`); } else if (go.isAnyResult(result) || go.isBinaryResult(result) || go.isHeadAsBooleanResult(result) || go.isMonomorphicResult(result)) { return result.fieldName; } else if (go.isPolymorphicResult(result)) { return result.interfaceType.name; } else if (go.isModelResult(result)) { return result.modelType.name; } else { throw new CodegenError('InternalError', `unhandled result type for method ${method.client.name}.${method.name}`); } } export function formatStatusCodes(statusCodes) { const asHTTPStatus = new Array(); for (const rawCode of statusCodes) { asHTTPStatus.push(formatStatusCode(rawCode)); } return asHTTPStatus.join(', '); } export function formatStatusCode(statusCode) { switch (statusCode) { // 1xx case 100: return 'http.StatusContinue'; case 101: return 'http.StatusSwitchingProtocols'; case 102: return 'http.StatusProcessing'; case 103: return 'http.StatusEarlyHints'; // 2xx case 200: return 'http.StatusOK'; case 201: return 'http.StatusCreated'; case 202: return 'http.StatusAccepted'; case 203: return 'http.StatusNonAuthoritativeInfo'; case 204: return 'http.StatusNoContent'; case 205: return 'http.StatusResetContent'; case 206: return 'http.StatusPartialContent'; case 207: return 'http.StatusMultiStatus'; case 208: return 'http.StatusAlreadyReported'; case 226: return 'http.StatusIMUsed'; // 3xx case 300: return 'http.StatusMultipleChoices'; case 301: return 'http.StatusMovedPermanently'; case 302: return 'http.StatusFound'; case 303: return 'http.StatusSeeOther'; case 304: return 'http.StatusNotModified'; case 305: return 'http.StatusUseProxy'; case 307: return 'http.StatusTemporaryRedirect'; // 4xx case 400: return 'http.StatusBadRequest'; case 401: return 'http.StatusUnauthorized'; case 402: return 'http.StatusPaymentRequired'; case 403: return 'http.StatusForbidden'; case 404: return 'http.StatusNotFound'; case 405: return 'http.StatusMethodNotAllowed'; case 406: return 'http.StatusNotAcceptable'; case 407: return 'http.StatusProxyAuthRequired'; case 408: return 'http.StatusRequestTimeout'; case 409: return 'http.StatusConflict'; case 410: return 'http.StatusGone'; case 411: return 'http.StatusLengthRequired'; case 412: return 'http.StatusPreconditionFailed'; case 413: return 'http.StatusRequestEntityTooLarge'; case 414: return 'http.StatusRequestURITooLong'; case 415: return 'http.StatusUnsupportedMediaType'; case 416: return 'http.StatusRequestedRangeNotSatisfiable'; case 417: return 'http.StatusExpectationFailed'; case 418: return 'http.StatusTeapot'; case 421: return 'http.StatusMisdirectedRequest'; case 422: return 'http.StatusUnprocessableEntity'; case 423: return 'http.StatusLocked'; case 424: return 'http.StatusFailedDependency'; case 425: return 'http.StatusTooEarly'; case 426: return 'http.StatusUpgradeRequired'; case 428: return 'http.StatusPreconditionRequired'; case 429: return 'http.StatusTooManyRequests'; case 431: return 'http.StatusRequestHeaderFieldsTooLarge'; case 451: return 'http.StatusUnavailableForLegalReasons'; // 5xx case 500: return 'http.StatusInternalServerError'; case 501: return 'http.StatusNotImplemented'; case 502: return 'http.StatusBadGateway'; case 503: return 'http.StatusServiceUnavailable'; case 504: return 'http.StatusGatewayTimeout '; case 505: return 'http.StatusHTTPVersionNotSupported'; case 506: return 'http.StatusVariantAlsoNegotiates'; case 507: return 'http.StatusInsufficientStorage'; case 508: return 'http.StatusLoopDetected'; case 510: return 'http.StatusNotExtended'; case 511: return 'http.StatusNetworkAuthenticationRequired'; default: throw new CodegenError('InternalError', `unhandled status code ${statusCode}`); } } export function formatCommentAsBulletItem(prefix, docs) { // first create the comment block. note that it can be multi-line depending on length: // // some comment first line // and it finishes here. let description = formatDocCommentWithPrefix(prefix, docs); if (description.length === 0) { return ''; } // transform the above to look like this: // // - some comment first line // and it finishes here. const chunks = description.split('\n'); for (let i = 0; i < chunks.length; ++i) { if (i === 0) { chunks[i] = chunks[i].replace('// ', '// - '); } else { chunks[i] = chunks[i].replace('// ', '// '); } } return chunks.join('\n'); } // conditionally returns a doc comment on an entity that requires a prefix. // e.g.: // {Prefix} - {docs.summary} // // {docs.description} export function formatDocCommentWithPrefix(prefix, docs) { if (!docs.summary && !docs.description) { return ''; } let docComment = ''; if (docs.summary) { docComment = `${comment(`${prefix} - ${docs.summary}`, '//', undefined, commentLength)}\n`; } if (docs.description) { let description = docs.description; if (docs.summary) { docComment += '//\n'; } else { // only apply the prefix to the description if there was no summary description = `${prefix} - ${description}`; } docComment += `${comment(`${description}`, '//', undefined, commentLength)}\n`; } return docComment; } // conditionally returns a doc comment // {docs.summary} // // {docs.description} export function formatDocComment(docs) { if (!docs.summary && !docs.description) { return ''; } let docComment = ''; if (docs.summary) { docComment = `${comment(docs.summary, '//', undefined, commentLength)}\n`; } if (docs.description) { if (docs.summary) { docComment += '//\n'; } docComment += `${comment(docs.description, '//', undefined, commentLength)}\n`; } return docComment; } export function getParentImport(codeModel) { const clientPkg = codeModel.packageName; if (codeModel.options.module) { return codeModel.options.module.name; } else if (codeModel.options.containingModule) { return codeModel.options.containingModule + '/' + clientPkg; } else { throw new CodegenError('InvalidArgument', 'unable to determine containing module for fakes. specify either the module or containing-module switch'); } } export function getBitSizeForNumber(intSize) { switch (intSize) { case 'int8': return '8'; case 'int16': return '16'; case 'int32': case 'float32': return '32'; case 'int64': case 'float64': return '64'; } } // returns the underlying map/slice element/value type // if item isn't a map or slice, item is returned export function recursiveUnwrapMapSlice(item) { if (go.isMapType(item)) { return recursiveUnwrapMapSlice(item.valueType); } else if (go.isSliceType(item)) { return recursiveUnwrapMapSlice(item.elementType); } return item; } // returns a * for optional params export function star(param) { return go.isRequiredParameter(param) || param.byValue ? '' : '*'; } // used by getSerDeFormat to cache results const serDeFormatCache = new Map(); // returns the wire format for the named model. // at present this assumes the formats to be mutually exclusive. export function getSerDeFormat(model, codeModel) { let serDeFormat = serDeFormatCache.get(model.name); if (serDeFormat) { return serDeFormat; } // for model-only builds we assume the format to be JSON if (codeModel.clients.length === 0) { return 'JSON'; } // recursively walks the fields in model, updating serDeFormatCache with the model name and specified format const recursiveWalkModelFields = function (type, serDeFormat) { type = recursiveUnwrapMapSlice(type); if (go.isInterfaceType(type)) { recursiveWalkModelFields(type.rootType, serDeFormat); for (const possibleType of type.possibleTypes) { recursiveWalkModelFields(possibleType, serDeFormat); } } else if (go.isPolymorphicType(type) || go.isModelType(type)) { if (serDeFormatCache.has(type.name)) { // we've already processed this type, don't do it again return; } serDeFormatCache.set(type.name, serDeFormat); for (const field of type.fields) { const fieldType = recursiveUnwrapMapSlice(field.type); recursiveWalkModelFields(fieldType, serDeFormat); } } }; // walk the methods, indexing the model formats for (const client of codeModel.clients) { for (const method of client.methods) { for (const param of method.parameters) { if (!go.isBodyParameter(param) || (param.bodyFormat !== 'JSON' && param.bodyFormat !== 'XML')) { continue; } recursiveWalkModelFields(param.type, param.bodyFormat); } if (method.responseEnvelope.result) { const resultType = method.responseEnvelope.result; if (go.isAnyResult(resultType) && (resultType.format === 'JSON' || resultType.format === 'XML')) { for (const type of Object.values(resultType.httpStatusCodeType)) { recursiveWalkModelFields(type, resultType.format); } } else if (go.isPolymorphicResult(resultType)) { recursiveWalkModelFields(resultType.interfaceType, resultType.format); } else if (go.isModelResult(resultType)) { recursiveWalkModelFields(resultType.modelType, resultType.format); } else if (go.isMonomorphicResult(resultType) && (resultType.format === 'JSON' || resultType.format === 'XML')) { recursiveWalkModelFields(resultType.monomorphicType, resultType.format); } } } } serDeFormat = serDeFormatCache.get(model.name); if (!serDeFormat) { // if we get here there are two possibilities // - we have a bug in the above indexing // - the model type is unreferenced by any operation // // while the former is possible, the latter has the potential to be real. // regardless of the cause, we will just assume the format to be JSON. serDeFormat = 'JSON'; } return serDeFormat; } // return combined client parameters for all the clients export function getAllClientParameters(codeModel) { const allClientParams = new Array(); for (const clients of codeModel.clients) { for (const clientParam of values(clients.parameters)) { if (values(allClientParams).where(param => param.name === clientParam.name).any()) { continue; } allClientParams.push(clientParam); } } allClientParams.sort(sortParametersByRequired); return allClientParams; } // returns common client parameters for all the clients export function getCommonClientParameters(codeModel) { const paramCount = new Map(); let numClients = 0; // track client count since we might skip some for (const clients of codeModel.clients) { // special cases: some ARM clients always don't contain any parameters (OperationsClient will be depracated in the future) if (codeModel.type === 'azure-arm' && clients.name.match(/^OperationsClient$/)) { continue; } ++numClients; for (const clientParam of values(clients.parameters)) { let entry = paramCount.get(clientParam.name); if (!entry) { entry = { uses: 0, param: clientParam }; paramCount.set(clientParam.name, entry); } ++entry.uses; } } // for each param, if its usage count is equal to the // number of clients, then it's common to all clients const commonClientParams = new Array(); for (const entry of paramCount.values()) { if (entry.uses === numClients) { commonClientParams.push(entry.param); } } return commonClientParams.sort(sortParametersByRequired); } //# sourceMappingURL=helpers.js.map