powershell/cmdlets/class.ts (1,929 lines of code) (raw):
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const ejs = require('ejs');
import { Schema as NewSchema, SchemaType, ArraySchema, SchemaResponse, HttpParameter, ObjectSchema, BinaryResponse, DictionarySchema, ChoiceSchema, SealedChoiceSchema, Response, Operation, isObjectSchema } from '@autorest/codemodel';
import { command, getAllProperties, JsonType, http, getAllPublicVirtualProperties, getVirtualPropertyFromPropertyName, ParameterLocation, getAllVirtualProperties, VirtualParameter, VirtualProperty } from '@azure-tools/codemodel-v3';
import { CommandOperation, isWritableCmdlet, OperationType, VirtualParameter as NewVirtualParameter, CommandType } from '../utils/command-operation';
import { getAllProperties as NewGetAllProperties, getAllPublicVirtualProperties as NewGetAllPublicVirtualProperties, getVirtualPropertyFromPropertyName as NewGetVirtualPropertyFromPropertyName, VirtualProperty as NewVirtualProperty } from '../utils/schema';
import { escapeString, docComment, serialize, pascalCase, DeepPartial, camelCase } from '@azure-tools/codegen';
import { items, values, Dictionary, length } from '@azure-tools/linq';
import {
Access, Attribute, BackedProperty, Catch, Class, ClassType, Constructor, dotnet, Else, Expression, Finally, ForEach, If, LambdaProperty, LiteralExpression, LocalVariable, Method, Modifier, Namespace, OneOrMoreStatements, Parameter, Property, Return, Statements, BlockStatement, StringExpression,
Switch, System, TerminalCase, toExpression, Try, Using, valueOf, Field, IsNull, Or, ExpressionOrLiteral, TerminalDefaultCase, xmlize, TypeDeclaration, And, IsNotNull, PartialMethod, Case, While, LiteralStatement, Not, ElseIf
} from '@azure-tools/codegen-csharp';
import { ClientRuntime, EventListener, Schema, ArrayOf, EnumImplementation } from '../llcsharp/exports';
import { NullableBoolean, Alias, ArgumentCompleterAttribute, PSArgumentCompleterAttribute, AsyncCommandRuntime, AsyncJob, CmdletAttribute, ErrorCategory, ErrorRecord, Events, InvocationInfo, OutputTypeAttribute, ParameterAttribute, PSCmdlet, PSCredential, SwitchParameter, ValidateNotNull, verbEnum, GeneratedAttribute, DescriptionAttribute, ExternalDocsAttribute, CategoryAttribute, ParameterCategory, ProfileAttribute, PSObject, InternalExportAttribute, ExportAsAttribute, DefaultRunspace, RunspaceFactory, AllowEmptyCollectionAttribute, DoNotExportAttribute, HttpPathAttribute, NotSuggestDefaultParameterSetAttribute } from '../internal/powershell-declarations';
import { State } from '../internal/state';
import { Channel } from '@autorest/extension-base';
import { IParameter } from '@azure-tools/codemodel-v3/dist/code-model/components';
import { IParameter as NewIParameter } from '../utils/components';
import { Variable, Local, ParameterModifier } from '@azure-tools/codegen-csharp';
import { getVirtualPropertyName } from '../llcsharp/model/model-class';
import { HandlerDirective } from '../plugins/modifiers-v2';
import { getChildResourceNameFromPath, getResourceNameFromPath } from '../utils/resourceName';
import { OperationParameter } from '../llcsharp/operation/parameter';
import { get } from 'http';
import { hasValidBodyParameters } from '../utils/http-operation';
import { assert } from 'console';
const PropertiesRequiringNew = new Set(['Host', 'Events']);
const Verbs = {
Common: 'global::System.Management.Automation.VerbsCommon',
Data: 'global::System.Management.Automation.VerbsData',
Lifecycle: 'global::System.Management.Automation.VerbsLifecycle',
Diagnostic: 'global::System.Management.Automation.VerbsDiagnostic',
Communications: 'global::System.Management.Automation.VerbsCommunications',
Security: 'global::System.Management.Automation.VerbsSecurity',
Other: 'global::System.Management.Automation.VerbsOther'
};
const category: { [verb: string]: string } = {
'Add': Verbs.Common,
'Clear': Verbs.Common,
'Close': Verbs.Common,
'Copy': Verbs.Common,
'Enter': Verbs.Common,
'Exit': Verbs.Common,
'Find': Verbs.Common,
'Format': Verbs.Common,
'Get': Verbs.Common,
'Hide': Verbs.Common,
'Join': Verbs.Common,
'Lock': Verbs.Common,
'Move': Verbs.Common,
'New': Verbs.Common,
'Open': Verbs.Common,
'Optimize': Verbs.Common,
'Pop': Verbs.Common,
'Push': Verbs.Common,
'Redo': Verbs.Common,
'Remove': Verbs.Common,
'Rename': Verbs.Common,
'Reset': Verbs.Common,
'Resize': Verbs.Common,
'Search': Verbs.Common,
'Select': Verbs.Common,
'Set': Verbs.Common,
'Show': Verbs.Common,
'Skip': Verbs.Common,
'Split': Verbs.Common,
'Step': Verbs.Common,
'Switch': Verbs.Common,
'Undo': Verbs.Common,
'Unlock': Verbs.Common,
'Watch': Verbs.Common,
'Backup': Verbs.Data,
'Checkpoint': Verbs.Data,
'Compare': Verbs.Data,
'Compress': Verbs.Data,
'Convert': Verbs.Data,
'ConvertFrom': Verbs.Data,
'ConvertTo': Verbs.Data,
'Dismount': Verbs.Data,
'Edit': Verbs.Data,
'Expand': Verbs.Data,
'Export': Verbs.Data,
'Group': Verbs.Data,
'Import': Verbs.Data,
'Initialize': Verbs.Data,
'Limit': Verbs.Data,
'Merge': Verbs.Data,
'Mount': Verbs.Data,
'Out': Verbs.Data,
'Publish': Verbs.Data,
'Restore': Verbs.Data,
'Save': Verbs.Data,
'Sync': Verbs.Data,
'Unpublish': Verbs.Data,
'Update': Verbs.Data,
'Approve': Verbs.Lifecycle,
'Assert': Verbs.Lifecycle,
'Complete': Verbs.Lifecycle,
'Confirm': Verbs.Lifecycle,
'Deny': Verbs.Lifecycle,
'Disable': Verbs.Lifecycle,
'Enable': Verbs.Lifecycle,
'Install': Verbs.Lifecycle,
'Invoke': Verbs.Lifecycle,
'Register': Verbs.Lifecycle,
'Request': Verbs.Lifecycle,
'Restart': Verbs.Lifecycle,
'Resume': Verbs.Lifecycle,
'Start': Verbs.Lifecycle,
'Stop': Verbs.Lifecycle,
'Submit': Verbs.Lifecycle,
'Suspend': Verbs.Lifecycle,
'Uninstall': Verbs.Lifecycle,
'Unregister': Verbs.Lifecycle,
'Wait': Verbs.Lifecycle,
'Debug': Verbs.Diagnostic,
'Measure': Verbs.Diagnostic,
'Ping': Verbs.Diagnostic,
'Repair': Verbs.Diagnostic,
'Resolve': Verbs.Diagnostic,
'Test': Verbs.Diagnostic,
'Trace': Verbs.Diagnostic,
'Connect': Verbs.Communications,
'Disconnect': Verbs.Communications,
'Read': Verbs.Communications,
'Receive': Verbs.Communications,
'Send': Verbs.Communications,
'Write': Verbs.Communications,
'Block': Verbs.Security,
'Grant': Verbs.Security,
'Protect': Verbs.Security,
'Revoke': Verbs.Security,
'Unblock': Verbs.Security,
'Unprotect': Verbs.Security,
'Use': Verbs.Other,
};
export function createStepper(p: Expression) {
return toExpression(`${System.Linq.Enumerable.declaration}.Select<${ClientRuntime.SendAsyncStep.declaration},${ClientRuntime.SendAsyncStep.declaration}>(${p.value}, step =>
(r, c, n) => {
${DefaultRunspace.value} = ${DefaultRunspace.value} ?? ${RunspaceFactory.declaration}.CreateRunspace();
return step(r, c, n);
})`);
}
export function addCompleterInfo(targetProperty: Property, parameter: VirtualParameter) {
if (parameter.completerInfo && parameter.completerInfo.script) {
targetProperty.add(new Attribute(ClientRuntime.CompleterInfoAttribute, {
parameters: [
new LiteralExpression(`\nName = ${new StringExpression(parameter.completerInfo.name || '').value}`),
new LiteralExpression(`\nDescription =${new StringExpression(parameter.completerInfo.description || '').value}`),
new LiteralExpression(`\nScript = ${new StringExpression(parameter.completerInfo.script).value}`)
]
}));
}
}
export function NewAddCompleterInfo(targetProperty: Property, parameter: NewVirtualParameter) {
if (parameter.completerInfo && parameter.completerInfo.script) {
targetProperty.add(new Attribute(ClientRuntime.CompleterInfoAttribute, {
parameters: [
new LiteralExpression(`\nName = ${new StringExpression(parameter.completerInfo.name || '').value}`),
new LiteralExpression(`\nDescription =${new StringExpression(parameter.completerInfo.description || '').value}`),
new LiteralExpression(`\nScript = ${new StringExpression(parameter.completerInfo.script).value}`)
]
}));
}
}
export function isEnumImplementation(schema: NewSchema | undefined): boolean {
return (schema?.type === SchemaType.SealedChoice && !schema.language.default.skip) ||
(schema?.extensions && schema.extensions['x-ms-enum']);
}
export function addPSArgumentCompleterAttribute(targetProperty: Property, parameterSchema: any) {
const enumValues = values(parameterSchema.language.csharp.enum.values).select(v => `"${(<string>(<any>v).value)}"`).toArray().join(', ');
targetProperty.add(new Attribute(PSArgumentCompleterAttribute, { parameters: [`${enumValues}`] }));
}
export function addParameterBreakingChange(targetProperty: Property, parameter: any) {
if (parameter.breakingChange) {
const parameters = [];
parameters.push(`"${parameter.breakingChange.parameterName}"`);
if (!parameter.breakingChange.deprecateByVersion || !parameter.breakingChange.deprecateByAzVersion) {
throw new Error(`breakingChange.deprecateByVersion and breakingChange.deprecateByAzVersion must be set for ${parameter.name}`);
}
parameters.push(`"${parameter.breakingChange.deprecateByAzVersion}"`);
parameters.push(`"${parameter.breakingChange.deprecateByVersion}"`);
if (parameter.breakingChange.changeInEfectByDate) parameters.push(`"${parameter.breakingChange.changeInEfectByDate}"`);
if (parameter.breakingChange.replacement) parameters.push(`ReplaceMentCmdletParameterName="${parameter.breakingChange.replacement}"`);
if (parameter.breakingChange.isBecomingMandatory) parameters.push(`IsBecomingMandatory=${parameter.breakingChange.isBecomingMandatory}`);
if (parameter.breakingChange.changeDescription) parameters.push(`ChangeDescription="${parameter.breakingChange.changeDescription}"`);
if (parameter.breakingChange.newParameterType) {
// If old type is set in directive, use it, otherwise try to get the type from the schema
if (parameter.breakingChange.oldParamaterType) {
parameters.push(`OldParamaterType="${parameter.breakingChange.oldParamaterType}"`);
} else {
// for primitive types, should use name, otherwise use fullname which contains namespace
parameters.push(`OldParamaterType="${parameter.schema.language.csharp.fullname.startsWith('<') ? parameter.schema.language.csharp.name : parameter.schema.language.csharp.fullname}"`);
}
parameters.push(`NewParameterType="${parameter.breakingChange.newParameterType}"`);
}
targetProperty.add(new Attribute(ClientRuntime.ParameterBreakingChangeAttribute, {
parameters: parameters
}));
}
}
export function addParameterPreviewMessage(targetProperty: Property, parameter: any) {
if (parameter.previewAnnouncement) {
const parameters = [];
parameters.push(`"${parameter.previewAnnouncement.previewMessage}"`);
if (parameter.previewAnnouncement.estimatedGaDate) parameters.push(`"${parameter.previewAnnouncement.estimatedGaDate}"`);
targetProperty.add(new Attribute(ClientRuntime.PreviewMessageAttribute, { parameters: parameters }));
}
}
export function addDefaultInfo(targetProperty: Property, parameter: any) {
if (parameter.defaultInfo && parameter.defaultInfo.script) {
targetProperty.add(new Attribute(ClientRuntime.DefaultInfoAttribute, {
parameters: [
new LiteralExpression(`\nName = ${new StringExpression(parameter.defaultInfo.name || '').value}`),
new LiteralExpression(`\nDescription =${new StringExpression(parameter.defaultInfo.description || '').value}`),
new LiteralExpression(`\nScript = ${new StringExpression(parameter.defaultInfo.script).value}`),
new LiteralExpression(`\nSetCondition = ${new StringExpression(parameter.defaultInfo['set-condition'] || '').value}`)
]
}));
}
}
export function addInfoAttribute(targetProperty: Property, pType: TypeDeclaration, isRequired: boolean, isReadOnly: boolean, description: string, serializedName: string) {
let pt = <any>pType;
while (pt.elementType) {
switch (pt.elementType.schema.type) {
case JsonType.Object:
if (pt.elementType.schema.details.csharp.interfaceImplementation) {
pt = {
declaration: pt.elementType.schema.details.csharp.interfaceImplementation.declaration,
schema: pt.elementType.schema,
};
} else {
// arg! it's not done yet. Hope it's not polymorphic itself.
pt = {
declaration: `${pt.elementType.schema.details.csharp.namespace}.${pt.elementType.schema.details.csharp.interfaceName}`,
schema: pt.elementType.schema,
};
}
break;
case JsonType.Array:
pt = pt.elementType;
break;
default:
pt = pt.elementType;
break;
}
}
const ptypes = new Array<string>();
if (pt.schema && pt.schema && pt.schema.details.csharp.byReference) {
ptypes.push(`typeof(${pt.schema.details.csharp.namespace}.${pt.schema.details.csharp.interfaceName}_Reference)`);
// do we need polymorphic types for by-resource ? Don't think so.
} else {
ptypes.push(`typeof(${pt.declaration})`);
if (pt.schema && pt.schema.details.csharp.classImplementation && pt.schema.details.csharp.classImplementation.discriminators) {
ptypes.push(...[...pt.schema.details.csharp.classImplementation.discriminators.values()].map(each => `typeof(${each.modelInterface.fullName})`));
}
}
targetProperty.add(new Attribute(ClientRuntime.InfoAttribute, {
parameters: [
new LiteralExpression(`\nRequired = ${isRequired}`),
new LiteralExpression(`\nReadOnly = ${isReadOnly}`),
new LiteralExpression(`\nDescription = ${new StringExpression(description).value}`),
new LiteralExpression(`\nSerializedName = ${new StringExpression(serializedName).value}`),
new LiteralExpression(`\nPossibleTypes = new [] { ${ptypes.join(',').replace(/\?/g, '').replace(/undefined\./g, '')} }`),
]
}));
}
export function NewAddInfoAttribute(targetProperty: Property, pType: TypeDeclaration, isRequired: boolean, isReadOnly: boolean, description: string, serializedName: string) {
let pt = <any>pType;
while (pt.elementType) {
switch (pt.elementType.schema.type) {
case JsonType.Object:
if (pt.elementType.schema.language.csharp.interfaceImplementation) {
pt = {
declaration: pt.elementType.schema.language.csharp.interfaceImplementation.declaration,
schema: pt.elementType.schema,
};
} else {
// arg! it's not done yet. Hope it's not polymorphic itself.
pt = {
declaration: `${pt.elementType.schema.language.csharp.namespace}.${pt.elementType.schema.language.csharp.interfaceName}`,
schema: pt.elementType.schema,
};
}
break;
case JsonType.Array:
pt = pt.elementType;
break;
default:
pt = pt.elementType;
break;
}
}
const ptypes = new Array<string>();
if (pt.schema && pt.schema && pt.schema.language.csharp.byReference) {
ptypes.push(`typeof(${pt.schema.language.csharp.namespace}.${pt.schema.language.csharp.interfaceName}_Reference)`);
// do we need polymorphic types for by-resource ? Don't think so.
} else {
ptypes.push(`typeof(${pt.declaration})`);
if (pt.schema && pt.schema.language.csharp.classImplementation && pt.schema.language.csharp.classImplementation.discriminators) {
ptypes.push(...[...pt.schema.language.csharp.classImplementation.discriminators.values()].map(each => `typeof(${each.modelInterface.fullName})`));
}
}
targetProperty.add(new Attribute(ClientRuntime.InfoAttribute, {
parameters: [
new LiteralExpression(`\nRequired = ${isRequired}`),
new LiteralExpression(`\nReadOnly = ${isReadOnly}`),
new LiteralExpression(`\nDescription = ${new StringExpression(description).value}`),
new LiteralExpression(`\nSerializedName = ${new StringExpression(serializedName).value}`),
new LiteralExpression(`\nPossibleTypes = new [] { ${ptypes.join(',').replace(/\?/g, '').replace(/undefined\./g, '')} }`),
]
}));
}
type operationParameter = {
name: Property;
expression: Property;
parameterLocation: ParameterLocation;
} | {
name: Property;
expression: Expression;
parameterLocation: ParameterLocation;
} | {
name: string | undefined;
expression: LiteralExpression;
parameterLocation: ParameterLocation;
};
type PreProcess = ((cmdlet: CmdletClass, pathParameters: Array<Expression>, nonPathParameters: Array<Expression | Property>, viaIdentity: boolean) => Statements) | undefined;
type ProcessGetResponse = ((cmdlet: CmdletClass) => Statements) | undefined;
export class CmdletClass extends Class {
private cancellationToken!: Expression;
public state: State;
private readonly eventListener: EventListener;
private readonly dropBodyParameter: boolean;
private invocationInfo!: Property;
correlationId!: Field;
processRecordId!: Field;
defaultProfile!: Property;
private readonly thingsToSerialize: Array<Variable>;
private bodyParameter?: Variable;
private bodyParameterInfo?: { type: TypeDeclaration; valueType: TypeDeclaration };
private apProp?: Property;
private operation: CommandOperation;
private debugMode?: boolean;
private variantName: string;
private isViaIdentity: boolean;
private hasStreamOutput: boolean;
private outFileParameter?: Property;
private clientsidePagination?: boolean;
private inputObjectParameterName: string;
private apiCall: Operation;
private operationParameters: Array<operationParameter>;
private responses: Array<Response>;
private callbackMethods: Array<LiteralExpression>;
private serializationMode: LiteralExpression | undefined;
private disableTransformIdentityType?: boolean;
private flattenUserAssignedIdentity?: boolean;
constructor(namespace: Namespace, operation: CommandOperation, state: State, objectInitializer?: DeepPartial<CmdletClass>) {
// generate the 'variant' part of the name
const noun = `${state.project.prefix}${operation.details.csharp.subjectPrefix}${operation.details.csharp.subject}`;
const variantName = `${noun}${operation.details.csharp.name ? `_${operation.details.csharp.name}` : ''}`;
const name = `${operation.details.csharp.verb}${variantName}`;
super(namespace, name, PSCmdlet);
this.dropBodyParameter = operation.details.csharp.dropBodyParameter ? true : false;
this.apply(objectInitializer);
this.operation = operation;
this.apiCall = this.operation.callGraph[this.operation.callGraph.length - 1];
// create the response handlers
this.responses = [...values(this.apiCall.responses), ...values(this.apiCall.exceptions)];
this.callbackMethods = values(this.responses).toArray().map(each => new LiteralExpression(each.language.csharp?.name || ''));
this.operationParameters = [];
this.state = state;
this.thingsToSerialize = [];
this.variantName = variantName;
this.hasStreamOutput = false;
this.interfaces.push(ClientRuntime.IEventListener);
this.interfaces.push(ClientRuntime.IContext);
this.eventListener = new EventListener(new LiteralExpression(`((${ClientRuntime.IEventListener})this)`), true);
this.isViaIdentity = variantName.indexOf('ViaIdentity') > 0;
this.clientsidePagination = !!operation.details.csharp.clientsidePagination && !!this.apiCall.language.csharp?.pageable;
this.inputObjectParameterName = 'InputObject';
}
async init() {
// basic stuff
this.addCommonStuff();
this.description = escapeString(this.operation.details.csharp.description);
const $this = this;
this.disableTransformIdentityType = await $this.state.getValue('disable-transform-identity-type', false);
this.flattenUserAssignedIdentity = await $this.state.getValue('flatten-userassignedidentity', true);
this.add(new Method('BeginProcessing', dotnet.Void, {
override: Modifier.Override,
access: Access.Protected,
description: `(overrides the default BeginProcessing method in ${PSCmdlet})`,
*body() {
if ($this.state.project.azure) {
yield `var telemetryId = ${$this.state.project.serviceNamespace.moduleClass.declaration}.Instance.GetTelemetryId.Invoke();`;
yield If('telemetryId != "" && telemetryId != "internal"', '__correlationId = telemetryId;');
}
yield 'Module.Instance.SetProxyConfiguration(Proxy, ProxyCredential, ProxyUseDefaultCredentials);';
yield If($this.$<Property>('Break'), `${ClientRuntime.AttachDebugger}.Break();`);
yield $this.eventListener.syncSignal(Events.CmdletBeginProcessing);
}
}));
// construct the class
this.NewAddClassAttributes(this.operation, this.variantName);
if (this.hasStreamOutput) {
this.outFileParameter = this.add(new Property('OutFile', System.String, { attributes: [], description: 'Path to write output file to.' }));
this.outFileParameter.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = true', 'HelpMessage = "Path to write output file to"'] }));
this.outFileParameter.add(new Attribute(ValidateNotNull));
this.outFileParameter.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Body`] }));
}
this.NewAddPowershellParameters(this.operation);
// implement IEventListener
this.implementIEventListener();
// implement part of the IContext
this.implementIContext();
// add constructors
this.implementConstructors();
// add callback methods
this.NewImplementResponseMethod();
// processRecord
this.NewImplementProcessRecord();
// find each parameter to the method, and find out where the value is going to come from.
this.operationParameters =
values(this.apiCall.parameters).
// filter out constants and path parameters when using piping for identity
where(each => !(each.language.csharp?.constantValue) && each.language.default?.name !== '$host'/* && (!$this.isViaIdentity || each.in !== ParameterLocation.Path) */).
select(p => {
return {
name: p.language.csharp?.name,
param: values(this.properties).
where(each => each.metadata.parameterDefinition).
first(each => each.metadata.parameterDefinition.language.csharp?.serializedName === p.language.csharp?.serializedName), // xichen: Is it safe enough to use serializedName?
parameterLocation: p.protocol?.http?.in
};
}).
select(each => {
if (each.param) {
const httpParam = (<HttpParameter>(each.param.metadata.parameterDefinition));
if (httpParam.required) {
return {
name: each.param,
expression: each.param,
parameterLocation: each.parameterLocation
};
}
const httpParamTD = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration((<NewSchema>httpParam.schema), httpParam.required, this.state, this.state.project.fixedArray);
return {
name: each.param,
expression: toExpression(`this.InvocationInformation.BoundParameters.ContainsKey("${each.param.value}") ? ${each.param.value} : ${httpParamTD.defaultOfType}`),
parameterLocation: each.parameterLocation
};
}
return { name: each.name, expression: dotnet.Null, parameterLocation: each.parameterLocation };
}).toArray();
this.NewImplementProcessRecordAsync();
if (this.state.project.azure) {
this.NewImplementWriteObject();
}
this.debugMode = await this.state.getValue('debug', false);
// json serialization
this.NewImplementSerialization(this.operation);
for (const prop of this.properties) {
if (prop.name === 'Host') {
prop['new'] = Modifier.New;
}
}
return this;
}
public get headerComment(): string {
const header = super.headerComment;
let ops = '';
for (const httpOperation of values(this.operation.callGraph)) {
const request = httpOperation.requests?.[0];
if (!request) {
continue;
}
const httpMethod = request.protocol.http?.method ?? '';
const httpPath = request.protocol.http?.path ?? '';
ops = `${ops}\n[OpenAPI] ${httpOperation.language.default.name}=>${httpMethod.toUpperCase()}:"${httpPath}"`;
if (this.debugMode) {
// x-ms-metadata seems no longer exists
// const m = (httpOperation.extensions && httpOperation.extensions['x-ms-metadata']) || (httpOperation.pathExtensions ? httpOperation.pathExtensions['x-ms-metadata'] : undefined);
// if (m) {
// ops = `${ops}\n [METADATA]\n${serialize(m)}`;
// }
ops = `${ops}\n [DETAILS]`;
ops = `${ops}\n verb: ${this.operation.details.csharp.verb}`;
ops = `${ops}\n subjectPrefix: ${this.operation.details.csharp.subjectPrefix}`;
ops = `${ops}\n subject: ${this.operation.details.csharp.subject}`;
ops = `${ops}\n variant: ${this.operation.details.csharp.name}`;
}
}
return ops ? `${header}\n${docComment(xmlize('remarks', ops))}` : header;
}
private AddSwitchViewProperty(responseType: TypeDeclaration = dotnet.Object) {
const fieldNames = this.fields.map(f => f.name);
if (!fieldNames.includes('_responseSize')) {
this.add(new Field('_responseSize', dotnet.Int, {
initialValue: '0', description: 'A flag to tell whether it is the first returned object in a call. Zero means no response yet. One means 1 returned object. Two means multiple returned objects in response.', access: Access.Private
}));
}
if (!fieldNames.includes('_firstResponse')) {
this.add(new Field('_firstResponse', responseType, {
initialValue: dotnet.Null, description: 'A buffer to record first returned object in response.', access: Access.Private
}));
}
}
private FlushResponse(singleFlush = true) {
const fieldNames = this.fields.map(f => f.name);
return fieldNames.includes('_responseSize') ?
If('1 ==_responseSize', function* () {
yield '// Flush buffer';
if (singleFlush) {
yield 'WriteObject(_firstResponse);';
} else {
yield 'WriteObject(_firstResponse.AddMultipleTypeNameIntoPSObject());';
}
}) : '';
}
private WriteObjectWithViewControl(valueName: string, isEnumerable = false) {
const $this = this;
const lengthFunc = $this.state.project.fixedArray ? 'Length' : 'Count';
const listToArrayFunc = $this.state.project.fixedArray ? '.ToArray()' : '';
if ($this.state.project.autoSwitchView) {
if (isEnumerable) {
return function* () {
yield If(`null != ${valueName}`, function* () {
yield If(`0 == _responseSize && 1 == ${valueName}.${lengthFunc}`, function* () {
yield `_firstResponse = ${valueName}[0];`;
yield '_responseSize = 1;';
});
yield Else(function* () {
yield $this.FlushResponse(false);
yield 'var values = new System.Collections.Generic.List<System.Management.Automation.PSObject>();';
yield ForEach('value', valueName, function* () {
yield 'values.Add(value.AddMultipleTypeNameIntoPSObject());';
});
yield `WriteObject(values${listToArrayFunc}, true); `;
yield '_responseSize = 2;';
});
});
};
} else {
return function* () {
yield If(`null != ${valueName}`, function* () {
yield If('0 == _responseSize', function* () {
yield `_firstResponse = ${valueName};`;
yield '_responseSize = 1;';
});
yield Else(function* () {
yield $this.FlushResponse(false);
yield `WriteObject(${valueName}.AddMultipleTypeNameIntoPSObject());`;
yield '_responseSize = 2;';
});
});
};
}
} else {
return `WriteObject(${valueName}, ${isEnumerable});`;
}
}
private addCommonStuff() {
// add a private copy of invocation information for our own uses.
const privateInvocationInfo = this.add(new Field('__invocationInfo', InvocationInfo, { description: 'A copy of the Invocation Info (necessary to allow asJob to clone this cmdlet)', access: Access.Private }));
this.invocationInfo = new Property('InvocationInformation', InvocationInfo, { description: 'Accessor for our copy of the InvocationInfo.' });
this.invocationInfo.get = toExpression(`${privateInvocationInfo.value} = ${privateInvocationInfo.value} ?? this.MyInvocation `);
this.invocationInfo.set = new Statements(privateInvocationInfo.assign('value'));
this.add(this.invocationInfo);
if (this.state.project.azure) {
this.correlationId = this.add(new Field('__correlationId', dotnet.String, { initialValue: 'System.Guid.NewGuid().ToString()', description: 'A unique id generatd for the this cmdlet when it is instantiated.', access: Access.Private }));
this.processRecordId = this.add(new Field('__processRecordId', dotnet.String, { description: 'A unique id generatd for the this cmdlet when ProcessRecord() is called.', access: Access.Private }));
}
// switch view property
if (this.state.project.autoSwitchView) {
this.AddSwitchViewProperty(dotnet.Object);
}
// pipeline property
this.add(new Property('Pipeline', ClientRuntime.HttpPipeline, { getAccess: Access.Public, setAccess: Access.Public, description: `The instance of the <see cref="${ClientRuntime.HttpPipeline}" /> that the remote call will use.` }));
// client API property (gs01: fill this in correctly)
const clientAPI = new ClassType(this.state.model.language.csharp?.namespace, this.state.model.language.csharp?.name || '');
this.add(new LambdaProperty('Client', clientAPI, new LiteralExpression(`${this.state.project.serviceNamespace.moduleClass.declaration}.Instance.ClientAPI`), { description: 'The reference to the client API class.' }));
this.add(new Method('StopProcessing', dotnet.Void, { access: Access.Protected, override: Modifier.Override, description: 'Interrupts currently running code within the command.' })).add(function* () {
yield `((${ClientRuntime.IEventListener})this).Cancel();`;
yield 'base.StopProcessing();';
});
const $this = this;
this.add(new Method('EndProcessing', dotnet.Void, { access: Access.Protected, override: Modifier.Override, description: 'Performs clean-up after the command execution' })).add(
function* () {
// gs01: remember what you were doing here to make it so these can be parallelized...
if ($this.state.project.autoSwitchView) {
yield $this.FlushResponse();
}
if (!$this.state.project.azure) {
yield $this.eventListener.syncSignal(Events.CmdletEndProcessing);
}
else {
yield `var telemetryInfo = ${$this.state.project.serviceNamespace.moduleClass.declaration}.Instance.GetTelemetryInfo?.Invoke(__correlationId);`;
yield If('telemetryInfo != null', function* () {
yield 'telemetryInfo.TryGetValue("ShowSecretsWarning", out var showSecretsWarning);';
yield 'telemetryInfo.TryGetValue("SanitizedProperties", out var sanitizedProperties);';
yield 'telemetryInfo.TryGetValue("InvocationName", out var invocationName);';
yield If('showSecretsWarning == "true"', function* () {
yield If('string.IsNullOrEmpty(sanitizedProperties)', 'WriteWarning($"The output of cmdlet {invocationName} may compromise security by showing secrets. Learn more at https://go.microsoft.com/fwlink/?linkid=2258844");');
yield Else('WriteWarning($"The output of cmdlet {invocationName} may compromise security by showing the following secrets: {sanitizedProperties}. Learn more at https://go.microsoft.com/fwlink/?linkid=2258844");');
});
});
}
});
// debugging
const brk = this.add(new Property('Break', SwitchParameter, { attributes: [], description: 'Wait for .NET debugger to attach' }));
brk.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'DontShow = true', 'HelpMessage = "Wait for .NET debugger to attach"'] }));
brk.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Runtime`] }));
// Cmdlet Parameters for pipeline manipulations.
const prepend = this.add(new Property('HttpPipelinePrepend', ClientRuntime.SendAsyncSteps, { attributes: [], description: 'SendAsync Pipeline Steps to be prepended to the front of the pipeline' }));
prepend.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'DontShow = true', 'HelpMessage = "SendAsync Pipeline Steps to be prepended to the front of the pipeline"'] }));
prepend.add(new Attribute(ValidateNotNull));
prepend.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Runtime`] }));
const append = this.add(new Property('HttpPipelineAppend', ClientRuntime.SendAsyncSteps, { attributes: [], description: 'SendAsync Pipeline Steps to be appended to the front of the pipeline' }));
append.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'DontShow = true', 'HelpMessage = "SendAsync Pipeline Steps to be appended to the front of the pipeline"'] }));
append.add(new Attribute(ValidateNotNull));
append.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Runtime`] }));
const proxyCredential = this.add(new Property('ProxyCredential', PSCredential, { attributes: [], description: 'Credentials for a proxy server to use for the remote call' }));
proxyCredential.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'DontShow = true', 'HelpMessage = "Credentials for a proxy server to use for the remote call"'] }));
proxyCredential.add(new Attribute(ValidateNotNull));
proxyCredential.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Runtime`] }));
const useDefaultCreds = this.add(new Property('ProxyUseDefaultCredentials ', SwitchParameter, { attributes: [], description: 'Use the default credentials for the proxy' }));
useDefaultCreds.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'DontShow = true', 'HelpMessage = "Use the default credentials for the proxy"'] }));
useDefaultCreds.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Runtime`] }));
const proxyUri = this.add(new Property('Proxy', System.Uri, { attributes: [], description: 'The URI for the proxy server to use' }));
proxyUri.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'DontShow = true', 'HelpMessage = "The URI for the proxy server to use"'] }));
proxyUri.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Runtime`] }));
if (this.state.project.azure) {
this.defaultProfile = this.add(new Property('DefaultProfile', PSObject, { description: 'The DefaultProfile parameter is not functional. Use the SubscriptionId parameter when available if executing the cmdlet against a different subscription' }));
this.defaultProfile.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'HelpMessage = "The DefaultProfile parameter is not functional. Use the SubscriptionId parameter when available if executing the cmdlet against a different subscription."'] }));
this.defaultProfile.add(new Attribute(ValidateNotNull));
this.defaultProfile.add(new Attribute(Alias, { parameters: ['"AzureRMContext"', '"AzureCredential"'] }));
this.defaultProfile.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Azure`] }));
}
}
private NewImplementProcessRecord() {
const $this = this;
const operation: CommandOperation = $this.operation;
this.add(new Method('ProcessRecord', undefined, { access: Access.Protected, override: Modifier.Override, description: 'Performs execution of the command.' })).add(function* () {
yield $this.eventListener.syncSignal(Events.CmdletProcessRecordStart);
if ($this.state.project.azure) {
yield $this.processRecordId.assign('System.Guid.NewGuid().ToString()');
}
yield Try(function* () {
yield '// work';
const normal = new Statements(function* () {
const acr = new LocalVariable('asyncCommandRuntime', dotnet.Var, { initializer: AsyncCommandRuntime.new(dotnet.This, $this.cancellationToken) });
yield Using(acr.declarationExpression, function* () {
yield `${acr}.Wait( ProcessRecordAsync(),${$this.cancellationToken});`;
});
});
if (operation.asjob) {
const asjob = $this.add(new Property('AsJob', SwitchParameter, { description: 'when specified, runs this cmdlet as a PowerShell job' }));
asjob.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'HelpMessage = "Run the command as a job"'] }));
asjob.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Runtime`] }));
const nowait = $this.add(new Property('NoWait', SwitchParameter, { description: 'when specified, will make the remote call, and return an AsyncOperationResponse, letting the remote operation continue asynchronously.' }));
nowait.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'HelpMessage = "Run the command asynchronously"'] }));
nowait.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Runtime`] }));
}
const work: OneOrMoreStatements = operation.asjob ? function* () {
yield If('true == MyInvocation?.BoundParameters?.ContainsKey("AsJob")', function* () {
// clone the cmdlet instance, since the instance can be reused and overwrite data.
const instance = new LocalVariable('instance', dotnet.Var, { initializer: 'this.Clone()' });
yield instance.declarationStatement;
// create the job (which will set the CommandRuntime of the clone to the AsyncJob itself)
const job = new LocalVariable('job', dotnet.Var, { initializer: AsyncJob.new(instance, 'this.MyInvocation.Line, this.MyInvocation.MyCommand.Name, this._cancellationTokenSource.Token', 'this._cancellationTokenSource.Cancel') });
yield job.declarationStatement;
// add the job to the repository
yield `JobRepository.Add(${job});`;
// invoke the cmdlet's PRA
const task = new LocalVariable('task', dotnet.Var, { initializer: `${instance}.ProcessRecordAsync()` });
yield task.declarationStatement;
// have the AsyncJob monitor the lifetime of the Task
yield `${job}.Monitor(${task});`;
// return the job to the user now.
yield `WriteObject(${job});`;
});
yield Else(normal);
} : normal;
if (isWritableCmdlet(operation) && !operation.details.csharp.supportShouldProcess) {
yield If(`ShouldProcess($"Call remote '${$this.apiCall.language.csharp?.name}' operation")`, work);
} else {
yield work;
}
});
const aggregateException = new Parameter('aggregateException', System.AggregateException);
yield Catch(aggregateException, function* () {
yield '// unroll the inner exceptions to get the root cause';
yield ForEach('innerException', new LiteralExpression(`${aggregateException.use}.Flatten().InnerExceptions`), function* () {
yield $this.eventListener.syncSignal(Events.CmdletException, new LiteralExpression('$"{innerException.GetType().Name} - {innerException.Message} : {innerException.StackTrace}"'));
yield '// Write exception out to error channel.';
yield `WriteError( new ${ErrorRecord}(innerException,string.Empty, ${ErrorCategory('NotSpecified')}, null) );`;
});
});
const exception = new Parameter('exception', System.Exception);
yield Catch(exception, function* () {
yield $this.eventListener.syncSignal(Events.CmdletException, new LiteralExpression(`$"{${exception.use}.GetType().Name} - {${exception.use}.Message} : {${exception.use}.StackTrace}"`));
yield '// Write exception out to error channel.';
yield `WriteError( new ${ErrorRecord}(${exception.use},string.Empty, ${ErrorCategory('NotSpecified')}, null) );`;
}, { when: new LiteralExpression('(exception as System.Management.Automation.PipelineStoppedException)== null || (exception as System.Management.Automation.PipelineStoppedException).InnerException != null') });
yield Finally(function* () {
yield $this.eventListener.syncSignalNoCheck(Events.CmdletProcessRecordEnd);
});
});
}
private NewImplementProcessRecordAsync() {
const $this = this;
const operationParameters = $this.operationParameters;
const pipeline = $this.$<Property>('Pipeline');
this.serializationMode = this.bodyParameter ? (this.operation.operationType === OperationType.Create ? ClientRuntime.SerializationMode.IncludeCreate : (this.operation.operationType === OperationType.Update ? ClientRuntime.SerializationMode.IncludeUpdate : undefined)) : undefined;
if (this.operation.commandType === CommandType.GetPut || this.operation.commandType === CommandType.ManagedIdentityUpdate) {
this.serializationMode = ClientRuntime.SerializationMode.IncludeCreateOrUpdate;
}
const PRA = this.add(new Method('ProcessRecordAsync', System.Threading.Tasks.Task(), {
access: Access.Protected, async: Modifier.Async,
description: 'Performs execution of the command, working asynchronously if required.',
returnsDescription: `A <see cref="${System.Threading.Tasks.Task()}" /> that will be complete when handling of the method is completed.`
}));
// we don't want to use SynchContext here.
PRA.push(Using('NoSynchronizationContext', ''));
PRA.add(function* () {
if ($this.apProp && $this.bodyParameter && $this.bodyParameterInfo) {
// yield `${ClientRuntime}.DictionaryExtensions.HashTableToDictionary<${$this.bodyParameterInfo.type.declaration},${$this.bodyParameterInfo.valueType.declaration}>(${$this.apProp.value},${$this.bodyParameter.Cast($this.bodyParameterInfo.type)});`;
let vt = $this.bodyParameterInfo.valueType.declaration;
if (vt.endsWith('SwitchParameter')) {
vt = 'bool';
}
yield `${ClientRuntime}.DictionaryExtensions.HashTableToDictionary<${vt}>(${$this.apProp.value},${$this.bodyParameter}.AdditionalProperties);`;
}
// construct the call to the operation
if (!$this.state.project.azure) {
yield $this.eventListener.signal(Events.CmdletProcessRecordAsyncStart);
}
yield $this.eventListener.signal(Events.CmdletGetPipeline);
if ($this.state.project.azure) {
yield pipeline.assign(new LiteralExpression(`${$this.state.project.serviceNamespace.moduleClass.declaration}.Instance.CreatePipeline(${$this.invocationInfo}, ${$this.correlationId}, ${$this.processRecordId}, this.ParameterSetName, this.ExtensibleParameters)`));
} else {
yield pipeline.assign(new LiteralExpression(`${$this.state.project.serviceNamespace.moduleClass.declaration}.Instance.CreatePipeline(${$this.invocationInfo}, this.ParameterSetName, this.ExtensibleParameters)`));
}
yield If(IsNotNull($this.$<Property>('HttpPipelinePrepend')), pipeline.invokeMethod('Prepend', toExpression(`(this.CommandRuntime as Microsoft.Rest.ClientRuntime.PowerShell.IAsyncCommandRuntimeExtensions)?.Wrap(${$this.$<Property>('HttpPipelinePrepend')}) ?? ${$this.$<Property>('HttpPipelinePrepend')}`)));
yield If(IsNotNull($this.$<Property>('HttpPipelineAppend')), pipeline.invokeMethod('Append', toExpression(`(this.CommandRuntime as Microsoft.Rest.ClientRuntime.PowerShell.IAsyncCommandRuntimeExtensions)?.Wrap(${$this.$<Property>('HttpPipelineAppend')}) ?? ${$this.$<Property>('HttpPipelineAppend')}`)));
yield '// get the client instance';
// Add input pipeline if it is configured by developers.
if ($this.operation.extensions.inputPipe) {
const handlers = <Array<HandlerDirective>>$this.operation.extensions.inputPipe;
// default priority is 100
handlers.sort((a, b) => { return (a.priority || 100) - (b.priority || 100); });
for (const { index, handler } of handlers.map((handler, index) => ({ index, handler }))) {
yield `var handler_${index} = new ${handler.name}();`;
if (index > 0) {
yield `handler_${index - 1}.SetNextHandler(handler_${index});`;
}
}
yield 'handler_0.Process(this);';
}
const vps = $this.operation.details.csharp.virtualParameters || {
body: [],
operation: [],
};
for (const vParam of [...vps.body, ...vps.operation]) {
if (vParam.hidden) {
const td = $this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(<NewSchema>vParam.schema, true, $this.state, true);
yield If(`true == this.MyInvocation?.BoundParameters?.ContainsKey("${vParam.name}")`, `${vParam.name} = (${td.declaration})this.MyInvocation.BoundParameters["${vParam.name}"];`);
}
}
// is there a body parameter we should include?
// if ($this.bodyParameter) {
// operationParameters.push({ name: 'body', expression: $this.bodyParameter, isPathParam: false });
// }
yield Try(function* () {
// make the call.
let preProcess: PreProcess;
switch ($this.operation.commandType) {
case CommandType.ManagedIdentityUpdate:
preProcess = !$this.disableTransformIdentityType && $this.ContainsIdentityTypeParameter() ? $this.ManagedIdentityUpdateCmdletPreProcess : undefined;
break;
case CommandType.GetPut:
preProcess = $this.GetPutPreProcess;
break;
case CommandType.ManagedIdentityNew:
preProcess = !$this.disableTransformIdentityType && $this.ContainsIdentityTypeParameter() && $this.ContainsUserAssignedIdentityParameter() ? $this.ManagedIdentityPreProcessForNewVerbCmdlet : undefined;
break;
case CommandType.Atomic:
default:
preProcess = $this.ContainsUserAssignedIdentityParameter() ? $this.ManagedUserAssignedIdentityPreProcess : undefined;
break;
}
const actualCall = function* () {
yield $this.eventListener.signal(Events.CmdletBeforeAPICall);
yield $this.ImplementCall(preProcess);
yield $this.eventListener.signal(Events.CmdletAfterAPICall);
};
if ($this.state.project.azure && operationParameters.find(each => each.expression && each.expression.value === 'SubscriptionId') && $this.operation.details.csharp.verb.toLowerCase() === 'get') {
yield 'foreach( var SubscriptionId in this.SubscriptionId )';
yield BlockStatement(actualCall);
} else {
yield actualCall;
}
});
const ure = new Parameter('urexception', { declaration: `${ClientRuntime.fullName}.UndeclaredResponseException` });
yield Catch(ure, function* () {
yield `WriteError(new global::System.Management.Automation.ErrorRecord(${ure.value}, ${ure.value}.StatusCode.ToString(), global::System.Management.Automation.ErrorCategory.InvalidOperation, new { ${operationParameters.filter(e => valueOf(e.expression) !== 'null').map(each => `${each.name}=${each.expression}`).join(',')}})
{
ErrorDetails = new global::System.Management.Automation.ErrorDetails(${ure.value}.Message) { RecommendedAction = ${ure.value}.Action }
});`;
});
yield Finally(function* () {
yield $this.eventListener.signalNoCheck(Events.CmdletProcessRecordAsyncEnd);
});
});
}
private NewImplementWriteObject() {
const $this = this;
const sendToPipeline = new Parameter('sendToPipeline', dotnet.Object);
const enumerateCollection = new Parameter('enumerateCollection', dotnet.Bool);
const snglWriteObject = new Method('WriteObject', dotnet.Void, {
access: Access.Protected,
new: Modifier.New,
parameters: [sendToPipeline]
});
snglWriteObject.add(function* () {
yield `${$this.state.project.serviceNamespace.moduleClass.declaration}.Instance.SanitizeOutput?.Invoke(sendToPipeline, __correlationId);`;
yield 'base.WriteObject(sendToPipeline);';
});
const collWriteObject = new Method('WriteObject', dotnet.Void, {
access: Access.Protected,
new: Modifier.New,
parameters: [sendToPipeline, enumerateCollection]
});
collWriteObject.add(function* () {
yield `${$this.state.project.serviceNamespace.moduleClass.declaration}.Instance.SanitizeOutput?.Invoke(sendToPipeline, __correlationId);`;
yield 'base.WriteObject(sendToPipeline, enumerateCollection);';
});
$this.add(snglWriteObject);
$this.add(collWriteObject);
}
private * ImplementCall(preProcess: PreProcess) {
const $this = this;
const operation = $this.operation;
const apiCall = $this.apiCall;
const operationParameters: Array<operationParameter> = $this.operationParameters;
const callbackMethods = $this.callbackMethods;
const pipeline = $this.$<Property>('Pipeline');
const serializationMode = $this.serializationMode;
const bodyParameter = this.bodyParameter ? { name: 'body', value: valueOf(this.bodyParameter) } : undefined;
const pathParamsNotInIdentity: Array<{ name: string | undefined; value: string; }> = [];
const pathParamsInIdentity: Array<{ name: string; value: string; }> = [];
const pathParamsInIdentitySerializedName: Array<string> = [];
const headerParams: Array<{ name: string | undefined; value: string; }> = [];
const queryParams: Array<{ name: string | undefined; value: string; }> = [];
const otherParams: Array<{ name: string | undefined; value: string; }> = [];
const idschema = values($this.state.project.model.schemas.objects).first(each => each.language.default.uid === 'universal-parameter-type');
let httpOperationName = `${apiCall.language.csharp?.name}`;
if (idschema) {
const allVPs = NewGetAllPublicVirtualProperties(idschema.language.csharp?.virtualProperties);
const props = [...values(idschema.properties)];
operationParameters.forEach(each => {
const pascalName = pascalCase(`${each.name}`);
//push parameters that is not path parameters into allParams and idOpParamsNotFromIdentity
if (each.parameterLocation !== ParameterLocation.Path) {
const param = {
name: undefined,
value: valueOf(each.expression)
};
switch (each.parameterLocation) {
case ParameterLocation.Header:
headerParams.push(param);
break;
case ParameterLocation.Query:
queryParams.push(param);
break;
default:
otherParams.push(param);
break;
}
return;
} else {
const match = props.find(p => pascalCase(p.serializedName) === pascalName);
if (match) {
const defaultOfType = $this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(match.schema, true, $this.state, $this.state.project.fixedArray).defaultOfType;
// match up vp name
const vp = allVPs.find(pp => pascalCase(pp.property.serializedName) === pascalName);
//push path parameters that form current identity into allParams, idOpParamsFromIdentity and idOpParamsFromIdentityserializedName
if (vp && each.expression === dotnet.Null) {
const param = {
name: `${$this.inputObjectParameterName}.${vp.name}`,
value: `${$this.inputObjectParameterName}.${vp.name} ?? ${defaultOfType}`
};
pathParamsInIdentity.push(param);
pathParamsInIdentitySerializedName.push(match.serializedName);
return;
}
// fall back!
this.state.debug(`Unable to match identity parameter '${each.name}' member to appropriate virtual parameter. (Guessing '${pascalCase(match.language.csharp?.name ?? '')}').`, {});
//push path parameters that current identity does not contain into allParams and idOpParamsNotFromIdentity
const param = {
name: `${pascalCase(match.language.csharp?.name ?? '')}`,
value: `${pascalCase(match.language.csharp?.name ?? '')}`
};
pathParamsNotInIdentity.push(param);
} else {
this.state.debug(`Unable to match identity parameter '${each.name}' member to appropriate virtual parameter. (Guessing '${pascalName}')`, {});
/*
push path parameters do not match the name in identity schema into allParams and idOpParamsNotFromIdentity
for example, module 'Service' has only one GET API:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Service/resource1/{resource1Name}/resource2/{resource2Name}/resource3/{resource3Name}
the identity schema 'ServiceIdentity.cs' will have properties: {subscriptionId, resourceGroupName, resource1Name, resource2Name, resource3Name}
for variant which has (identity of resource2 + resource3Name) combined as parameters, the parameter name for 'resource3Name' is called 'Name' which do not match 'resource3Name' in 'ServiceIdentity.cs'
*/
const param = {
name: `${pascalName}`,
value: `${pascalName}`
};
pathParamsNotInIdentity.push(param);
}
}
});
}
if ($this.isViaIdentity) {
//when identity does not contain property: 'Id'
const identityFromPathParams = function* () {
yield '// try to call with PATH parameters from Input Object';
if (idschema) {
for (const opParam of pathParamsInIdentity) {
if (opParam && opParam.name) {
yield If(IsNull(opParam.name), `ThrowTerminatingError( new ${ErrorRecord}(new global::System.Exception("${$this.inputObjectParameterName} has null value for ${opParam.name}"),string.Empty, ${ErrorCategory('InvalidArgument')}, ${$this.inputObjectParameterName}) );`);
}
}
const pathParameters = [...pathParamsInIdentity.map(each => toExpression(each.value)), ...pathParamsNotInIdentity.map(each => toExpression(each.value))];
const nonPathParameters = [...headerParams.map(each => toExpression(each.value)), ...queryParams.map(each => toExpression(each.value)), ...otherParams.map(each => toExpression(each.value))];
const parameters = bodyParameter ? [...pathParameters, ...nonPathParameters, toExpression(bodyParameter.value), ...callbackMethods, dotnet.This, pipeline] : [...pathParameters, ...nonPathParameters, ...callbackMethods, dotnet.This, pipeline];
if (serializationMode) {
parameters.push(serializationMode);
}
if (preProcess) {
yield preProcess($this, pathParameters, [...otherParams.map(each => toExpression(each.value)), dotnet.This, pipeline], false);
}
yield `await this.${$this.$<Property>('Client').invokeMethod(httpOperationName, ...parameters).implementation}`;
}
};
//when identity does contain property: 'Id'
const identityParams = function* () {
const path = apiCall.requests?.[0].protocol.http?.path;
let pathParams = '';
//append path parameters which are not part of current identity
pathParamsNotInIdentity.forEach(each => {
const serializedName = values($this.properties)
.where(p => p.metadata.parameterDefinition)
.first(p => p.name === each.name)?.metadata.parameterDefinition.language.csharp.serializedName;
if (each.name && serializedName) {
if (!pathParams) {
pathParams += '$"';
}
const resourceName = getResourceNameFromPath(path, serializedName);
if (resourceName) {
pathParams += `/${resourceName}/{(global::System.Uri.EscapeDataString(this.${each.name}.ToString()))}`;
}
}
});
//add child resource to path for list operation
if ($this.operation.variant.startsWith('List') && $this.operation.callGraph[0].requests?.[0].protocol.http?.method.toLowerCase() !== 'post' && pathParamsInIdentitySerializedName?.[pathParamsInIdentitySerializedName.length - 1]) {
const childResourceName = getChildResourceNameFromPath(path, pathParamsInIdentitySerializedName?.[pathParamsInIdentitySerializedName.length - 1]);
if (pathParams && pathParams.length > 0) {
pathParams += `/${childResourceName}`;
} else {
pathParams += `"/${childResourceName}`;
}
}
if (pathParams && pathParams.length > 0) {
pathParams += '";';
yield `this.${$this.inputObjectParameterName}.Id += ${pathParams}`;
}
const pathParameters = [toExpression(`${$this.inputObjectParameterName}.Id`)];
const nonPathParameters = [...headerParams.map(each => toExpression(each.value)), ...queryParams.map(each => toExpression(each.value)), ...otherParams.map(each => toExpression(each.value))];
const parameters = bodyParameter ? [...pathParameters, ...nonPathParameters, toExpression(bodyParameter.value), ...callbackMethods, dotnet.This, pipeline] : [...pathParameters, ...nonPathParameters, ...callbackMethods, dotnet.This, pipeline];
if (serializationMode) {
parameters.push(serializationMode);
}
if (preProcess) {
yield preProcess($this, pathParameters, [...otherParams.map(each => toExpression(each.value)), dotnet.This, pipeline], true);
}
yield `await this.${$this.$<Property>('Client').invokeMethod(`${httpOperationName}ViaIdentity`, ...parameters).implementation}`;
};
if (idschema && values(idschema.properties).first(each => each.language.csharp?.uid === 'universal-parameter:resource identity')) {
yield If(`${$this.inputObjectParameterName}?.Id != null`, identityParams);
yield Else(identityFromPathParams);
} else {
yield identityFromPathParams;
}
} else {
const pathParameters = [...pathParamsInIdentity.map(each => toExpression(each.value)), ...pathParamsNotInIdentity.map(each => toExpression(each.value))];
const nonPathParameters = [...headerParams.map(each => toExpression(each.value)), ...queryParams.map(each => toExpression(each.value)), ...otherParams.map(each => toExpression(each.value))];
let parameters = bodyParameter ? [...pathParameters, ...nonPathParameters, toExpression(bodyParameter.value), ...callbackMethods, dotnet.This, pipeline] : [...pathParameters, ...nonPathParameters, ...callbackMethods, dotnet.This, pipeline];
if (serializationMode) {
parameters.push(serializationMode);
}
if (operation.variant.includes('ViaJsonString') || operation.variant.includes('ViaJsonFilePath')) {
httpOperationName = `${httpOperationName}ViaJsonString`;
const jsonParameter = new Field('_jsonString', System.String);
parameters = [...pathParameters, ...nonPathParameters, jsonParameter, ...callbackMethods, dotnet.This, pipeline];
}
if (preProcess) {
yield preProcess($this, pathParameters, [...otherParams.map(each => toExpression(each.value)), dotnet.This, pipeline], false);
}
yield `await this.${$this.$<Property>('Client').invokeMethod(httpOperationName, ...parameters).implementation}`;
}
}
private ContainsSpecifiedParameter(parameterName: string): boolean {
return this.operation.details.csharp.virtualParameters?.body?.map(p => p.name)?.includes(parameterName) ?? false;
}
private ContainsIdentityTypeParameter(): boolean {
return this.ContainsSpecifiedParameter('IdentityType');
}
private ContainsUserAssignedIdentityParameter(): boolean {
return this.ContainsSpecifiedParameter('IdentityUserAssignedIdentity') || this.ContainsSpecifiedParameter('UserAssignedIdentity');
}
private GetUserAssignedIdentityParameterTypeDefinition(): string | undefined {
return (<DictionarySchema>this.operation.details.csharp.virtualParameters?.body?.filter(p => p.name === 'UserAssignedIdentity' || p.name === 'IdentityUserAssignedIdentity')?.[0]?.schema)?.type;
}
private GetUserAssignedIdentityParameterElementType(): string | undefined {
return (<DictionarySchema>this.operation.details.csharp.virtualParameters?.body?.filter(p => p.name === 'UserAssignedIdentity' || p.name === 'IdentityUserAssignedIdentity')?.[0]?.schema)?.elementType?.language?.csharp?.fullname;
}
private GetUserAssignedIdentityPropertyTypeDeclaration(): string {
const userAssignedIdentityParameter = this.properties.filter(p => p.name === 'UserAssignedIdentity' || p.name === 'IdentityUserAssignedIdentity');
return userAssignedIdentityParameter?.[0]?.type?.declaration ?? undefined;
}
private GetUserAssignedIdentityPropertyName(): string {
const response = this.responses.filter(re => (<SchemaResponse>re)?.schema?.extensions?.['is-return-object'])?.[0];
const props = NewGetAllPublicVirtualProperties((<SchemaResponse>response).schema.language.csharp?.virtualProperties);
const userAssignedIdentityParameter = props?.filter(p => p.name === 'UserAssignedIdentity' || p.name === 'IdentityUserAssignedIdentity');
return userAssignedIdentityParameter?.[0]?.name ?? undefined;
}
private ManagedUserAssignedIdentityPreProcess(cmdlet: CmdletClass, pathParams: Array<Expression> = [], nonPathParams: Array<Expression> = [], viaIdentity = false): Statements {
const $this = cmdlet;
if ($this.ContainsUserAssignedIdentityParameter() && $this.flattenUserAssignedIdentity && $this.GetUserAssignedIdentityParameterTypeDefinition() === 'dictionary') {
return If('this.UserAssignedIdentity?.Length > 0',
function* () {
yield '// calculate UserAssignedIdentity';
const userAssignedIdentityPropertyName = $this.GetUserAssignedIdentityPropertyName();
yield `${$this.bodyParameter?.value}.${userAssignedIdentityPropertyName}.Clear();`;
yield ForEach('id', 'this.UserAssignedIdentity', `${$this.bodyParameter?.value}.${userAssignedIdentityPropertyName}.Add(id, new ${$this.GetUserAssignedIdentityParameterElementType()}());`);
yield '';
}
);
}
return new Statements();
}
private ManagedIdentityPreProcessForNewVerbCmdlet(cmdlet: CmdletClass, pathParams: Array<Expression>, nonPathParams: Array<Expression>, viaIdentity: boolean): Statements {
const $this = cmdlet;
const preProcessManagedIdentityParametersMethod = new Method('PreProcessManagedIdentityParameters', dotnet.Void, {
access: Access.Private
});
const preProcessManagedIdentityParameters = function* () {
yield $this.ManagedUserAssignedIdentityPreProcess($this);
yield '// calculate IdentityType';
if ($this.operation.details.csharp.virtualParameters?.body?.filter(p => p.name === 'IdentityType')[0]?.required) {
yield If(`null == ${$this.bodyParameter?.value}.IdentityType`, `${$this.bodyParameter?.value}.IdentityType = "None";`);
}
// if UserAssignedIdentity is a string array or string, use Length. Otherwise, use Count
yield If(`this.UserAssignedIdentity?.${!$this.flattenUserAssignedIdentity && $this.GetUserAssignedIdentityParameterTypeDefinition() === 'dictionary' ? 'Count' : 'Length'} > 0`,
function* () {
yield If(`"SystemAssigned".Equals(${$this.bodyParameter?.value}.IdentityType, StringComparison.InvariantCultureIgnoreCase)`, `${$this.bodyParameter?.value}.IdentityType = "SystemAssigned,UserAssigned";`);
yield Else(`${$this.bodyParameter?.value}.IdentityType = "UserAssigned";`);
});
};
if (!$this.hasMethodWithSameDeclaration(preProcessManagedIdentityParametersMethod)) {
preProcessManagedIdentityParametersMethod.add(preProcessManagedIdentityParameters);
$this.add(preProcessManagedIdentityParametersMethod);
}
return new Statements(function* () {
yield `this.${preProcessManagedIdentityParametersMethod.name}();`;
});
}
private ProcessGetResponseForManagedIdentityUpdateCmdlet(cmdlet: CmdletClass): Statements {
const $this = cmdlet;
const containsUserAssignedIdentity = $this.ContainsUserAssignedIdentityParameter();
const preProcessManagedIdentity = function* () {
yield new LocalVariable('supportsSystemAssignedIdentity', dotnet.Bool, { initializer: Or('true == this.EnableSystemAssignedIdentity', `null == this.EnableSystemAssignedIdentity && true == ${$this.bodyParameter?.value}?.IdentityType?.Contains("SystemAssigned")`) });
yield new LocalVariable('supportsUserAssignedIdentity', dotnet.Bool, { initializer: `${dotnet.False}` });
if (containsUserAssignedIdentity) {
yield $this.ManagedUserAssignedIdentityPreProcess($this);
yield `supportsUserAssignedIdentity = true == this.MyInvocation?.BoundParameters?.ContainsKey("UserAssignedIdentity") && ${!$this.flattenUserAssignedIdentity && $this.GetUserAssignedIdentityParameterTypeDefinition() === 'dictionary' ? `((${$this.GetUserAssignedIdentityPropertyTypeDeclaration()})this.MyInvocation?.BoundParameters["UserAssignedIdentity"])?.Count` : 'this.UserAssignedIdentity?.Length'} > 0 ||
true != this.MyInvocation?.BoundParameters?.ContainsKey("UserAssignedIdentity") && true == ${$this.bodyParameter?.value}.IdentityType?.Contains("UserAssigned");`;
yield If('!supportsUserAssignedIdentity', function* () {
yield `${$this.bodyParameter?.value}.${$this.GetUserAssignedIdentityPropertyName()} = null;`;
});
yield '';
}
yield '// calculate IdentityType';
yield If(And('supportsUserAssignedIdentity', 'supportsSystemAssignedIdentity'), `${$this.bodyParameter?.value}.IdentityType = "SystemAssigned,UserAssigned";`);
yield ElseIf(And('supportsUserAssignedIdentity', '!supportsSystemAssignedIdentity'), `${$this.bodyParameter?.value}.IdentityType = "UserAssigned";`);
yield ElseIf(And('!supportsUserAssignedIdentity', 'supportsSystemAssignedIdentity'), `${$this.bodyParameter?.value}.IdentityType = "SystemAssigned";`);
yield Else(`${$this.bodyParameter?.value}.IdentityType = "None";`);
};
const preProcessManagedIdentityMethod = new Method('PreProcessManagedIdentityParametersWithGetResult', dotnet.Void, {
access: Access.Private
});
if (!$this.hasMethodWithSameDeclaration(preProcessManagedIdentityMethod)) {
preProcessManagedIdentityMethod.add(preProcessManagedIdentity);
$this.add(preProcessManagedIdentityMethod);
}
return new Statements(function* () {
yield `this.${preProcessManagedIdentityMethod.name}();`;
});
}
private ManagedIdentityUpdateCmdletPreProcess(cmdlet: CmdletClass, pathParams: Array<Expression>, nonPathParams: Array<Expression>, viaIdentity: boolean): Statements {
return cmdlet.GetPutPreProcess(cmdlet, pathParams, nonPathParams, viaIdentity, cmdlet.ProcessGetResponseForManagedIdentityUpdateCmdlet);
}
private GetPutPreProcess(cmdlet: CmdletClass, pathParams: Array<Expression>, nonPathParams: Array<Expression>, viaIdentity: boolean, processGetResponse: ProcessGetResponse = undefined): Statements {
const $this = cmdlet;
const updateBodyMethod = new Method(`Update${$this.bodyParameter?.value}`, dotnet.Void, {
access: Access.Private
});
const httpOperationName = `${$this.operation.callGraph[0].language.csharp?.name}${viaIdentity ? 'ViaIdentity' : ''}WithResult`;
if (!$this.hasMethodWithSameDeclaration(updateBodyMethod)) {
updateBodyMethod.add(function* () {
const bodyParameters = $this.properties.filter(each => {
for (const attribute of each.attributes) {
for (const parameter of attribute.parameters) {
if ('global::Microsoft.Rest.ParameterCategory.Body' === valueOf(parameter)) {
return true;
}
}
}
return false;
});
for (const param of bodyParameters) {
yield If(`(bool)(true == this.MyInvocation?.BoundParameters.ContainsKey("${param.name}"))`, `this.${param.name} = (${param.type.declaration})(this.MyInvocation?.BoundParameters["${param.name}"]);`);
}
});
$this.add(updateBodyMethod);
}
const getPut = function* () {
yield `${$this.bodyParameter?.value} = await this.${$this.$<Property>('Client').invokeMethod(httpOperationName, ...[...pathParams, ...nonPathParams]).implementation} `;
// PreProcess body parameter
if (processGetResponse) {
yield processGetResponse($this);
}
yield `this.${updateBodyMethod.name}();`;
/** Instance:
* _requestBodyParametersBody = await this.Client.GrafanaGetWithResult(SubscriptionId, ResourceGroupName, Name, this, Pipeline);
* this.Update_requestBodyParametersBody();
* */
};
return new Statements(getPut);
}
private NewImplementResponseMethod() {
const $this = this;
const apiCall = $this.apiCall;
const operationParameters: Array<operationParameter> = $this.operationParameters;
const callbackMethods = $this.callbackMethods;
const pipeline = $this.$<Property>('Pipeline');
// make callback methods
for (const each of values($this.responses)) {
const parameters = new Array<Parameter>();
const isBinary = (<BinaryResponse>each).binary;
parameters.push(new Parameter('responseMessage', System.Net.Http.HttpResponseMessage, { description: `the raw response message as an ${System.Net.Http.HttpResponseMessage}.` }));
if (each.language.csharp?.responseType) {
parameters.push(new Parameter('response', System.Threading.Tasks.Task({ declaration: each.language.csharp?.responseType }), {
description: `the body result as a <see cref="${each.language.csharp?.responseType.replace(/\[|\]|\?/g, '')}">${each.language.csharp?.responseType}</see> from the remote call`
}));
}
if (each.language.csharp?.headerType) {
parameters.push(new Parameter('headers', System.Threading.Tasks.Task({ declaration: each.language.csharp.headerType }), { description: `the header result as a <see cref="${each.language.csharp.headerType}" /> from the remote call` }));
}
if (isBinary) {
parameters.push(new Parameter('response', System.Threading.Tasks.Task({ declaration: 'global::System.IO.Stream' }), { description: 'the body result as a <see cref="global::System.IO.Stream" /> from the remote call' }));
}
const override = `override${pascalCase(each.language.csharp?.name || '')}`;
const returnNow = new Parameter('returnNow', System.Threading.Tasks.Task(dotnet.Bool), { modifier: ParameterModifier.Ref, description: `/// Determines if the rest of the ${each.language.csharp?.name} method should be processed, or if the method should return immediately (set to true to skip further processing )` });
const overrideResponseMethod = new PartialMethod(override, dotnet.Void, {
parameters: [...parameters, returnNow],
description: `<c>${override}</c> will be called before the regular ${each.language.csharp?.name} has been processed, allowing customization of what happens on that response. Implement this method in a partial class to enable this behavior`,
returnsDescription: `A <see cref="${System.Threading.Tasks.Task()}" /> that will be complete when handling of the method is completed.`
});
$this.add(overrideResponseMethod);
const responseMethod = new Method(`${each.language.csharp?.name}`, System.Threading.Tasks.Task(), {
access: Access.Private,
parameters,
async: Modifier.Async,
description: each.language.csharp?.description,
returnsDescription: `A <see cref="${System.Threading.Tasks.Task()}" /> that will be complete when handling of the method is completed.`
});
responseMethod.push(Using('NoSynchronizationContext', ''));
responseMethod.add(function* () {
const skip = Local('_returnNow', `${System.Threading.Tasks.Task(dotnet.Bool).declaration}.FromResult(${dotnet.False})`);
yield skip.declarationStatement;
yield `${overrideResponseMethod.invoke(...parameters, `ref ${skip.value}`)};`;
yield `// if ${override} has returned true, then return right away.`;
yield If(And(IsNotNull(skip), `await ${skip}`), Return());
if (each.language.csharp?.isErrorResponse) {
// this should write an error to the error channel.
yield `// Error Response : ${each.protocol.http?.statusCodes[0]}`;
const unexpected = function* () {
yield '// Unrecognized Response. Create an error record based on what we have.';
const ex = (each.language.csharp?.responseType) ?
Local('ex', `new ${ClientRuntime.name}.RestException<${each.language.csharp.responseType}>(responseMessage, await response)`) :
Local('ex', `new ${ClientRuntime.name}.RestException(responseMessage)`);
yield ex.declarationStatement;
yield `WriteError( new global::System.Management.Automation.ErrorRecord(${ex.value}, ${ex.value}.Code, global::System.Management.Automation.ErrorCategory.InvalidOperation, new { ${operationParameters.filter(e => valueOf(e.expression) !== 'null').map(each => `${each.name}=${each.expression}`).join(', ')} })
{
ErrorDetails = new global::System.Management.Automation.ErrorDetails(${ex.value}.Message) { RecommendedAction = ${ex.value}.Action }
});`;
};
if ((<SchemaResponse>each).schema !== undefined) {
// the schema should be the error information.
// this supports both { error { message, code} } and { message, code}
let props = NewGetAllPublicVirtualProperties((<SchemaResponse>each).schema.language.csharp?.virtualProperties);
const errorProperty = values(props).first(p => p.property.serializedName === 'error');
let ep = '';
if (errorProperty) {
props = NewGetAllPublicVirtualProperties(errorProperty.property.schema.language.csharp?.virtualProperties);
ep = `${errorProperty.name}?.`;
}
const codeProp = props.find(p => p.name.toLowerCase().indexOf('code') > -1); // first property with 'code'
const messageProp = props.find(p => p.name.toLowerCase().indexOf('message') > -1); // first property with 'message'
const actionProp = props.find(p => p.name.toLowerCase().indexOf('action') > -1); // first property with 'action'
if (codeProp && messageProp) {
const lcode = new LocalVariable('code', dotnet.Var, { initializer: `(await response)?.${ep}${codeProp.name}` });
const lmessage = new LocalVariable('message', dotnet.Var, { initializer: `(await response)?.${ep}${messageProp.name}` });
const laction = actionProp ? new LocalVariable('action', dotnet.Var, { initializer: `(await response)?.${ep}${actionProp.name} ?? ${System.String.Empty}` }) : undefined;
yield lcode;
yield lmessage;
yield laction;
yield If(Or(IsNull(lcode), (IsNull(lmessage))), unexpected);
yield Else(`WriteError( new global::System.Management.Automation.ErrorRecord(new global::System.Exception($"[{${lcode}}] : {${lmessage}}"), ${lcode}?.ToString(), global::System.Management.Automation.ErrorCategory.InvalidOperation, new { ${operationParameters.filter(e => valueOf(e.expression) !== 'null').map(each => `${each.name}=${each.expression}`).join(', ')} })
{
ErrorDetails = new global::System.Management.Automation.ErrorDetails(${lmessage}) { RecommendedAction = ${laction || System.String.Empty} }
});`
);
return;
} else {
yield unexpected;
return;
}
} else {
yield unexpected;
return;
}
}
yield `// ${each.language.csharp?.name} - response for ${each.protocol.http?.statusCodes[0]} / ${values(each.protocol.http?.mediaTypes).join('/')}`;
if ('schema' in each) {
const schema = (<SchemaResponse>each).schema;
const props = NewGetAllPublicVirtualProperties(schema.language.csharp?.virtualProperties);
const rType = $this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(<NewSchema>schema, true, $this.state, $this.state.project.fixedArray);
const result = new LocalVariable('result', dotnet.Var, { initializer: new LiteralExpression('(await response)') });
yield `// (await response) // should be ${rType.declaration}`;
yield result.declarationStatement;
if (apiCall.language.csharp?.pageable) {
const pageable = apiCall.language.csharp.pageable;
if ($this.clientsidePagination) {
yield '// clientside pagination enabled';
}
yield '// response should be returning an array of some kind. +Pageable';
yield `// ${pageable.responseType} / ${pageable.itemName || '<none>'} / ${pageable.nextLinkName || '<none>'}`;
switch (pageable.responseType) {
// the result is (or works like a x-ms-pageable)
case 'pageable':
case 'nested-array': {
const valueProperty = (<ObjectSchema>schema).properties?.find(p => p.serializedName === pageable.itemName);
const nextLinkProperty = (<ObjectSchema>schema)?.properties?.find(p => p.serializedName === pageable.nextLinkName);
if (valueProperty && nextLinkProperty) {
// it's pageable!
// write out the current contents
const vp = NewGetVirtualPropertyFromPropertyName(schema.language.csharp?.virtualProperties, valueProperty.serializedName);
if (vp) {
const lengthFunc = $this.state.project.fixedArray ? 'Length' : 'Count';
const subArrayFunc = $this.state.project.fixedArray ? 'SubArray' : 'GetRange';
if ($this.clientsidePagination) {
yield (If(`(ulong)result.Value.${lengthFunc} <= this.PagingParameters.Skip`, function* () {
yield (`this.PagingParameters.Skip = this.PagingParameters.Skip - (ulong)result.Value.${lengthFunc};`);
}));
yield Else(function* () {
yield (`ulong toRead = Math.Min(this.PagingParameters.First, (ulong)result.Value.${lengthFunc} - this.PagingParameters.Skip);`);
yield (`var requiredResult = result.Value.${subArrayFunc}((int)this.PagingParameters.Skip, (int)toRead);`);
yield $this.WriteObjectWithViewControl('requiredResult', true);
yield ('this.PagingParameters.Skip = 0;');
yield ('this.PagingParameters.First = this.PagingParameters.First <= toRead ? 0 : this.PagingParameters.First - toRead;');
});
} else {
yield $this.WriteObjectWithViewControl(`${result.value}.${vp.name}`, true);
}
}
const nl = NewGetVirtualPropertyFromPropertyName(schema.language.csharp?.virtualProperties, nextLinkProperty.serializedName);
if (nl) {
$this.add(new Field('_isFirst', dotnet.Bool, {
access: Access.Private,
initialValue: new LiteralExpression('true'),
description: 'A flag to tell whether it is the first onOK call.'
}));
$this.add(new Field('_nextLink', dotnet.String, {
access: Access.Private,
description: 'Link to retrieve next page.'
}));
const nextLinkName = `${result.value}.${nl.name}`;
yield `_nextLink = ${nextLinkName};`;
const nextLinkCondition = $this.clientsidePagination ? '!String.IsNullOrEmpty(_nextLink) && this.PagingParameters.First > 0' : '!String.IsNullOrEmpty(_nextLink)';
yield (If('_isFirst', function* () {
yield '_isFirst = false;';
yield (While(nextLinkCondition,
If('responseMessage.RequestMessage is System.Net.Http.HttpRequestMessage requestMessage ', function* () {
yield `requestMessage = requestMessage.Clone(new global::System.Uri( _nextLink ),${ClientRuntime.Method.Get} );`;
yield $this.eventListener.signal(Events.FollowingNextLink);
yield `await this.${$this.$<Property>('Client').invokeMethod(`${apiCall.language.csharp?.name}_Call`, ...[toExpression('requestMessage'), ...callbackMethods, dotnet.This, pipeline]).implementation}`;
})
));
}));
}
return;
} else if (valueProperty) {
// it's just a nested array
const p = getVirtualPropertyFromPropertyName(schema.language.csharp?.virtualProperties, valueProperty.serializedName);
if (p) {
yield $this.WriteObjectWithViewControl(`${result.value}.${p.name}`, true);
}
return;
}
}
break;
// it's just an array,
case 'array':
// just write-object(enumerate) with the output
yield $this.WriteObjectWithViewControl(result.value, true);
return;
}
// ok, let's see if the response type
}
// we expect to get back some data from this call.
if ($this.hasStreamOutput && $this.outFileParameter) {
const outfile = $this.outFileParameter;
const provider = Local('provider');
provider.initializer = undefined;
const paths = Local('paths', `this.SessionState.Path.GetResolvedProviderPathFromPSPath(${outfile.value}, out ${provider.declarationExpression})`);
yield paths.declarationStatement;
yield If(`${provider.value}.Name != "FileSystem" || ${paths.value}.Count == 0`, `ThrowTerminatingError( new System.Management.Automation.ErrorRecord(new global::System.Exception("Invalid output path."),string.Empty, global::System.Management.Automation.ErrorCategory.InvalidArgument, ${outfile.value}) );`);
yield If(`${paths.value}.Count > 1`, `ThrowTerminatingError( new System.Management.Automation.ErrorRecord(new global::System.Exception("Multiple output paths not allowed."),string.Empty, global::System.Management.Automation.ErrorCategory.InvalidArgument, ${outfile.value}) );`);
if (rType.declaration === System.IO.Stream.declaration) {
// this is a stream output. write to outfile
const stream = Local('stream', result.value);
yield Using(stream.declarationExpression, function* () {
const fileStream = Local('fileStream', `global::System.IO.File.OpenWrite(${paths.value}[0])`);
yield Using(fileStream.declarationExpression, `await ${stream.value}.CopyToAsync(${fileStream.value});`);
});
} else {
// assuming byte array output (via result)
yield `global::System.IO.File.WriteAllBytes(${paths.value}[0],${result.value});`;
}
yield If('true == MyInvocation?.BoundParameters?.ContainsKey("PassThru")', function* () {
// no return type. Let's just return ... true?
yield 'WriteObject(true);';
});
return;
}
// let's just return the result object (or unwrapped result object)
yield $this.WriteObjectWithViewControl(result.value);
return;
}
// in m4, there will be no schema deinfed for the binary response, instead, we will have a field called binary with value true.
if ('binary' in each) {
yield '// (await response) // should be global::System.IO.Stream';
if ($this.hasStreamOutput && $this.outFileParameter) {
const outfile = $this.outFileParameter;
const provider = Local('provider');
provider.initializer = undefined;
const paths = Local('paths', 'new global::System.Collections.ObjectModel.Collection<global::System.String>()');
yield paths.declarationStatement;
yield Try(function* () {
yield `${paths.value} = this.SessionState.Path.GetResolvedProviderPathFromPSPath(${outfile.value}, out ${provider.declarationExpression});`;
yield If(`${provider.value}.Name != "FileSystem" || ${paths.value}.Count == 0`, `ThrowTerminatingError(new System.Management.Automation.ErrorRecord(new global::System.Exception("Invalid output path."), string.Empty, global::System.Management.Automation.ErrorCategory.InvalidArgument, ${outfile.value}));`);
yield If(`${paths.value}.Count > 1`, `ThrowTerminatingError(new System.Management.Automation.ErrorRecord(new global::System.Exception("Multiple output paths not allowed."), string.Empty, global::System.Management.Automation.ErrorCategory.InvalidArgument, ${outfile.value}));`);
});
const notfound = new Parameter('', { declaration: 'global::System.Management.Automation.ItemNotFoundException' });
yield Catch(notfound, function* () {
yield '// If the file does not exist, we will try to create it';
yield `${paths.value}.Add(${outfile.value});`;
});
// this is a stream output. write to outfile
const stream = Local('stream', 'await response');
yield Using(stream.declarationExpression, function* () {
const fileStream = Local('fileStream', `global::System.IO.File.OpenWrite(${paths.value}[0])`);
yield Using(fileStream.declarationExpression, `await ${stream.value}.CopyToAsync(${fileStream.value});`);
});
yield If('true == MyInvocation?.BoundParameters?.ContainsKey("PassThru")', function* () {
// no return type. Let's just return ... true?
yield 'WriteObject(true);';
});
return;
}
}
yield If('true == MyInvocation?.BoundParameters?.ContainsKey("PassThru")', function* () {
// no return type. Let's just return ... true?
yield 'WriteObject(true);';
});
});
$this.add(responseMethod);
}
}
private NewImplementSerialization(operation: CommandOperation) {
const $this = this;
// clone
if (operation.asjob) {
const clone = this.add(new Method('Clone', this, {
description: 'Creates a duplicate instance of this cmdlet (via JSON serialization).',
returnsDescription: `a duplicate instance of ${this.name}`,
}));
clone.add(function* () {
const i = new LocalVariable('clone', dotnet.Var, {
initializer: $this.new()
});
yield i.declarationStatement;
if ($this.state.project.azure) {
for (const f of [$this.correlationId, $this.processRecordId, $this.defaultProfile]) {
yield `${i.value}.${f} = this.${f};`;
}
}
for (const f of [$this.invocationInfo, 'Proxy', 'Pipeline', 'AsJob', 'Break', 'ProxyCredential', 'ProxyUseDefaultCredentials', 'HttpPipelinePrepend', 'HttpPipelineAppend',]) {
yield `${i.value}.${f} = this.${f};`;
}
for (const f of $this.thingsToSerialize) {
yield `${i.value}.${f} = this.${f};`;
}
// _name = this._name,
//_parametersBody = this._parametersBody,
//_resourceGroupName = this._resourceGroupName,
//_subscriptionId = this._subscriptionId,
yield Return(i);
});
}
}
private implementConstructors() {
// default constructor
this.add(new Constructor(this, { description: `Initializes a new instance of the <see cref="${this.name}" /> cmdlet class.` }));
}
private implementIContext() {
const extensibleParameters = this.add(new Field('_extensibleParameters', System.Collections.Generic.Dictionary(System.String, System.Object), {
access: Access.Private,
initialValue: new LiteralExpression('new System.Collections.Generic.Dictionary<string, object>()'),
description: 'A dictionary to carry over additional data for pipeline.'
}));
const extensibleParametersProp = new Property('ExtensibleParameters', System.Collections.Generic.IDictionary(System.String, System.Object), { description: 'Accessor for extensibleParameters.' });
extensibleParametersProp.get = toExpression(`${extensibleParameters.value} `);
this.add(extensibleParametersProp);
}
private implementIEventListener() {
const $this = this;
const cts = this.add(new Field('_cancellationTokenSource', System.Threading.CancellationTokenSource, {
access: Access.Private,
initialValue: new LiteralExpression(`new ${System.Threading.CancellationTokenSource.declaration}()`),
description: `The <see cref="${System.Threading.CancellationTokenSource}" /> for this operation.`
}));
const cancellationTokenSource = new Property('CancellationTokenSource', System.Threading.CancellationTokenSource, { description: 'Accessor for cancellationTokenSource.' });
cancellationTokenSource.get = toExpression(`${cts.value} `);
cancellationTokenSource.set = new Statements(cts.assign('value'));
this.add(cancellationTokenSource);
this.add(new LambdaProperty(`${ClientRuntime.IEventListener}.Token`, System.Threading.CancellationToken, new LiteralExpression(`${cts.value}.Token`), { getAccess: Access.Default, setAccess: Access.Default, description: `<see cref="${ClientRuntime}.IEventListener" /> cancellation token.` }));
this.cancellationToken = toExpression(`((${ClientRuntime.IEventListener})this).Token`);
this.add(new LambdaProperty(`${ClientRuntime.IEventListener}.Cancel`, System.Action(), new LiteralExpression(`${cts.value}.Cancel`), { getAccess: Access.Default, setAccess: Access.Default, description: `<see cref="${ClientRuntime}.IEventListener" /> cancellation delegate. Stops the cmdlet when called.` }));
const id = new Parameter('id', dotnet.String, { description: 'The message id' });
const token = new Parameter('token', System.Threading.CancellationToken, { description: 'The message cancellation token. When this call is cancelled, this should be <c>true</c>' });
const messageData = new Parameter('messageData', System.Func(ClientRuntime.EventData), { description: 'Detailed message data for the message event.' });
const signalMethod = this.add(new Method(`${ClientRuntime.IEventListener}.Signal`, System.Threading.Tasks.Task(), {
async: Modifier.Async,
parameters: [id, token, messageData],
access: Access.Default,
description: 'Handles/Dispatches events during the call to the REST service.',
returnsDescription: `A <see cref="${System.Threading.Tasks.Task()}" /> that will be complete when handling of the message is completed.`
}));
signalMethod.push(Using('NoSynchronizationContext', ''));
signalMethod.add(function* () {
yield If(`${token.value}.IsCancellationRequested`, Return());
// if the
const sw = Switch(id, [
TerminalCase(Events.Verbose.value, function* () {
yield `WriteVerbose($"{(messageData().Message ?? ${System.String.Empty})}");`;
yield Return();
}),
TerminalCase(Events.Warning.value, function* () {
yield `WriteWarning($"{(messageData().Message ?? ${System.String.Empty})}");`;
yield Return();
}),
TerminalCase(Events.Information.value, function* () {
if ($this.operation.asjob) {
yield '// When an operation supports asjob, Information messages must go thru verbose.';
yield `WriteVerbose($"INFORMATION: {(messageData().Message ?? ${System.String.Empty})}");`;
}
else {
const data = new LocalVariable('data', dotnet.Var, { initializer: new LiteralExpression(`${messageData.use}()`) });
yield data.declarationStatement;
yield 'WriteInformation(data.Message, new string[]{});';
}
yield Return();
}),
TerminalCase(Events.Debug.value, function* () {
yield `WriteDebug($"{(messageData().Message ?? ${System.String.Empty})}");`;
yield Return();
}),
TerminalCase(Events.Error.value, function* () {
yield 'WriteError(new global::System.Management.Automation.ErrorRecord( new global::System.Exception(messageData().Message), string.Empty, global::System.Management.Automation.ErrorCategory.NotSpecified, null ) );';
yield Return();
}),
TerminalCase(Events.Progress.value, function* () {
yield 'var data = messageData();';
yield 'int progress = (int)data.Value;';
yield 'string activityMessage, statusDescription;';
yield 'global::System.Management.Automation.ProgressRecordType recordType;';
yield 'if (progress < 100)';
yield '{';
yield ' activityMessage = "In progress";';
yield ' statusDescription = "Checking operation status";';
yield ' recordType = System.Management.Automation.ProgressRecordType.Processing;';
yield '}';
yield 'else';
yield '{';
yield ' activityMessage = "Completed";';
yield ' statusDescription = "Completed";';
yield ' recordType = System.Management.Automation.ProgressRecordType.Completed;';
yield '}';
// hardcode id = 1 because there is no need for nested progress bar
yield 'WriteProgress(new global::System.Management.Automation.ProgressRecord(1, activityMessage, statusDescription)';
yield '{';
yield ' PercentComplete = progress,';
yield 'RecordType = recordType';
yield '});';
yield Return();
}),
]);
if ($this.operation.asjob) {
// if we support -AsJob, we support -NoWait
sw.add(Case(Events.DelayBeforePolling.value, function* () {
yield 'var data = messageData();';
yield If('true == MyInvocation?.BoundParameters?.ContainsKey("NoWait")', function* () {
yield 'if (data.ResponseMessage is System.Net.Http.HttpResponseMessage response)';
yield '{';
yield ' var asyncOperation = response.GetFirstHeader(@"Azure-AsyncOperation");';
yield ' var location = response.GetFirstHeader(@"Location");';
yield ' var uri = global::System.String.IsNullOrEmpty(asyncOperation) ? global::System.String.IsNullOrEmpty(location) ? response.RequestMessage.RequestUri.AbsoluteUri : location : asyncOperation;';
yield ` WriteObject(new ${ClientRuntime}.PowerShell.AsyncOperationResponse { Target = uri });`;
yield ' // do nothing more. ';
yield ' data.Cancel();';
yield ' return;';
yield '}';
});
yield Else(function* () {
yield 'if (data.ResponseMessage is System.Net.Http.HttpResponseMessage response)';
yield '{';
yield ' int delay = (int)(response.Headers.RetryAfter?.Delta?.TotalSeconds ?? 30);';
yield ' WriteDebug($"Delaying {delay} seconds before polling.");';
yield ' for (var now = 0; now < delay; ++now)';
yield ' {';
// hardcode id = 1 because there is no need for nested progress bar
yield ' WriteProgress(new global::System.Management.Automation.ProgressRecord(1, "In progress", "Checking operation status")';
yield ' {';
yield ' PercentComplete = now * 100 / delay';
yield ' });';
yield ` await global::System.Threading.Tasks.Task.Delay(1000, ${token.use});`;
yield ' }';
yield '}';
});
}));
}
// the whole switch statement
yield sw;
if ($this.state.project.azure) {
// in azure mode, we signal the AzAccount module with every event that makes it here.
yield `await ${$this.state.project.serviceNamespace.moduleClass.declaration}.Instance.Signal(${id.value}, ${token.value}, ${messageData.value}, (i, t, m) => ((${ClientRuntime.IEventListener})this).Signal(i, t, () => ${ClientRuntime.EventDataConverter}.ConvertFrom(m()) as ${ClientRuntime.EventData}), ${$this.invocationInfo.value}, this.ParameterSetName, ${$this.correlationId.value}, ${$this.processRecordId.value}, null );`;
yield If(`${token.value}.IsCancellationRequested`, Return());
} else {
// In Non-Azure Modes, emit the Signal method without coorelation and processrecordid
yield `await ${$this.state.project.serviceNamespace.moduleClass.declaration}.Instance.Signal(${id.value}, ${token.value}, ${messageData.value}, (i, t, m) => ((${ClientRuntime.IEventListener})this).Signal(i, t, () => ${ClientRuntime.EventDataConverter}.ConvertFrom(m()) as ${ClientRuntime.EventData}), ${$this.invocationInfo.value}, this.ParameterSetName, null ); `;
yield If(`${token.value}.IsCancellationRequested`, Return());
}
yield `WriteDebug($"{id}: {(messageData().Message ?? ${System.String.Empty})}");`;
// any handling of the signal on our side...
});
}
private NewAddPowershellParameters(operation: CommandOperation) {
const vps = operation.details.csharp.virtualParameters || {
body: [],
operation: [],
};
for (const parameter of values(operation.parameters)) {
// these are the parameters that this command expects
parameter.schema;
const td = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(<NewSchema>parameter.schema, true, this.state, this.state.project.fixedArray);
if (parameter.details.csharp.constantValue) {
// this parameter has a constant value -- SKIP IT
continue;
}
if (parameter.details.csharp.fromHost) {
// the parameter is expected to be gotten from the host.(ie, Az.Accounts)
const hostParameter = this.add(new BackedProperty(parameter.details.csharp.name, td, {
metadata: {
parameterDefinition: parameter.details.csharp.httpParameter
},
description: parameter.details.csharp.description,
}));
this.thingsToSerialize.push(hostParameter);
// in the BeginProcessing, we should tell it to go get the value for this property from the common module
this.$<Method>('BeginProcessing').add(hostParameter.assignPrivate(new LiteralExpression(`${this.state.project.serviceNamespace.moduleClass.declaration}.Instance.GetParameter(this.MyInvocation, ${this.correlationId.value}, "${parameter.name}") as string`)));
continue;
}
const $this = this;
if (parameter.details.csharp.apiversion) {
// Api-version parameters for azure are a custom implementation
this.add(new Property(parameter.details.csharp.name, td, {
getAccess: Access.Internal,
setAccess: Access.Private,
metadata: {
parameterDefinition: parameter.details.csharp.httpParameter
},
description: parameter.details.csharp.description,
*get() {
const metadata = operation.extensions['x-ms-metadata'];
const profiles = <Dictionary<string>>metadata.profiles || new Dictionary<string>();
yield Switch(`${$this.state.project.serviceNamespace.moduleClass.declaration}.Instance.Profile`, function* () {
for (const { key: profileName, value: apiVersion } of items(profiles)) {
yield TerminalCase(`"${profileName}"`, Return(`"${apiVersion}"`));
}
yield TerminalDefaultCase(Return(`"${metadata.apiVersions[0]}"`));
});
}
}));
continue;
}
if (this.dropBodyParameter && parameter.details.csharp.isBodyParameter) {
// we're supposed to use parameters for the body parameter instead of a big object
const expandedBodyParameter = this.add(new Field('_' + camelCase(parameter.details.csharp.name), td, {
description: parameter.details.csharp.description,
initialValue: (parameter.schema.type === SchemaType.Array) ? dotnet.Null : `new ${parameter.schema.language.csharp?.fullname}()`,
access: Access.Private
}));
this.thingsToSerialize.push(expandedBodyParameter);
for (const vParam of vps.body) {
const vSchema = vParam.schema;
const schemaProperty = <NewVirtualProperty>vParam.origin;
const propertyType = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, true, this.state, this.state.project.fixedArray);
// we need to know if the actual underlying property is actually nullable.
const nullable = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, !!(schemaProperty.required && schemaProperty.read && schemaProperty.create && schemaProperty.update), this.state, this.state.project.fixedArray).isNullable;
let cmdletParameter: Property;
if (propertyType.schema.type !== SchemaType.Array || this.state.project.fixedArray) {
if (vParam.name === 'IdentityType' && !this.disableTransformIdentityType &&
(this.operation.commandType === CommandType.ManagedIdentityNew || this.operation.commandType === CommandType.ManagedIdentityUpdate)) {
const enableSystemAssignedIdentity = new Property('EnableSystemAssignedIdentity', operation.details.csharp.verb.toLowerCase() === 'new' ? SwitchParameter : NullableBoolean, {
set: operation.details.csharp.verb.toLowerCase() === 'new' ? toExpression(`${expandedBodyParameter.value}.${getVirtualPropertyName((<any>vParam.origin)) || vParam.origin.name} = value.IsPresent ? "SystemAssigned": null `) : undefined
});
enableSystemAssignedIdentity.description = 'Determines whether to enable a system-assigned identity for the resource.';
enableSystemAssignedIdentity.add(new Attribute(ParameterAttribute, { parameters: [new LiteralExpression(`Mandatory = ${vParam.required && operation.details.csharp.verb.toLowerCase() !== 'new' ? 'true' : 'false'}`), new LiteralExpression(`HelpMessage = "${escapeString(enableSystemAssignedIdentity.description || '.')}"`)] }));
if (length(vParam.alias) > 0) {
enableSystemAssignedIdentity.add(new Attribute(Alias, { parameters: vParam.alias.map(x => '"' + x + '"') }));
}
this.add(enableSystemAssignedIdentity);
continue;
}
if (vParam.name === 'IdentityUserAssignedIdentity' || vParam.name === 'UserAssignedIdentity') {
if (this.flattenUserAssignedIdentity && vParam.schema.type === 'dictionary') {
const userAssignedIdentity = new Property('UserAssignedIdentity', dotnet.StringArray);
userAssignedIdentity.description = 'The array of user assigned identities associated with the resource. The elements in array will be ARM resource ids in the form: \'/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}.\'';
userAssignedIdentity.add(new Attribute(ParameterAttribute, { parameters: [new LiteralExpression(`Mandatory = ${vParam.required ? 'true' : 'false'}`), new LiteralExpression(`HelpMessage = "${escapeString(userAssignedIdentity.description || '.')}"`)] }));
userAssignedIdentity.add(new Attribute(AllowEmptyCollectionAttribute));
if (length(vParam.alias) > 0) {
userAssignedIdentity.add(new Attribute(Alias, { parameters: vParam.alias.map(x => '"' + x + '"') }));
}
this.add(userAssignedIdentity);
continue;
}
}
cmdletParameter = new Property(vParam.name, propertyType, {
get: toExpression(`${expandedBodyParameter.value}.${getVirtualPropertyName((<any>vParam.origin)) || vParam.origin.name}${!nullable ? '' : ` ?? ${propertyType.defaultOfType}`}`), // /* ${inspect(vParam.origin)} */
// get: toExpression(`null == ${expandedBodyParameter.value}.${vParam.origin.name} ? ${propertyType.defaultOfType} : (${propertyType.declaration}) ${expandedBodyParameter.value}.${vParam.origin.name}`),
set: toExpression(`${expandedBodyParameter.value}.${getVirtualPropertyName((<any>vParam.origin)) || vParam.origin.name} = value`),
new: PropertiesRequiringNew.has(vParam.name) ? Modifier.New : Modifier.None
});
} else {
const fixedArrayPropertyType = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, true, this.state, true);
cmdletParameter = new Property(vParam.name, fixedArrayPropertyType, {
get: toExpression(`${expandedBodyParameter.value}.${getVirtualPropertyName((<any>vParam.origin)) || vParam.origin.name}?.ToArray()${` ?? ${fixedArrayPropertyType.defaultOfType}`}`),
set: toExpression(`${expandedBodyParameter.value}.${getVirtualPropertyName((<any>vParam.origin)) || vParam.origin.name} = (value != null ? new ${propertyType.declaration}(value) : null)`),
new: PropertiesRequiringNew.has(vParam.name) ? Modifier.New : Modifier.None
});
}
if (vParam.schema.language.csharp?.byReference) {
// this parameter's schema is marked as 'by-reference' which means we should
// tag it with an ExportAs attribute for the I*Reference type.
cmdletParameter.add(new Attribute(ExportAsAttribute, { parameters: [`typeof(${vParam.schema.language.csharp.referenceInterface})`] }));
}
if (vParam.schema.type === SchemaType.Array) {
//skip-for-time-being
// if ((<ArraySchema>vParam.schema). && vParam.schema.items.details.csharp.byReference) {
// cmdletParameter.add(new Attribute(ExportAsAttribute, { parameters: [`typeof(${vParam.schema.items.details.csharp.referenceInterface}[])`] }));
// }
cmdletParameter.add(new Attribute(AllowEmptyCollectionAttribute));
}
const dictSchema = vSchema.type === SchemaType.Dictionary ? vSchema :
vSchema.type === SchemaType.Object ? (<ObjectSchema>vSchema).parents?.immediate?.find((s) => s.type === SchemaType.Dictionary) :
undefined;
if (dictSchema) {
// we have to figure out if this is a standalone dictionary or a hybrid object/dictionary.
// if it's a hybrid, we have to create another parameter like -<XXX>AdditionalProperties and have that dump the contents into the dictionary
// if it's a standalone dictionary, we can just use hashtable instead
if (length((<ObjectSchema>vSchema).properties) === 0) {
// it's a pure dictionary
// add an attribute for changing the exported type.
cmdletParameter.add(new Attribute(ExportAsAttribute, { parameters: [`typeof(${System.Collections.Hashtable})`] }));
} else {
// it's a hybrid. We need to add an additional property that puts its items into the target container
}
}
const desc = (vParam.description || '.').replace(/[\r\n]/gm, '');
cmdletParameter.description = desc;
// check if this parameter is a byte array, which would indicate that it should really be a file input
if (cmdletParameter.type.declaration === dotnet.Binary.declaration) {
// set the generated parameter to internal
cmdletParameter.setAccess = Access.Internal;
cmdletParameter.getAccess = Access.Internal;
// create a InputFileXXX for the parameter
const ifname = vParam.name.toLowerCase() === 'value' ? 'InputFile' : pascalCase([vParam.name, 'Input', 'File']);
const inputFileParameter = new Property(ifname, dotnet.String, {
// get: toExpression(`${expandedBodyParameter.value}.${vParam.origin.name}${vParam.required ? '' : ` ?? ${propertyType.defaultOfType}`}`),
set: function* () {
const provider = Local('provider');
provider.initializer = undefined;
const paths = Local('paths', `this.SessionState.Path.GetResolvedProviderPathFromPSPath(value, out ${provider.declarationExpression})`);
yield paths.declarationStatement;
yield If(`${provider.value}.Name != "FileSystem" || ${paths.value}.Count == 0`, 'ThrowTerminatingError( new System.Management.Automation.ErrorRecord(new global::System.Exception("Invalid input path."),string.Empty, global::System.Management.Automation.ErrorCategory.InvalidArgument, value) );');
yield If(`${paths.value}.Count > 1`, 'ThrowTerminatingError( new System.Management.Automation.ErrorRecord(new global::System.Exception("Multiple input paths not allowed."),string.Empty, global::System.Management.Automation.ErrorCategory.InvalidArgument, value) );');
yield cmdletParameter.assign(`global::System.IO.File.ReadAllBytes(${paths.value}[0])`);
},
description: `Input File for ${cmdletParameter.name} (${escapeString(desc)})`
});
inputFileParameter.add(new Attribute(ParameterAttribute, { parameters: [new LiteralExpression(`Mandatory = ${vParam.required ? 'true' : 'false'}`), new LiteralExpression(`HelpMessage = "Input File for ${cmdletParameter.name} (${escapeString(desc || '.')})"`)] }));
inputFileParameter.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Body`] }));
if (length(vParam.alias) > 0) {
inputFileParameter.add(new Attribute(Alias, { parameters: vParam.alias.map(x => '"' + x + '"') }));
}
$this.add(inputFileParameter);
} else {
cmdletParameter.add(new Attribute(ParameterAttribute, { parameters: [new LiteralExpression(`Mandatory = ${vParam.required ? 'true' : 'false'}`), new LiteralExpression(`HelpMessage = "${escapeString(desc || '.')}"`)] }));
cmdletParameter.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Body`] }));
NewAddInfoAttribute(cmdletParameter, propertyType, !!vParam.required, false, desc, (<NewVirtualProperty>vParam.origin).property.serializedName);
NewAddCompleterInfo(cmdletParameter, vParam);
addParameterBreakingChange(cmdletParameter, vParam);
addParameterPreviewMessage(cmdletParameter, vParam);
addDefaultInfo(cmdletParameter, vParam);
this.addDoNotExport(cmdletParameter, vParam);
}
const addArgumentCompleter = isEnumImplementation(vParam.schema) || propertyType instanceof ArrayOf && isEnumImplementation((<ArraySchema>propertyType.schema).elementType);
if (addArgumentCompleter) {
propertyType instanceof ArrayOf ? addPSArgumentCompleterAttribute(cmdletParameter, (<ArraySchema>propertyType?.schema)?.elementType) : addPSArgumentCompleterAttribute(cmdletParameter, vParam.schema);
}
// add aliases if there is any
if (length(vParam.alias) > 0) {
cmdletParameter.add(new Attribute(Alias, { parameters: vParam.alias.map(x => '"' + x + '"') }));
}
this.add(cmdletParameter);
}
const paramDictSchema = parameter.schema.type === SchemaType.Dictionary ? parameter.schema :
parameter.schema.type === SchemaType.Object ? (<ObjectSchema>parameter.schema).parents?.immediate?.find((s) => s.type === SchemaType.Dictionary) :
undefined;
if (paramDictSchema) {
// if there is an additional properties on this type
// add a hashtable parameter for additionalProperties
let apPropName = '';
const options = ['AdditionalProperties', 'MoreProperties', 'ExtendedProperties', 'Properties'];
for (const n of options) {
if (this.properties.find(each => each.name === n)) {
continue;
}
apPropName = n;
break;
}
this.apProp = this.add(new Property(apPropName, System.Collections.Hashtable));
this.apProp.add(new Attribute(ParameterAttribute, {
parameters: ['Mandatory = false', 'HelpMessage = "Additional Parameters"']
}));
this.bodyParameterInfo = {
type: {
declaration: parameter.schema.language.csharp?.fullname
},
valueType: (<DictionarySchema>paramDictSchema).elementType.type === SchemaType.Any ? System.Object :
this.state.project.schemaDefinitionResolver.resolveTypeDeclaration((<DictionarySchema>paramDictSchema).elementType, true, this.state, this.state.project.fixedArray)
};
}
this.bodyParameter = expandedBodyParameter;
continue;
}
}
if (this.isViaIdentity) {
const viaIdentityRegex = /ViaIdentity\d?/g;
this.inputObjectParameterName = `${this.name.split(viaIdentityRegex)[1].split('Expanded')[0]}InputObject`;
// add in the pipeline parameter for the identity
const idschema = values(this.state.project.model.schemas.objects).first(each => each.language.default.uid === 'universal-parameter-type');
const idtd = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(idschema, true, this.state, this.state.project.fixedArray);
const idParam = this.add(new BackedProperty(this.inputObjectParameterName, idtd, {
description: 'Identity Parameter'
}));
const parameters = [new LiteralExpression('Mandatory = true'), new LiteralExpression('HelpMessage = "Identity Parameter"'), new LiteralExpression('ValueFromPipeline = true')];
idParam.add(new Attribute(ParameterAttribute, { parameters }));
idParam.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Path`] }));
}
for (const vParam of values(vps.operation)) {
if (vParam.name === 'Host') {
// skip 'Host'
continue;
}
let regularCmdletParameter: BackedProperty;
let origin = null;
let propertyType = null;
if (vParam.type) {
// Handle parameters added through directives
regularCmdletParameter = this.add(new BackedProperty(vParam.name, new ClassType('', vParam.type), {
description: vParam.description
}));
} else {
const vSchema = vParam.schema;
propertyType = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(vSchema, true, this.state, this.state.project.fixedArray);
origin = <NewIParameter>vParam.origin;
regularCmdletParameter = (this.state.project.azure && vParam.name === 'SubscriptionId' && operation.details.csharp.verb.toLowerCase() === 'get') ?
// special case for subscription id
this.add(new BackedProperty(vParam.name, dotnet.StringArray, {
metadata: {
parameterDefinition: origin.details.csharp.httpParameter
},
description: vParam.description
})) :
// everything else
this.add(new BackedProperty(vParam.name, propertyType, {
metadata: {
parameterDefinition: origin.details.csharp.httpParameter
},
description: vParam.description
}));
const dictSchema = vSchema.type === SchemaType.Dictionary ? vSchema :
vSchema.type === SchemaType.Object ? (<ObjectSchema>vSchema).parents?.immediate?.find((s) => s.type === SchemaType.Dictionary) :
undefined;
if (dictSchema) {
// we have to figure out if this is a standalone dictionary or a hybrid object/dictionary.
// if it's a hybrid, we have to create another parameter like -<XXX>AdditionalProperties and have that dump the contents into the dictionary
// if it's a standalone dictionary, we can just use hashtable instead
if (length((<ObjectSchema>vSchema).properties) === 0) {
// it's a pure dictionary
// change the property type to hashtable.
// add an attribute to change the exported type.
regularCmdletParameter.add(new Attribute(ExportAsAttribute, { parameters: [`typeof(${System.Collections.Hashtable})`] }));
} else {
// it's a hybrid. We need to add an additional property that puts its items into the target container
}
}
}
this.thingsToSerialize.push(regularCmdletParameter);
const parameters = [new LiteralExpression(`Mandatory = ${vParam.required ? 'true' : 'false'}`), new LiteralExpression(`HelpMessage = "${escapeString(vParam.description) || '.'}"`)];
if (!!origin && origin.details.csharp.isBodyParameter) {
parameters.push(new LiteralExpression('ValueFromPipeline = true'));
this.bodyParameter = regularCmdletParameter;
}
regularCmdletParameter.add(new Attribute(ParameterAttribute, { parameters }));
if (!vParam.type && vParam.schema.type === SchemaType.Array) {
regularCmdletParameter.add(new Attribute(AllowEmptyCollectionAttribute));
}
if (!!origin && !!propertyType) {
NewAddInfoAttribute(regularCmdletParameter, propertyType, vParam.required ?? false, false, vParam.description, origin.name);
}
NewAddCompleterInfo(regularCmdletParameter, vParam);
addParameterBreakingChange(regularCmdletParameter, vParam);
addParameterPreviewMessage(regularCmdletParameter, vParam);
addDefaultInfo(regularCmdletParameter, vParam);
this.addDoNotExport(regularCmdletParameter, vParam);
// add aliases if there is any
if (length(vParam.alias) > 0) {
regularCmdletParameter.add(new Attribute(Alias, { parameters: vParam.alias.map(x => '"' + x + '"') }));
}
const httpParam = origin ? origin.details.csharp.httpParameter : null;
//const uid = httpParam ? httpParam.details.csharp.uid : 'no-parameter';
if (httpParam) {
// xichen: Is it safe to compare by csharp serializedName? Because we no longer have uid
const cat = this.apiCall.parameters?.find((param) => !param.language.csharp?.constantValue && param.language.csharp?.serializedName === httpParam.language.csharp?.serializedName);
if (cat) {
regularCmdletParameter.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.${pascalCase((cat.protocol.http?.in))}`] }));
}
} else {
const isBodyParameter = origin ? origin.details.csharp.isBodyParameter : false;
if (isBodyParameter) {
regularCmdletParameter.add(
new Attribute(CategoryAttribute, {
parameters: [`${ParameterCategory}.Body`],
})
);
}
}
const addArgumentCompleter = isEnumImplementation(vParam.schema) || propertyType instanceof ArrayOf && isEnumImplementation((<ArraySchema>propertyType.schema).elementType);
if (addArgumentCompleter) {
propertyType instanceof ArrayOf ? addPSArgumentCompleterAttribute(regularCmdletParameter, (<ArraySchema>propertyType?.schema)?.elementType) : addPSArgumentCompleterAttribute(regularCmdletParameter, vParam.schema);
}
}
const ifmatch = this.properties.find((v) => v.name.toLowerCase() === 'ifmatch');
if (ifmatch) {
//no sure why there is an empty block
}
}
/**
* Add `DoNotExportAttribute` to parameters that should be hidden.
* @param cmdletParameter parameter in the variant.
* @param vParam parameter in the code model.
* @see DoNotExportAttribute
*/
private addDoNotExport(cmdletParameter: Property, vParam: NewVirtualParameter) {
if (vParam.hidden) {
this.state.message({
Channel: Channel.Debug,
Text: `[DIRECTIVE] Applied 'hide' directive to parameter ${cmdletParameter.name}. Added attribute ${DoNotExportAttribute.declaration} to parameter.`,
});
cmdletParameter.add(new Attribute(DoNotExportAttribute));
}
}
private NewAddClassAttributes(operation: CommandOperation, variantName: string) {
const cmdletAttribParams: Array<ExpressionOrLiteral> = [
category[operation.details.csharp.verb] ? verbEnum(category[operation.details.csharp.verb], operation.details.csharp.verb) : `"${operation.details.csharp.verb}"`,
new StringExpression(variantName)
];
if (isWritableCmdlet(operation) && !operation.details.csharp.supportShouldProcess) {
cmdletAttribParams.push('SupportsShouldProcess = true');
}
if (this.clientsidePagination) {
cmdletAttribParams.push('SupportsPaging = true');
}
if (operation.details.csharp.hidden) {
this.add(new Attribute(InternalExportAttribute));
const noun = `${operation.details.csharp.subjectPrefix}${operation.details.csharp.subject}`;
const cmdletName = `${operation.details.csharp.verb}-${noun}${operation.details.csharp.name ? `_${operation.details.csharp.name}` : ''}`;
this.state.message({ Channel: Channel.Debug, Text: `[DIRECTIVE] Applied 'hide' directive to ${cmdletName}. Added attribute ${InternalExportAttribute.declaration} to cmdlet.` });
}
this.add(new Attribute(CmdletAttribute, { parameters: cmdletAttribParams }));
// add alias attribute if there is any aliases for this cmdlet
if (length(operation.details.csharp.alias) > 0) {
this.add(new Attribute(Alias, { parameters: operation.details.csharp.alias.map((x: string) => '"' + x + '"') }));
}
let shouldAddPassThru = false;
// set to hold the output types
const outputTypes = new Set<string>();
for (const httpOperation of values(operation.callGraph)) {
const pageableInfo = httpOperation.language.csharp?.pageable;
const v = httpOperation.responses && httpOperation.responses.length > 0 && httpOperation.responses[0] instanceof SchemaResponse;
// Add this for binary response in m4
for (const binary of values(httpOperation.responses).selectNonNullable(each => (<BinaryResponse>each).binary)) {
if (binary) {
// if this is a stream, skip the output type.
this.hasStreamOutput = true;
shouldAddPassThru = true;
outputTypes.add(`typeof(${dotnet.Bool})`);
}
}
for (const schema of values(httpOperation.responses).selectNonNullable(each => (<SchemaResponse>each).schema)) {
const props = NewGetAllProperties(schema);
// does the target type just wrap a single output?
const resultSchema = <NewSchema>schema;
// make sure return type for boolean stays boolean!
if (resultSchema.type === SchemaType.Boolean ||
(resultSchema.type === SchemaType.Choice && (<any>resultSchema).choiceType.type === SchemaType.Boolean && (<ChoiceSchema>resultSchema).choices.length === 1) ||
(resultSchema.type === SchemaType.SealedChoice && (<any>resultSchema).choiceType.type === SchemaType.Boolean && (<SealedChoiceSchema>resultSchema).choices.length === 1)) {
outputTypes.add(`typeof(${dotnet.Bool})`);
} else {
const typeDeclaration = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(resultSchema, true, this.state, this.state.project.fixedArray);
if (typeDeclaration.declaration === System.IO.Stream.declaration || typeDeclaration.declaration === dotnet.Binary.declaration) {
// if this is a stream, skip the output type.
this.hasStreamOutput = true;
shouldAddPassThru = true;
outputTypes.add(`typeof(${dotnet.Bool})`);
} else {
let type = '';
if (typeDeclaration instanceof ArrayOf) {
type = typeDeclaration.elementTypeDeclaration;
} else if (pageableInfo && pageableInfo.responseType === 'pageable') {
if (typeDeclaration === undefined || ((<ObjectSchema>typeDeclaration.schema).properties?.find(p => p.serializedName === pageableInfo.itemName) === undefined
&& (<ObjectSchema>typeDeclaration.schema).parents?.all.find(s => isObjectSchema(s) && s.properties?.find((p => p.serializedName === pageableInfo.itemName)) === undefined))) {
//skip-for-time-being, since operationId does not support in m4 any more
//throw new Error(`\n\nOn operation:\n '${httpOperation.operationId}' at '${httpOperation.path}'\n -- you have used 'x-ms-pageable' and there is no property name '${pageableInfo.itemName}' that is an array.\n\n`);
throw new Error('An error needs to be more specific');
}
const nestedSchema = ((<ObjectSchema>typeDeclaration.schema).properties?.find(p => p.serializedName === pageableInfo.itemName)
|| (<ObjectSchema>(<ObjectSchema>typeDeclaration.schema).parents?.all.find(s => isObjectSchema(s) && s.properties?.find((p => p.serializedName === pageableInfo.itemName)))).properties?.find((p => p.serializedName === pageableInfo.itemName)))?.schema;
const nestedTypeDeclaration = this.state.project.schemaDefinitionResolver.resolveTypeDeclaration(nestedSchema, true, this.state, this.state.project.fixedArray);
type = (<ArrayOf>nestedTypeDeclaration).elementTypeDeclaration;
} else {
type = typeDeclaration.declaration;
}
// check if this is a stream output
if (type) {
outputTypes.add(`typeof(${type})`);
}
}
}
}
}
// if any response does not return,
// the cmdlet should have a PassThru parameter
shouldAddPassThru = shouldAddPassThru || values(operation.callGraph)
.selectMany(httpOperation => values((httpOperation.responses || []).concat(httpOperation.exceptions || [])))
//.selectMany(responsesItem => responsesItem.value)
.any(value => (<SchemaResponse>value).schema === undefined);
if (outputTypes.size === 0) {
outputTypes.add(`typeof(${dotnet.Bool})`);
}
//add breaking change attributes for cmdlet, variant, output type
if (operation.details.csharp.breakingChange) {
const breakingChange = operation.details.csharp.breakingChange;
if (breakingChange.cmdlet) {
const parameters = [];
if (!breakingChange.cmdlet.deprecateByVersion || !breakingChange.cmdlet.deprecateByAzVersion) {
throw new Error('Cmdlet breaking change requires both \'deprecateByVersion\' and \'deprecateByAzVersion\', please refer to https://github.com/Azure/azure-powershell/blob/main/documentation/development-docs/breakingchange-for-autogen-module.md for more details.');
}
parameters.push(`"${breakingChange.cmdlet.deprecateByAzVersion}"`);
parameters.push(`"${breakingChange.cmdlet.deprecateByVersion}"`);
if (breakingChange.cmdlet.changeInEfectByDate) parameters.push(`"${breakingChange.cmdlet.changeInEfectByDate}"`);
if (breakingChange.cmdlet.replacement) parameters.push(`ReplacementCmdletName = "${breakingChange.cmdlet.replacement}"`);
if (breakingChange.cmdlet.changeDescription) parameters.push(`ChangeDescription = "${breakingChange.cmdlet.changeDescription}"`);
this.add(new Attribute(ClientRuntime.CmdletBreakingChangeAttribute, { parameters: parameters }));
}
if (breakingChange.variant) {
const parameters = [];
parameters.push(`new string[] {"${breakingChange.variant.name}"}`);
if (!breakingChange.variant.deprecateByVersion || !breakingChange.variant.deprecateByAzVersion) {
throw new Error('Cmdlet breaking change requires both \'deprecateByVersion\' and \'deprecateByAzVersion\', please refer to https://github.com/Azure/azure-powershell/blob/main/documentation/development-docs/breakingchange-for-autogen-module.md for more details.');
}
parameters.push(`"${breakingChange.variant.deprecateByAzVersion}"`);
parameters.push(`"${breakingChange.variant.deprecateByVersion}"`);
if (breakingChange.variant.changeInEfectByDate) parameters.push(`"${breakingChange.variant.changeInEfectByDate}"`);
if (breakingChange.variant.changeDescription) parameters.push(`ChangeDescription = "${breakingChange.variant.changeDescription}"`);
this.add(new Attribute(ClientRuntime.ParameterSetBreakingChangeAttribute, { parameters: parameters }));
}
if (breakingChange.output) {
const parameters = [];
// if deprecated output types are set in directive, use it
if (breakingChange.output.deprecatedCmdLetOutputType) {
parameters.push(`"${breakingChange.output.deprecatedCmdLetOutputType}"`);
} else {
parameters.push(`"${outputTypes.values().next().value.replace(/typeof\((.*)\)/, '$1')}"`);
}
if (!breakingChange.output.deprecateByVersion || !breakingChange.output.deprecateByAzVersion) {
throw new Error('Cmdlet breaking change requires both \'deprecateByVersion\' and \'deprecateByAzVersion\', please refer to https://github.com/Azure/azure-powershell/blob/main/documentation/development-docs/breakingchange-for-autogen-module.md for more details.');
}
parameters.push(`"${breakingChange.output.deprecateByAzVersion}"`);
parameters.push(`"${breakingChange.output.deprecateByVersion}"`);
if (breakingChange.output.changeInEfectByDate) parameters.push(`"${breakingChange.output.changeInEfectByDate}"`);
if (breakingChange.output.replacement) parameters.push(`ReplacementCmdletOutputType = "${breakingChange.output.replacement}"`);
if (breakingChange.output.deprecatedOutputProperties) {
const properties: Array<string> = Object.assign([], breakingChange.output.deprecatedOutputProperties);
properties.forEach((element, index) => properties[index] = '"' + element + '"');
parameters.push(`DeprecatedOutputProperties = new string[] {${properties.join(',')}}`);
}
if (breakingChange.output.newOutputProperties) {
const properties: Array<string> = Object.assign([], breakingChange.output.newOutputProperties);
properties.forEach((element, index) => properties[index] = '"' + element + '"');
parameters.push(`NewOutputProperties = new string[] {${properties.join(',')} } `);
}
if (breakingChange.output.changeDescription) parameters.push(`ChangeDescription = "${breakingChange.output.changeDescription}"`);
this.add(new Attribute(ClientRuntime.OutputBreakingChangeAttribute, { parameters: parameters }));
}
}
//add preview message attribute for cmdlet
if (operation.details.csharp.previewAnnouncement) {
const parameters = [];
parameters.push(`"${operation.details.csharp.previewAnnouncement.previewMessage}"`);
if (operation.details.csharp.previewAnnouncement.estimatedGaDate) parameters.push(`"${operation.details.csharp.previewAnnouncement.estimatedGaDate}"`);
this.add(new Attribute(ClientRuntime.PreviewMessageAttribute, { parameters: parameters }));
}
this.add(new Attribute(OutputTypeAttribute, { parameters: [...outputTypes] }));
if (shouldAddPassThru) {
const passThru = this.add(new Property('PassThru', SwitchParameter, { description: 'When specified, forces the cmdlet return a \'bool\' given that there isn\'t a return type by default.' }));
passThru.add(new Attribute(ParameterAttribute, { parameters: ['Mandatory = false', 'HelpMessage = "Returns true when the command succeeds"'] }));
passThru.add(new Attribute(CategoryAttribute, { parameters: [`${ParameterCategory}.Runtime`] }));
}
this.add(new Attribute(DescriptionAttribute, { parameters: [new StringExpression(this.description)] }));
// If defines externalDocs for operation
if (operation.details.default.externalDocs) {
this.add(new Attribute(ExternalDocsAttribute, {
parameters: [`${new StringExpression(this.operation.details.default.externalDocs?.url ?? '')}`,
`${new StringExpression(this.operation.details.default.externalDocs?.description ?? '')}`]
}));
}
this.add(new Attribute(GeneratedAttribute));
if (operation.extensions && operation.extensions['x-ms-metadata'] && operation.extensions['x-ms-metadata'].profiles) {
const profileNames = Object.keys(operation.extensions && operation.extensions['x-ms-metadata'].profiles);
// wrap profile names
profileNames.forEach((element, index) => {
profileNames[index] = `"${element}"`;
});
this.add(new Attribute(ProfileAttribute, { parameters: [...profileNames] }));
}
if (this.operation.callGraph.length === 1) {
this.add(new Attribute(HttpPathAttribute, { parameters: [`Path = "${this.apiCall.requests?.[0].protocol?.http?.path}"`, `ApiVersion = "${this.apiCall.apiVersions?.[0].version}"`] }));
}
if (variantName.includes('ViaJsonString') || variantName.includes('ViaJsonFilePath')) {
this.add(new Attribute(NotSuggestDefaultParameterSetAttribute));
}
}
}