In OData, enums represent a subset of the nominal type they rely on, and are especially useful in cases where certain properties have predefined, limited options.
<EnumType Name="color">
<Member Name="Red" Value="0" />
<Member Name="Green" Value="1" />
<Member Name="Blue" Value="2" />
</EnumType>
- Our SDK generators will translate the enum to the best representation of the target programming language, resulting in a better developer experience and free client side validation
- Adding a new value requires to go through a (generally fast) API Review
- If the enum is not evolvable, adding a new value is a breaking change and will generally not be allowed
Enumerations are a good alternative to Booleans when one of the two values (true
, false
) conveys other possible values not yet conceived. Let's assume we have an publicNotification
type and a property to communicate how to display it:
<ComplexType Name="publicNotification">
<Property Name="title" Type="Edm.String" />
<Property Name="message" Type="Edm.String" />
<Property Name="displayAsTip" Type="Edm.Boolean" />
</ComplexType>
The false
value here merely communicates that the notification shall not be displayed as a tip. What if, in the future, the notification could be displayed as a tip
or alert
, and then in a more distant future, a dialog
option is viable?
With the current model, the only way is to add more boolean properties to convey the new information:
<ComplexType Name="publicNotification">
<Property Name="title" Type="Edm.String" />
<Property Name="message" Type="Edm.String" />
<Property Name="displayAsTip" Type="Edm.Boolean" />
+ <Property Name="displayAsAlert" Type="Edm.Boolean" />
+ <Property Name="displayAsDialog" Type="Edm.Boolean" />
</ComplexType>
Additionally speaking, the workload will now also have to validate the data structure and make sure that only one of the 3 values is true
By using an evolvable enum, instead, all we need to do is to add new members:
<ComplexType Name="publicNotification">
<Property Name="title" Type="Edm.String" />
<Property Name="message" Type="Edm.String" />
+ <Property Name="displayMethod" Type="microsoft.graph.displayMethod" />
- <Property Name="displayAsTip" Type="Edm.Boolean" />
- <Property Name="displayAsAlert" Type="Edm.Boolean" />
- <Property Name="displayAsDialog" Type="Edm.Boolean" />
</ComplexType>
<EnumType Name="displayMethod">
<Member Name="tip" Value="0" />
<Member Name="unknownFutureValue" Value="1" />
<Member Name="alert" Value="2" />
<Member Name="dialog" Value="3" />
</EnumType>
Similarly speaking, if you find yourself using a nullable
Enum, that is a indication that maybe what you are trying to model is something that has 3 states and an enum is more appropraite. For instance, let's assume we have a boolean property called syncEnabled
, where null
means that the value is undefined and inherited from the general tenant configuration. Instead of modelling like a boolean:
<Property Name="syncEnabled" Type="Edm.Boolean" Nullable="true"/>
An enum not only better conveys the message:
<EnumType Name="syncState">
<Member Name="enabled" Value="0" />
<Member Name="disabled" Value="1" />
<Member Name="tenantInherit" Value="2" />
<Member Name="unknownFutureValue" Value="3" />
</EnumType>
but it is also open for future scenarios:
<EnumType Name="syncState">
<Member Name="enabled" Value="0" />
<Member Name="disabled" Value="1" />
<Member Name="tenantInherit" Value="2" />
<Member Name="unknownFutureValue" Value="3" />
+ <Member Name="groupInherit" Value="4" />
</EnumType>
Additionally speaking, depending on the situation, a nullable enum can very likely be avoided by adding a none
member.
If used, EnumType
names should be singular if the are non-flags enums, and the names should be plural if they are flags enums.
In case an enum can have multiple values at the same time the tentation is to model the property as a collection of Enums:
<Property Name="displayMethods" Type="Collection(displayMethod)"/>
However, Flagged Enums can model this use case scenario:
- <EnumType Name="displayMethod">
+ <EnumType Name="displayMethod" isFlag="true">
- <Member Name="tip" Value="0" />
+ <Member Name="tip" Value="1" />
- <Member Name="unknownFutureValue" Value="1" />
+ <Member Name="unknownFutureValue" Value="2" />
- <Member Name="alert" Value="2" />
+ <Member Name="alert" Value="4" />
- <Member Name="dialog" Value="3" />
+ <Member Name="dialog" Value="8" />
</EnumType>
With such enum, customers can select multiple values in a single field:
displayMethod = tip | alert
In cases where two properties want to use the same conceptual EnumType
, but one property is a collection while the other is single-values, the model should define two separate EnumType
s, one being a non-flags enum with a singular name and the other marked as a flags enum with its name being the plural form of the non-flags enum.
There are occasions where one API will want to use a non-flag enum, but another API will want a flags enum.
For example, the displayMethod
example above may have one API that is configuring which display methods to use, and another API which is configuring that particular display method.
In this case, the first API will want a flags enum, but the second API will want to only allow configuring one display method at a time, and will therefore prefer a non-flags enum.
Two enum types should be defined, one as a flags enum and the other as a non-flags enum. The flags enum should be named such that it is plural, and the non-flags enum should be named such that it is singular. The two types should be kept in sync with each other.