Constructing API Requests
In this project, API requests are constructed using a structured configuration object defined by the ApiRequestOptions type. This approach decouples the definition of an API endpoint from the underlying execution logic (handled by Axios), allowing the generated SDK to describe requests declaratively.
The Request Blueprint: ApiRequestOptions
The ApiRequestOptions type in frontend/src/client/core/ApiRequestOptions.ts serves as the central interface for defining how a request should be built. Every service method in the generated SDK (e.g., ItemsService.readItems) creates an instance of this type and passes it to the core request function.
type ApiRequestOptions<T = unknown> = {
readonly method: 'DELETE' | 'GET' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'POST' | 'PUT';
readonly url: string;
readonly path?: Record<string, unknown>;
readonly query?: Record<string, unknown>;
readonly body?: any;
readonly formData?: Record<string, unknown> | any[] | Blob | File;
readonly headers?: Record<string, unknown>;
readonly mediaType?: string;
readonly errors?: Record<number | string, string>;
// ... other specialized options
};
Dynamic Path Resolution
The url property often contains placeholders wrapped in curly braces, such as /api/v1/items/{id}. The core logic in frontend/src/client/core/request.ts uses the path property to resolve these placeholders.
When getUrl is called, it performs a regex replacement:
- It identifies placeholders like
{id}. - It looks up the corresponding key in the
options.pathobject. - It encodes the value and injects it into the URL.
Example from sdk.gen.ts:
public static updateItem(data: ItemsUpdateItemData): CancelablePromise<ItemsUpdateItemResponse> {
return __request(OpenAPI, {
method: 'PUT',
url: '/api/v1/items/{id}',
path: {
id: data.id // Resolves {id} in the URL
},
body: data.requestBody,
mediaType: 'application/json',
});
}
Query Parameters
Query parameters are defined via the query object. The getQueryString utility in request.ts recursively processes this object to build a standard URL query string, handling dates, arrays, and nested objects.
// Example: /api/v1/items/?skip=0&limit=10
public static readItems(data: ItemsReadItemsData = {}): CancelablePromise<ItemsReadItemsResponse> {
return __request(OpenAPI, {
method: 'GET',
url: '/api/v1/items/',
query: {
skip: data.skip,
limit: data.limit
},
});
}
Request Payloads: Body vs. FormData
The project distinguishes between standard JSON payloads and multi-part form data:
body: Used for JSON payloads or raw Blobs. IfmediaTypeis not specified, the system defaults toapplication/jsonfor objects.formData: Used for file uploads orapplication/x-www-form-urlencodedsubmissions. ThegetFormDatahelper automatically converts theformDatarecord into a nativeFormDataobject.
Example of Form Data (Login):
public static loginAccessToken(data: LoginLoginAccessTokenData): CancelablePromise<LoginLoginAccessTokenResponse> {
return __request(OpenAPI, {
method: 'POST',
url: '/api/v1/login/access-token',
formData: data.formData,
mediaType: 'application/x-www-form-urlencoded',
});
}
Headers and Media Types
The Headers type is a simple alias for Record<string, string>. Headers are resolved in a specific hierarchy in getHeaders:
- Global Headers: Defined in
OpenAPI.HEADERS. - Request Headers: Defined in
options.headers. - Authentication: If
OpenAPI.TOKENis present, anAuthorization: Bearer <token>header is automatically added. - Content-Type: Inferred from the
mediaTypeproperty or the type of thebody.
Custom Error Mapping
One of the most powerful features of ApiRequestOptions is the errors map. While the core logic provides default messages for standard HTTP status codes (e.g., 404: "Not Found"), individual requests can override these to provide domain-specific context.
errors: {
400: 'Custom bad request message for this specific endpoint',
422: 'Validation Error',
}
These mappings are processed by catchErrorCodes in request.ts, which throws an ApiError containing the mapped message if the response status matches a key in the errors object.