private NewImplementResponseMethod()

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);
    }
  }