UNPKG

@autorest/go

Version:
572 lines 31.1 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 './helpers.js'; import { ImportManager } from './imports.js'; import { fixUpMethodName } from './operations.js'; import { CodegenError } from './errors.js'; // represents the generated content for an example export class ExampleContent { name; content; constructor(name, content) { this.name = name; this.content = content; } } /** * Creates the content for all the *_example_test.go files. * * @param pkg contains the package content * @param target the codegen target for the module * @param options the emitter options * @returns the text for the files or the empty string */ export function generateExamples(pkg, target, options) { // generate examples const examples = new Array(); if (pkg.src.clients.length === 0) { return examples; } const azureARM = target === 'azure-arm'; for (const client of pkg.src.clients) { // client must be constructable to create a sample if (client.instance?.kind != 'constructable') { continue; } const imports = new ImportManager(pkg); // the list of packages to import if (client.methods.length > 0) { // add standard imports for clients with methods. // clients that are purely hierarchical (i.e. having no APIs) won't need them. imports.add('context'); imports.add('log'); imports.add('github.com/Azure/azure-sdk-for-go/sdk/azidentity'); imports.addForPkg(pkg.src); } let clientFactoryParams = new Array(); if (options.factoryGatherAllParams) { clientFactoryParams = helpers.getAllClientParameters(pkg.src, target); } else { clientFactoryParams = helpers.getCommonClientParameters(pkg.src, target); } const clientFactoryParamsMap = new Map(); for (const param of clientFactoryParams) { clientFactoryParamsMap.set(param.name, param); } let exampleText = ''; for (const method of client.methods) { for (const example of method.examples) { const indent = new helpers.Indentation(); // function signature exampleText += `// Generated from example definition: ${example.filePath}\n`; const exampleFuncNamePrefix = method.examples.length > 1 ? `_${helpers.camelCase(example.name)}` : ''; exampleText += `func Example${client.name}_${fixUpMethodName(method)}${exampleFuncNamePrefix}() {\n`; // create credential exampleText += `${indent.get()}cred, err := azidentity.NewDefaultAzureCredential(nil)\n`; exampleText += `${indent.get()}if err != nil {\n`; exampleText += `${indent.push().get()}log.Fatalf("failed to obtain a credential: %v", err)\n`; exampleText += `${indent.pop().get()}}\n`; // create context exampleText += `${indent.get()}ctx := context.Background()\n`; // create client const clientParameters = []; for (const param of method.parameters) { if (param.location === 'client') { if (go.isLiteralParameter(param.style)) { continue; } const clientParam = example.parameters.find((p) => p.parameter.name === param.name); if (clientParam) { clientParameters.push(clientParam); } } } // TODO: client optional parameters let clientRef = ''; if (azureARM) { // since not all operation has all the client factory required parameters, we need to fake for the missing ones const clientFactoryParamsExample = []; for (const clientParam of clientFactoryParams) { const clientFactoryParam = clientParameters.find((p) => p.parameter.name === clientParam.name); if (clientFactoryParam) { clientFactoryParamsExample.push(clientFactoryParam); } else { clientFactoryParamsExample.push({ parameter: clientParam, value: generateFakeExample(clientParam.type, clientParam.name) }); } } exampleText += `${indent.get()}clientFactory, err := ${go.getPackageName(pkg.src)}.NewClientFactory(${clientFactoryParamsExample.map((p) => getExampleValue(pkg, p.value, '\t', imports, p.parameter.byValue)).join(', ')}, nil)\n`; exampleText += `${indent.get()}if err != nil {\n`; exampleText += `${indent.push().get()}log.Fatalf("failed to create client: %v", err)\n`; exampleText += `${indent.pop().get()}}\n`; clientRef = `clientFactory.${client.instance.constructors[0].name}(`; // since not all operations have all the client constructor required parameters, we need to generate fake values for the missing ones const clientPrivateParameters = []; for (const ctorParam of client.instance.constructors[0].parameters) { if (clientFactoryParamsMap.has(ctorParam.name) || go.isAPIVersionParameter(ctorParam)) { continue; } const existingParam = clientParameters.find((p) => p.parameter.name === ctorParam.name); if (existingParam) { clientPrivateParameters.push(existingParam); } else { clientPrivateParameters.push({ parameter: ctorParam, value: generateFakeExample(ctorParam.type, ctorParam.name) }); } } if (clientPrivateParameters.length > 0) { clientRef += `${clientPrivateParameters.map((p) => getExampleValue(pkg, p.value, '\t', imports, p.parameter.byValue).slice(1)).join(', ')}`; } clientRef += `)`; } else { exampleText += `${indent.get()}client, err := ${go.getPackageName(client.instance.constructors[0].pkg)}.${client.instance.constructors[0].name}(${clientParameters.map((p) => getExampleValue(pkg, p.value, '\t', imports, p.parameter.byValue).slice(1)).join(', ')}, cred, nil)\n`; exampleText += `${indent.get()}if err != nil {\n`; exampleText += `${indent.push().get()}log.Fatalf("failed to create client: %v", err)\n`; exampleText += `${indent.pop().get()}}\n`; clientRef = 'client'; } // call method, using getMethodParameters for correct ordering (including required param groups) const renderedParams = []; for (const methodParam of helpers.getMethodParameters(method)) { if (methodParam.kind === 'paramGroup') { if (methodParam === method.optionalParamsGroup) continue; imports.addForPkg(methodParam.pkg); const fieldTexts = []; for (const groupParam of methodParam.params) { if (!shouldRenderParam(groupParam, example)) continue; fieldTexts.push(`${naming.capitalize(groupParam.name)}: ${getParamExampleValue(pkg, groupParam, example, imports)}`); } renderedParams.push(`${go.getPackageName(methodParam.pkg)}.${methodParam.groupName}{${fieldTexts.join(', ')}}`); } else { if (!shouldRenderParam(methodParam, example)) continue; renderedParams.push(getParamExampleValue(pkg, methodParam, example, imports)); } } const methodOptionalParameters = example.optionalParamsGroup.filter((p) => p.parameter.location === 'method'); const checkResponse = example.responseEnvelope !== undefined; let methodOptionalParametersText = 'nil'; if (methodOptionalParameters.length > 0) { methodOptionalParametersText = `&${go.getPackageName(method.optionalParamsGroup.pkg)}.${method.optionalParamsGroup.groupName}{\n`; methodOptionalParametersText += methodOptionalParameters .map((p) => `${naming.capitalize(p.parameter.name)}: ${getExampleValue(pkg, p.value, '\t', imports, isParamByValue(p)).slice(1)}`) .join(',\n'); methodOptionalParametersText += `}`; } switch (method.kind) { case 'lroMethod': case 'lroPageableMethod': exampleText += `${indent.get()}poller, err := ${clientRef}.${fixUpMethodName(method)}(ctx, ${renderedParams.join(', ')}${renderedParams.length > 0 ? ', ' : ''}${methodOptionalParametersText.split('\n').join('\n' + indent.get())})\n`; exampleText += `${indent.get()}if err != nil {\n`; exampleText += `${indent.push().get()}log.Fatalf("failed to finish the request: %v", err)\n`; exampleText += `${indent.pop().get()}}\n`; exampleText += `${indent.get()}${checkResponse ? 'res' : '_'}, err ${checkResponse ? ':=' : '='} poller.PollUntilDone(ctx, nil)\n`; exampleText += `${indent.get()}if err != nil {\n`; exampleText += `${indent.push().get()}log.Fatalf("failed to poll the result: %v", err)\n`; exampleText += `${indent.pop().get()}}\n`; break; case 'method': exampleText += `${indent.get()}${checkResponse ? 'res' : '_'}, err ${checkResponse ? ':=' : '='} ${clientRef}.${fixUpMethodName(method)}(ctx, ${renderedParams.join(', ')}${renderedParams.length > 0 ? ', ' : ''}${methodOptionalParametersText.split('\n').join('\n' + indent.get())})\n`; exampleText += `${indent.get()}if err != nil {\n`; exampleText += `${indent.push().get()}log.Fatalf("failed to finish the request: %v", err)\n`; exampleText += `${indent.pop().get()}}\n`; break; case 'pageableMethod': exampleText += `${indent.get()}pager := ${clientRef}.${fixUpMethodName(method)}(${renderedParams.join(', ')}${renderedParams.length > 0 ? ', ' : ''}${methodOptionalParametersText.split('\n').join('\n' + indent.get())})\n`; break; default: method; } // check response if ((method.kind === 'lroPageableMethod' || method.kind === 'pageableMethod') && example.responseEnvelope) { let resultName = 'pager'; if (method.kind === 'lroPageableMethod') { resultName = 'res'; } const itemType = method.returns.result.modelType.fields.find((f) => f.type.kind === 'slice'); exampleText += `${indent.get()}for ${resultName}.More() {\n`; exampleText += `${indent.push().get()}page, err := ${resultName}.NextPage(ctx)\n`; exampleText += `${indent.get()}if err != nil {\n`; exampleText += `${indent.push().get()}log.Fatalf("failed to advance page: %v", err)\n`; exampleText += `${indent.pop().get()}}\n`; exampleText += `${indent.get()}for _, v := range page.${itemType.name} {\n`; exampleText += `${indent.push().get()}// You could use page here. We use blank identifier for just demo purposes.\n`; exampleText += `${indent.get()}_ = v\n`; exampleText += `${indent.pop().get()}}\n`; exampleText += `${indent.get()}// If the HTTP response code is 200 as defined in example definition, your page structure would look as follows. Please pay attention that all the values in the output are fake values for just demo purposes.\n`; exampleText += `${indent.get()}// page = ${go.getPackageName(example.responseEnvelope.response.method.receiver.type.pkg)}.${example.responseEnvelope.response.name}{\n`; for (const header of example.responseEnvelope.headers ?? []) { exampleText += `${indent.get()}// \t${header.header.fieldName}: ${getExampleValue(pkg, header.value, '', undefined, header.header.byValue) .split('\n') .join(`\n${indent.get()}// \t`)},\n`; } exampleText += `${indent.get()}// \t${example.responseEnvelope.result.type.name}: ${getExampleValue(pkg, example.responseEnvelope.result, '', undefined, true).split('\n').join(`\n${indent.get()}// \t`)},\n`; exampleText += `${indent.get()}// }\n`; exampleText += `${indent.pop().get()}}\n`; } else if (example.responseEnvelope) { // if has fieldName, then the result is not a model type const fieldName = method.returns.result?.fieldName; exampleText += `${indent.get()}// You could use response here. We use blank identifier for just demo purposes.\n`; exampleText += `${indent.get()}_ = res\n`; exampleText += `${indent.get()}// If the HTTP response code is 200 as defined in example definition, your response structure would look as follows. Please pay attention that all the values in the output are fake values for just demo purposes.\n`; exampleText += `${indent.get()}// res = ${go.getPackageName(example.responseEnvelope.response.method.receiver.type.pkg)}.${example.responseEnvelope.response.name}{\n`; for (const header of example.responseEnvelope?.headers ?? []) { exampleText += `${indent.get()}// \t${header.header.fieldName}: ${getExampleValue(pkg, header.value, '', undefined, header.header.byValue) .split('\n') .join(`\n${indent.get()}// \t`)},\n`; } if (example.responseEnvelope?.result) { // modelResult and polymorphicResult are anonymously embedded by value in the response struct. // monomorphicResult has an explicit byValue property. all other result types default to by value. let resultByValue = true; let resultFieldName = fieldName ? fieldName : (example.responseEnvelope?.result.type).name; if (method.returns.result?.kind === 'monomorphicResult') { resultByValue = method.returns.result.byValue; } else if (method.returns.result?.kind === 'polymorphicResult') { resultFieldName = method.returns.result.interface.name; resultByValue = false; } exampleText += `${indent.get()}// \t${resultFieldName}: ${getExampleValue(pkg, example.responseEnvelope.result, '', undefined, resultByValue).split('\n').join(`\n${indent.get()}// \t`)},\n`; } exampleText += `${indent.get()}// }\n`; } exampleText += `}\n\n`; } } // if no example, then do not generate example file if (exampleText === '') continue; // stitch it all together let text = helpers.contentPreamble(pkg); text += imports.text(); text += exampleText; examples.push(new ExampleContent(client.name, text)); } return examples; } function getExampleValue(pkg, example, indent, imports, byValue = false, inArray = false) { switch (example.kind) { case 'string': { let exampleText = `"${escapeString(example.value)}"`; if (example.type.kind === 'constant') { exampleText = getConstantValue(pkg, example.type, example.value); } else if (example.type.kind === 'time') { exampleText = getTimeValue(example.type, example.value, imports); } else if (example.type.kind === 'encodedBytes') { exampleText = `[]byte("${escapeString(example.value)}")`; } else if (example.type.kind === 'literal' && example.type.type.kind === 'constant') { exampleText = getConstantValue(pkg, example.type.type, example.type.literal.value); } else if (example.type.kind === 'etag') { imports?.add(example.type.module); exampleText = `${go.getTypeDeclaration(example.type, pkg)}("${escapeString(example.value)}")`; } else if (example.type.kind === 'scalar' && example.type.type === 'byte') { exampleText = `io.NopCloser(bytes.NewReader([]byte("${escapeString(example.value)}")))`; } else if (example.type.kind === 'readSeekCloser') { imports?.add('bytes'); imports?.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming'); exampleText = `streaming.NopCloser(bytes.NewReader([]byte("${escapeString(example.value)}")))`; } return `${indent}${getPointerValue(example.type, exampleText, byValue, imports)}`; } case 'number': { let exampleText = `${example.value}`; switch (example.type.kind) { case 'constant': exampleText = `${indent}${getConstantValue(pkg, example.type, example.value)}`; break; case 'time': exampleText = getTimeValue(example.type, example.value, imports); break; } return `${indent}${getPointerValue(example.type, exampleText, byValue, imports)}`; } case 'boolean': { let exampleText = `${example.value}`; if (example.type.kind === 'constant') { exampleText = `${indent}${getConstantValue(pkg, example.type, example.value)}`; } return `${indent}${getPointerValue(example.type, exampleText, byValue, imports)}`; } case 'null': return `${indent}nil`; case 'any': return jsonToGo(example.value, indent); case 'array': { const isElementByValue = example.type.elementTypeByValue; // if polymorphic, need to add type name in array, so inArray will be set to false // if other case, no need to add type name in array, so inArray will be set to true const isElementPolymorphic = example.type.elementType.kind === 'interface'; let exampleText = `${indent}${getRef(byValue)}${go.getTypeDeclaration(example.type, pkg)}{\n`; for (const element of example.value) { exampleText += `${getExampleValue(pkg, element, indent + '\t', imports, isElementByValue && !isElementPolymorphic, !isElementPolymorphic)},\n`; } exampleText += `${indent}}`; return exampleText; } case 'dictionary': { let exampleText = `${indent}${getRef(byValue)}${go.getTypeDeclaration(example.type, pkg)}{\n`; const isValueByValue = example.type.valueTypeByValue; const isValuePolymorphic = example.type.valueType.kind === 'interface'; for (const key in example.value) { exampleText += `${indent}\t"${key}": ${getExampleValue(pkg, example.value[key], indent + '\t', imports, isValueByValue && !isValuePolymorphic).slice(indent.length + 1)},\n`; } exampleText += `${indent}}`; return exampleText; } case 'model': { let exampleText = `${indent}${getRef(byValue)}${go.getTypeDeclaration(example.type, pkg)}{\n`; if (inArray) { exampleText = `${indent}{\n`; } for (const field in example.value) { const goField = example.type.fields.find((f) => f.name === field); const isFieldByValue = goField.byValue ?? false; const isFieldPolymorphic = goField.type.kind === 'interface'; exampleText += `${indent}\t${field}: ${getExampleValue(pkg, example.value[field], indent + '\t', imports, isFieldByValue && !isFieldPolymorphic).slice(indent.length + 1)},\n`; } if (example.additionalProperties) { const additionalPropertiesField = example.type.fields.find((f) => f.annotations.isAdditionalProperties); if (additionalPropertiesField.type.kind !== 'map') { throw new CodegenError('InternalError', `additional properties field type should be map type`); } const isAdditionalPropertiesFieldByValue = additionalPropertiesField.type.valueTypeByValue ?? false; const isAdditionalPropertiesPolymorphic = additionalPropertiesField.type.valueType.kind === 'interface'; exampleText += `${indent}\t${additionalPropertiesField.name}: ${getRef(additionalPropertiesField.byValue)}${go.getTypeDeclaration(additionalPropertiesField.type, pkg)}{\n`; for (const key in example.additionalProperties) { exampleText += `${indent}\t"${key}": ${getExampleValue(pkg, example.additionalProperties[key], indent + '\t', imports, isAdditionalPropertiesFieldByValue && !isAdditionalPropertiesPolymorphic).slice(indent.length + 1)},\n`; } exampleText += `${indent}},\n`; } exampleText += `${indent}}`; return exampleText; } case 'tokenCredential': return example.value; } } function getRef(byValue) { return byValue ? '' : '&'; } function getConstantValue(pkg, type, value) { for (const constantValue of type.values) { if (constantValue.value === value) { return go.getTypeDeclaration(constantValue, pkg); } } switch (type.type) { case 'string': return `${go.getTypeDeclaration(type, pkg)}("${value}")`; default: return `${go.getTypeDeclaration(type, pkg)}(${value})`; } } function getTimeValue(type, value, imports) { const formatMap = { PlainDate: helpers.plainDateFormat, PlainTime: helpers.plainTimeFormat, RFC1123: helpers.RFC1123Format, RFC3339: helpers.RFC3339Format, }; if (type.format in formatMap) { imports?.add('time'); const format = formatMap[type.format]; return `func() time.Time { t, _ := time.Parse(${format}, "${value}"); return t}()`; } else { imports?.add('strconv'); imports?.add('time'); return `func() time.Time { t, _ := strconv.ParseInt(${value}, 10, 64); return time.Unix(t, 0).UTC()}()`; } } function getPointerValue(type, valueString, byValue, imports) { if (byValue) { return valueString; } switch (type.kind) { case 'any': case 'constant': case 'constantDef': case 'etag': case 'string': case 'time': if (imports) imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/to'); return `to.Ptr(${valueString})`; case 'literal': // unwrap the literal and delegate to the inner type's case return getPointerValue(type.type, valueString, byValue, imports); case 'scalar': { if (type.type === 'byte') { return valueString; } let prtType = ''; switch (type.type) { case `bool`: case `rune`: prtType = 'Ptr'; break; default: prtType = `Ptr[${type.type}]`; } if (imports) imports.add('github.com/Azure/azure-sdk-for-go/sdk/azcore/to'); return `to.${prtType}(${valueString})`; } case 'readSeekCloser': return valueString; default: return `&${valueString}`; } } function jsonToGo(value, indent) { if (typeof value === 'string') { return `${indent}"${escapeString(value)}"`; } else if (typeof value === 'number' || typeof value === 'bigint') { return `${indent}${value}`; } else if (typeof value === 'boolean') { return `${indent}${value}`; } else if (typeof value === 'undefined') { return `${indent}nil`; } else if (typeof value === 'object') { if (value === null) { return `${indent}nil`; } else if (Array.isArray(value)) { let result = `${indent}[]any{\n`; for (const item of value) { result += `${jsonToGo(item, indent + '\t')},\n`; } result += `${indent}}`; return result; } else { let result = `${indent}map[string]any{\n`; for (const key in value) { result += `${indent}\t"${key}": ${jsonToGo(value[key], indent + '\t').slice(indent.length + 1)},\n`; } result += `${indent}}`; return result; } } return ''; } function generateFakeExample(goType, name) { switch (goType.kind) { case 'any': return new go.NullExample(goType); case 'constant': switch (goType.type) { case 'bool': return new go.BooleanExample(goType.values[0].value, goType); case 'string': return new go.StringExample(goType.values[0].value, goType); default: return new go.NumberExample(goType.values[0].value, goType); } case 'literal': return new go.StringExample(goType.literal, goType); case 'scalar': switch (goType.type) { case 'bool': return new go.BooleanExample(false, goType); case 'byte': case 'rune': return new go.StringExample(`<${name ?? 'test'}>`, goType); default: return new go.NumberExample(0, goType); } case 'string': return new go.StringExample(`<${name ?? 'test'}>`, goType); case 'tokenCredential': // we hard code the credential var name to cred return new go.TokenCredentialExample('cred'); case 'encodedBytes': return new go.StringExample(`<${name ?? 'test'}>`, goType); case 'time': // use a placeholder date value for time types return new go.StringExample('2006-01-02T15:04:05Z', goType); case 'etag': return new go.StringExample(`<${name ?? 'etag'}>`, goType); case 'model': case 'polymorphicModel': // return an empty struct example for model types return new go.StructExample(goType); case 'slice': // return an empty array example for slice types return new go.ArrayExample(goType); case 'map': // return an empty map example for map types return new go.DictionaryExample(goType); case 'interface': // for interface types, use the root type (which is a PolymorphicModel) to create an example return new go.StructExample(goType.rootType); default: throw new CodegenError('InternalError', `unhandled fake example kind ${goType.kind}`); } } function escapeString(str) { return str.split('\\').join('\\\\').split('"').join('\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r'); } /** * returns true if the parameter's example value should be passed by value, * false if it should be passed by reference. For interface parameters, * it returns false when passing concrete types to ensure the example value * is passed by reference for Go interface satisfaction via pointer receivers, * even when the parameter style is byValue. * @param p the parameter example to check * @returns true if the parameter's example value should be passed by value, false if it should be passed by reference */ function isParamByValue(p) { switch (p.parameter.type.kind) { case 'interface': return p.value.kind === 'null'; default: return p.parameter.byValue; } } /** * Returns true if the parameter should be rendered in the example output. * Skips literal parameters and optional parameters without an example value. */ function shouldRenderParam(param, example) { if (go.isLiteralParameter(param.style)) return false; if (example.parameters.find((p) => p.parameter.name === param.name)) return true; return go.isRequiredParameter(param.style); } /** * Gets the example value text for a parameter. * Uses the example value if available, otherwise generates a fake value. * Callers should check shouldRenderParam before calling this. */ function getParamExampleValue(pkg, param, example, imports) { const paramExample = example.parameters.find((p) => p.parameter.name === param.name); if (paramExample) { return getExampleValue(pkg, paramExample.value, '\t', imports, isParamByValue(paramExample)).slice(1); } const fakeValue = generateFakeExample(param.type, param.name); return getExampleValue(pkg, fakeValue, '\t', imports, param.byValue).slice(1); } //# sourceMappingURL=example.js.map