OneOf and AnyOf
APIMatic's SDKs fully support the oneOf
and anyOf
types defined in OpenAPI specifications, allowing users to define flexible request parameters and response payloads that can accept or return different types of data. By leveraging these union types, users can represent alternative schemas or dynamic data structures in their APIs. This capability enhances the accuracy and reliability of API integrations, ensuring that SDKs can seamlessly handle complex API behaviors with improved validation and flexibility.
Configure OneOf and AnyOf in your OpenAPI Definition
Here is an example of OneOf
and AnyOf
types defined in OpenAPI definition:
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.
SDK Example
Here is how the parameter initialization would look like when calling the endpoint,
- TypeScript
- Java
- Python
- .NET
- Go
- PHP
- Ruby
const body: CreateUserBody = {
accountId: 'A001',
firstName: 'John',
lastName: 'Doe',
};
async () => {
const { result, ...httpResponse } = await apiController.createUser(body);
};
CreateUserBody accountantOrManager = CreateUserBody.fromAccountant(
new Accountant.Builder(
"A001",
"John",
"Doe"
)
.build()
);
CreateUserResponse result = await apiController.CreateUserAsync(accountantOrManager);
body = Accountant(
account_id='A001',
first_name='John',
last_name='Doe'
)
response = client.user_controller.create_user(body)
CreateUserBody accountantOrManager = CreateUserBody.FromAccountant(
new Accountant
{
AccountId = "A001",
FirstName = "John",
LastName = "Doe",
}
);
CreateUserResponse result = await apiController.CreateUserAsync(accountantOrManager);
accountantOrManager := models.CreateUserBodyContainer.
FromAccountant(models.Accountant{
accountId: "A001",
firstName: "John",
lastName: "Doe",
})
apiResponse, err := apiController.CreateUser(ctx, accountantOrManager)
$accountantOrManager = AccountantBuilder::init(
"A001",
"John",
"Doe"
)->build();
$result = $client->getApiController()->createUser($accountantOrManager);
body = Accountant.new(
'accountantId',
'john',
'doe'
)
response = client.user.create_user(body)
In the code snippet above, the CreateUser
method returns a response payload that can be either an AccountantResponse
or a ManagerResponse
instance. With the introduction of support for OneOf
and AnyOf
, the SDK explicitly validates that the response matches one of these specified types, ensuring more accurate and reliable response handling, as shown below:
- TypeScript
- Java
- Python
- .NET
- Go
- PHP
- Ruby
async () => {
const { result, ...httpResponse } = await apiController.createUser(body);
if (CreateUserResponse.isAccountantResponse(result)) {
// Use the result narrowed down to AccountantResponse type.
} else if (CreateUserResponse.isManagerResponse(result)) {
// Use the result narrowed down to ManagerResponse type.
} else {
// Result is narrowed down to type 'never'.
}
};
apiController.createUserAsync().thenAccept(result -> {
result.match(new CreateUserResponse.Cases<Void>() {
@Override
public Void accountantResponse(AccountantResponse accountantResponse) {
System.out.println(accountantResponse);
return null;
}
@Override
public Void managerResponse(ManagerResponse managerResponse) {
System.out.println(managerResponse);
return null;
}
});
}).exceptionally(exception -> {
// TODO failure callback handler
exception.printStackTrace();
return null;
});
return super().new_api_call_builder.response(
ResponseHandler()
.deserializer(lambda value: APIHelper.deserialize_union_type(
UnionTypeLookUp.get('CreateUserResponse'), value))
).execute()
CreateUserResponse result = await apiController.CreateUserAsync(accountantOrManager);
result.Match<VoidType>(
accountantResponse: @case =>
{
Console.WriteLine(@case);
return null;
},
managerResponse: @case =>
{
Console.WriteLine(@case);
return null;
});
apiResponse, err := apiController.CreateUserAsync(ctx, accountantOrManager)
if accountantResponse, isAccountantResponse := apiResponse.Data.AsAccountantResponse(); isAccountantResponse {
fmt.Println(accountantResponse)
} else if managerResponse, isManagerResponse := apiResponse.Data.AsManagerResponse(); isManagerResponse {
fmt.Println(managerResponse)
}
/* @return AccountantResponse|ManagerResponse Response from the API call */
$result = $client->getApiController()->createUser($accountantOrManager);
new_api_call_builder
.response(new_response_handler
.deserializer(proc do |response, should_symbolize|
APIHelper.deserialize_union_type(
UnionTypeLookUp.get(:CreateUserResponse),
response, should_symbolize, true
)
end))
.execute
Validation Errors
Validation errors with helpful messages 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 will enforce type strictness and validation of requests and responses within the SDK. These exceptions will be thrown for:
OneOf types: When either more than one acceptable type matches or no type matches at all against the provided value.
AnyOf types: When no acceptable type matches at all against the provided value.