We are introducing the special oneOf and anyOf types in our CSharp SDKs.
Details
In OpenAPI, any request parameter or response payload could use union types i.e. OneOf/AnyOf, which provides the flexibility of accepting different types of request parameters and returning various types of response payload. Here is an example of an API call using union types,
paths:
/users/create:
post:
summary: Create a new user
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
anyOf:
- $ref: '#/components/schemas/Accountant'
- $ref: '#/components/schemas/Manager'
responses:
'201':
description: User created successfully
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/AccountantResponse'
- $ref: '#/components/schemas/ManagerResponse'
...
The API call, shown in the OpenApi format above, is a post-call that takes in either Accountant
or Manager
as request payload. Previously, union types were not supported therefore the endpoint was expecting the combined schema UsersCreateRequest
as a parameter. Here is how the parameter initialization would look like when calling the endpoint,
- Before
- After
UsersCreateRequest accountantOrManager = new UsersCreateRequest
{
AccountId = "A001",
FirstName = "John",
LastName = "Doe",
ManagerId = "M001",
};
dynamic result = await aPIController.CreateUserAsync(accountantOrManager);
CreateUserBody accountantOrManager = CreateUserBody.FromAccountant(
new Accountant
{
AccountId = "A001",
FirstName = "John",
LastName = "Doe",
}
);
CreateUserResponse result = await aPIController.CreateUserAsync(accountantOrManager);
Now, we have containers that can hold different types of data combined. With these containers, you can check what type of data is inside using the Functions.
You can see an example of this concept in the Match
method.
- Before
- After
dynamic result = await aPIController.CreateUserAsync(accountantOrManager);
if (result is AccountantResponse accountantResponse)
{
// use the accountantResponse
}
if (result is ManagerResponse managerResponse)
{
// use the managerResponse
}
CreateUserResponse result = await aPIController.CreateUserAsync(accountantOrManager);
result.Match<VoidType>(
accountantResponse: @case =>
{
Console.WriteLine(@case);
return null;
},
managerResponse: @case =>
{
Console.WriteLine(@case);
return null;
});
In the above code snippet, CreateUserAsync
will return either an AccountantResponse
or ManagerResponse
instance as the response payload. Previously, it was considered a dynamic response which allowed any type of response payload. Now, with the introduction of OneOf and AnyOf support, we can match that the response can be either AccountantResponse
or ManagerResponse
.
What has Changed?
The CSharp SDK now can handle union types in an endpoint's request and response. The type validation for union types is also added to support the OneOf/AnyOf constraints.
Introducing Containers
Containers are introduced in the CSharp SDK to manage union types. The union types cases are contained in a Container, and this container class exposes the From<#CaseName>
method to set the case value. Containers and case classes are assigned the attributes UnionTypeConverter
and UnionTypeCaseConverter
, respectively. Union-type serialization and deserialization are handled by these converters.
Changes in Models
Now, any model type participating as a union type has the [JsonRequired]
annotation on all the required properties to verify that required properties are present in the model. Number Enum is now annotated with NumberEnumConverter
to serialize and deserialize the number enum.
Changes in Endpoints
Endpoint Doc Changes
The endpoint's doc string has been updated to describe union-type parameters or return types.
- Before
- After
/// <summary>
/// Create a new user.
/// </summary>
/// <param name="body">Required parameter: Example: .</param>
/// <param name="cancellationToken"> cancellationToken. </param>
/// <returns>Returns the dynamic response from the API call.</returns>
public async Task<dynamic> CreateUserAsync(
Models.UsersCreateRequest body,
CancellationToken cancellationToken = default)
/// <summary>
/// Create a new user.
/// </summary>
/// <param name="body">Required parameter: Example: .</param>
/// <param name="cancellationToken"> cancellationToken. </param>
/// <returns>Returns the CreateUserResponse response from the API call.</returns>
public async Task<CreateUserResponse> CreateUserAsync(
CreateUserBody body,
CancellationToken cancellationToken = default)
Validation Exceptions
We have introduced OneOfValidationException
and AnyOfValidationException
which will be thrown whenever an input with an invalid type is provided to an endpoint or invalid typed data is received via a response from the server. This feature will enforce type strictness and validation of requests and responses within the SDK.
Following are the trigger points for raising these exceptions:
OneOfValidationException
: It is raised when either more than one type is matched or no type is matched at all against the provided value.
AnyOfValidationException
: It is raised when no type matches at all against the provided value.
You can explicitly rescue the OneOfValidationException and AnyOfValidationException in the code snippet.
try
{
CreateUserResponse result = await aPIController.CreateUserAsync(accountantOrManager);
result.Match<VoidType>(
accountantResponse: @case =>
{
Console.WriteLine(@case);
return null;
},
managerResponse: @case =>
{
Console.WriteLine(@case);
return null;
});
}
catch (OneOfValidationException e)
{
// TODO: Handle exception here
Console.WriteLine(e.Message);
}
catch (AnyOfValidationException e)
{
// TODO: Handle exception here
Console.WriteLine(e.Message);
}
catch (ApiException e)
{
// TODO: Handle exception here
Console.WriteLine(e.Message);
}
README Changes
Now, the CSharp SDK README will reflect the support for union types in parameter types or response types.
- Before
- After
Portal Changes
We have added the capability to select and configure any of the cases mentioned as union type through our API Code Playground on the portal. With this feature, you can now generate code samples for different cases of union types.