Azure / autorest.typescript

Extension for AutoRest (https://github.com/Azure/autorest) that generates TypeScript code. The transpiled javascript code is isomorphic. It can be run in browser and in node.js environment.
MIT License
177 stars 74 forks source link

Issues #5: CADL Generated Code Issue in EventGrid SDK #1851

Open sarangan12 opened 1 year ago

sarangan12 commented 1 year ago

While working on the EventGrid SDK, generated from the CADL files, I have noticed the following.

In CADL, we have the following CloudEvent Model:

@doc("Properties of an event published to an Azure Messaging EventGrid Namespace topic using the CloudEvent 1.0 Schema.")
model CloudEvent {
    @doc("An identifier for the event. The combination of id and source must be unique for each distinct event.")
    id: string;

    @doc("Identifies the context in which an event happened. The combination of id and source must be unique for each distinct event.")
    source: string;

    @doc("Event data specific to the event type.")
    data?: unknown;

    @doc("Event data specific to the event type, encoded as a base64 string.")
    data_base64?: bytes;

    @doc("Type of event related to the originating occurrence.")
    type: string;

    @doc("The time (in UTC) the event was generated, in RFC3339 format.")
    time?: utcDateTime;

    @doc("The version of the CloudEvents specification which the event uses.")
    specversion: string;

    @doc("Identifies the schema that data adheres to.")
    dataschema?: string;

    @doc("Content type of data value.")
    datacontenttype?: string;

    @doc("This describes the subject of the event in the context of the event producer (identified by source).")
    subject?: string;
}

Now, we have 2 APIs, publishCloudEvent & publishCloudEvents apis:

@doc("Publish Single Cloud Event to namespace topic. In case of success, the server responds with an HTTP 200 status code with an empty JSON object in response. Otherwise, the server can return various error codes. For example, 401: which indicates authorization failure, 403: which indicates quota exceeded or message is too large, 410: which indicates that specific topic is not found, 400: for bad request, and 500: for internal server error. ")
  @route("/topics/{topicName}:publish", {shared: true})
  @post op PublishCloudEvent is Azure.Core.RpcOperation<{
    @doc("content type")
    @header("content-type")
    contentType: "application/cloudevents+json; charset=utf-8";

    @doc("Topic Name.")
    @path
    topicName: string;

    @doc("Single Cloud Event being published.")
    @body 
    event: CloudEvent;
  }, PublishResult>;

@doc("Publish Batch Cloud Event to namespace topic. In case of success, the server responds with an HTTP 200 status code with an empty JSON object in response. Otherwise, the server can return various error codes. For example, 401: which indicates authorization failure, 403: which indicates quota exceeded or message is too large, 410: which indicates that specific topic is not found, 400: for bad request, and 500: for internal server error. ")  
  @route("/topics/{topicName}:publish", {shared: true})
  @post op PublishCloudEvents is Azure.Core.RpcOperation<{
    @doc("content type")
    @header("content-type")
    contentType: "application/cloudevents-batch+json; charset=utf-8";

    @doc("Topic Name.")
    @path
    topicName: string;

    @doc("Array of Cloud Events being published.")
    @body
    events: CloudEvent[];
}, PublishResult>;

Now, if you look at the generated SDK code, it looks like the following:

publishCloudEvent(
    id: string,
    source: string,
    type: string,
    specversion: string,
    topicName: string,
    options: PublishCloudEventOptions = { requestOptions: {} }
  ): Promise<Record<string, any>> {
    return publishCloudEvent(this._client, event, topicName, options);
  }

  publishCloudEvents(
    events: CloudEvent[],
    topicName: string,
    options: PublishCloudEventsOptions = { requestOptions: {} }
  ): Promise<Record<string, any>> {
    return publishCloudEvents(this._client, events, topicName, options);
  }

Now, this is unexpected. Look at the parameters of the publishCloudEvent API. the CloudEvent object is flattened and the mandatory parameters of CloudEvent object are pulled outside and made individual parameters which is not the expected output. The expected output is:

publishCloudEvent(
    event: CloudEvent,
    topicName: string,
    options: PublishCloudEventOptions = { requestOptions: {} }
  ): Promise<Record<string, any>> {
    return publishCloudEvent(this._client, event, topicName, options);
}

Currently, per Jose's suggestion, I am using the following workaround in CADL as:

@doc("Publish Cloud Event Body")
  model PublishCloudEventBody {
    @doc("content type")
    @header("content-type")
    contentType: "application/cloudevents+json; charset=utf-8";

    @doc("Topic Name.")
    @path
    topicName: string;

    @doc("Single Cloud Event being published.")
    @body 
    event: { event: CloudEvent };
  }

@doc("Publish Single Cloud Event to namespace topic. In case of success, the server responds with an HTTP 200 status code with an empty JSON object in response. Otherwise, the server can return various error codes. For example, 401: which indicates authorization failure, 403: which indicates quota exceeded or message is too large, 410: which indicates that specific topic is not found, 400: for bad request, and 500: for internal server error. ")
  @route("/topics/{topicName}:publish", {shared: true})
  @post op PublishCloudEvent is Azure.Core.RpcOperation<PublishCloudEventBody, PublishResult>;

This kind of solves the problem partially. It has other issues in the generated code which I have modified manually. So, we need to find a permanent fix for this issue.

sarangan12 commented 1 year ago

Input file that can be used for testing: https://github.com/Azure/azure-rest-api-specs/blob/feature/eventgrid/typespec/specification/eventgrid/Azure.Messaging.EventGrid/main.tsp