export async function tweakModel()

in powershell/plugins/plugin-tweak-model-azure-v2.ts [18:200]


export async function tweakModel(state: State): Promise<PwshModel> {
  const model = state.model;

  // service.message{ Channel: Channel.Debug, Text: "THIS IS THE AZURE TWEAKER" });

  // TODO:
  // look at models, and extract out any case that has an IRESOURCE, IPROXYRESOURCE, etc.
  // and use the common versions of those models.

  // Is the result marked x-ms-pagable?
  // identify the next link (null means just get the results as an array)
  // if nextLinkName is null, then it won't actually page, but we'd like to unroll the contents anyway.
  for (const group of values(model.operationGroups)) {
    for (const operation of values(group.operations)) {
      if (operation.extensions && operation.extensions[xmsPageable]) {
        // it's marked pagable.
        operation.language.default.pageable = {
          responseType: 'pageable',
          nextLinkName: operation.extensions[xmsPageable].nextLinkName || undefined,
          itemName: operation.extensions[xmsPageable].itemName || 'value',
          operationName: operation.extensions[xmsPageable].operationName || `${operation.language.default.name}Next`,
        };
        continue;
      }

      // let's just check to see if it looks like it's supposed to be a collection
      for (const response of values(operation.responses)) {
        // does the response have a schema?
        // TODO: check schema
        if (getSchema(response)) {
          const schema = getSchema(response);

          // is this just an array response?
          if (schema.type === SchemaType.Array) {
            operation.language.default.pageable = {
              responseType: 'array',
            };
            continue;
          }

          // if it returns an object, let's see what's inside...
          if (schema.type === SchemaType.Object) {
            const objSchema = <ObjectSchema>schema;

            // does it have  a single member that is an array (ie, value :  [...])
            if (length(objSchema.properties) === 1 && !objSchema.parents) {
              const property = objSchema.properties?.[0];
              if (property) {
                if (property.schema.type === SchemaType.Array) {
                  // nested array!
                  operation.language.default.pageable = {
                    responseType: 'nested-array',
                    itemName: property.serializedName,
                  };
                }
                continue;
              }
            }

            // xichen: If response schema has only 2 properties and one of it is nextLink, the other is array

            // does it kinda look like a x-ms-pagable (value/nextlink?)
            if (length(objSchema.properties) === 2 && !objSchema.parents) {
              const hasNextLink = objSchema.properties?.some((prop) => prop.serializedName === 'nextLink');
              if (hasNextLink) {
                const property = objSchema.properties?.find((prop) => prop.serializedName !== 'nextLink');
                if (property) {
                  if (property.schema.type === SchemaType.Array) {
                    // nested array!
                    operation.language.default.pageable = {
                      responseType: 'nested-array',
                      itemName: property.serializedName,
                      nextLinkName: 'nextLink'
                    };
                  }
                  continue;
                }
              }

            }

          }
        }
      }
    }
  }


  // make sure that all operations with lro have an options block.
  for (const group of values(model.operationGroups)) {
    for (const operation of values(group.operations)) {
      if (operation.extensions && operation.extensions['x-ms-long-running-operation']) {
        operation.language.default.asjob = true;

        operation.language.default.lro = operation.extensions['x-ms-long-running-operation-options'] || {
          'final-state-via': 'default'
        };

        // LRO 201 and 202 responses are handled internally, so remove any 201/202 responses in the operation
        operation.responses = (<Array<Response>>(operation.responses)).filter(each => each.protocol.http?.statusCodes[0] !== '201' && each.protocol.http?.statusCodes[0] !== '202');
        //delete operation.responses['201'];
        //delete operation.responses['202'];

        // for lro deletion, we need to add the 200 response if it's not already there.
        if (operation.requests && operation.requests[0].protocol.http?.method === 'delete') {
          if (!operation.responses.find(each => each.protocol.http?.statusCodes[0] === '200')) {
            const response = new Response();
            response.protocol.http = response.protocol.http ?? new Protocol();
            response.protocol.http.statusCodes = ['200'];
            operation.responses.push(response);
          }
        }
      }
    }
  }

  // xichen: Cannot find 'x-ms-metadata' from swagger repo. Are we still using it?

  // Api Version parameter handling for Azure.
  // if there is only a single api-version for the operation, let's just make it a constant
  // otherwise, we need to make it selectable, but default to the 'latest' version there is.
  for (const group of values(model.operationGroups)) {
    for (const operation of values(group.operations)) {
      const apiVersions = operation.apiVersions;
      for (const parameter of values(operation.parameters)) {

        if (parameter.language.default.serializedName === 'api-version') {
          // only set it if it hasn't been set yet.
          // if (parameter.details.default.constantValue) {
          //continue;
          //}

          if (apiVersions) {
            // set the constant value to the first one
            if (length(apiVersions) === 1) {
              parameter.language.default.constantValue = apiVersions[0].version;
              continue;
            }

            // otherwise, the parameter can't have a constant value
            parameter.language.default.constantValue = undefined;

            // mark it so that we can add profile support in the method generation
            parameter.language.default.apiversion = true;
          }
        }
      }
    }
  }

  // when make-sub-resources-byreference is specified, mark models with a writable id as byref.
  if (await state.getValue('azure', false) && await state.getValue('make-sub-resources-byreference', false)) {

    for (const schema of values(model.schemas.objects ?? [])) {
      // find schemas that have an 'id' and are not readonly
      if (values(getAllProperties(schema)).any(prop => prop.serializedName === 'id' && !prop.language.default.readOnly)) {

        // look thru the operations, and the PUT methods 
        for (const group of model.operationGroups) {
          for (const op of values(group.operations)) {
            for (const request of op.requests ?? []) {

              if (request.protocol.http?.method === 'put') {
                for (const response of op.responses ?? []) {
                  // see if any of the responses have the same schema as we are looking for 
                  if (getSchema(response) === schema) {
                    // tell it not to inline that 
                    schema.language.default.byReference = true;
                    break;
                  }
                }
                break;
              }

            }
          }
        }
      }
    }
  }

  return model;
}