AsyncAPI enum with specific values

1.8k Views Asked by At

Is it possible to create an enum with specific values in AsyncAPI? In my legacy c# code theres an enum like this

public enum OrderStatus
{

    Ordered = 30,
    UnderDelivery = 40,
    Deliveret  = 50,
    Cancelled = 99
}

I would like to create the same enum, with the same values in AsyncAPI. But it seems that you cannot specify the values in async API. Am I missing something? Is there an alternative solution?

1

There are 1 best solutions below

0
On BEST ANSWER

It is not, or at least not in the general sense.

With AsyncAPI 2.4 you can define payloads with different schema formats (schemaFormat), by default this is an AsyncAPI Schema Object which is a superset of JSON Schema Draft 7. There might exist a schema format you can use that allows this, I just don't recall knowing one, for example not even JDT allows this.

Looking into JSON Schema Draft 7, you will not find it possible to define an enum value with an associated name such as Ordered and UnderDelivery. This means the only way to define it is something like this:

{
  "title": "OrderStatus",
  "type": "number",
  "enum": [
    30,
    40,
    50,
    99
  ]
}

This is because JSON Schema focuses on validating JSON data, and in the JSON world, there is no such thing as an associated name to the enum value. That is entirely related to programming languages.

Solution

There are a couple of solutions and how you can proceed, let me highlight one way I see it could be achieved.

Specification Extensions

If you use the default AsyncAPI 2.4.0 Schema Object, AsyncAPI allows you to add your own extension such as:

{
  "title": "OrderStatus",
  "type": "number",
  "enum": [
    30,
    40,
    50,
    99
  ],
  "x-enumNames": {
    30: "Ordered",
    40: "UnderDelivery",
    50: "Deliveret",
    99: "Cancelled"
  }
}

This will also work if you use pure JSON Schema draft 7 because any extra properties are allowed.

In newer versions of JSON Schema, they introduce something called vocabularies, which could standardize this feature. I started some work around a code generation vocabulary unfortunately, there are many other areas that need to be solved first, so I don't have the bandwidth to personally push it forward.

Generating the enum model

Regardless of how you actually define it with the specification, I expect you want tooling to generate the "accurate" enum model in code generation so here is one way to do it.

Modelina is an open-source tool that is being developed exactly for these cases. I have added an example test case to showcase how it could be done for Modelina v0.59.

Let me break the implementation down:

const generator = new CSharpGenerator({ 
  presets: [
    {
      enum: {
        item: ({model, item, content}) => {
          // Lets see if an enum has any associated names
          const hasCustomName = model.originalInput !== undefined && model.originalInput['x-enumNames'] !== undefined;
          if (hasCustomName) {
            // Lets see if the specific value has an associated name
            const customName = model.originalInput['x-enumNames'][item];
            if (customName !== undefined) {
              return customName;
            }
          }
          return content;
        }
      }
    }
  ]
});

The Csharp generator is being instructed to use a custom preset (can be seen as a Node.js middleware) for the enum renderer. Here we add a middleware to overwrite the name of each enum "item"/value based on whether it has our extension or not.

This results in the following generated model:

public enum OrderStatus
{
  Ordered,
  UnderDelivery,
  Deliveret,
  Cancelled
}

public static class OrderStatusExtensions
{
  public static dynamic GetValue(this OrderStatus enumValue)
  {
    switch (enumValue)
    {
      case OrderStatus.Ordered: return 30;
      case OrderStatus.UnderDelivery: return 40;
      case OrderStatus.Deliveret: return 50;
      case OrderStatus.Cancelled: return 99;
    }
    return null;
  }

  public static OrderStatus? ToOrderStatus(dynamic value)
  {
    switch (value)
    {
      case 30: return OrderStatus.Ordered;
      case 40: return OrderStatus.UnderDelivery;
      case 50: return OrderStatus.Deliveret;
      case 99: return OrderStatus.Cancelled;
    }
    return null;
  }
}