in powershell/cmdlets/class.ts [1292:1572]
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);
}
}