Skip to content
5 changes: 4 additions & 1 deletion scripts/apply.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ async function main() {
.map((operation) => {
const { operationId, method, path, requiredParams, hasResponseBody } = operation;
return `async ${operationId}(options${requiredParams ? "" : "?"}: FetchOptions<operations["${operationId}"]>) {
${hasResponseBody ? `const { data } = ` : ``}await this.client.${method}("${path}", options);
const { ${hasResponseBody ? `data, ` : ``}error, response } = await this.client.${method}("${path}", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
${
hasResponseBody
? `return data;
Expand Down
112 changes: 86 additions & 26 deletions src/common/atlas/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,6 @@ export class ApiClient {
},
};

private readonly errorMiddleware: Middleware = {
async onResponse({ response }) {
if (!response.ok) {
throw await ApiClientError.fromResponse(response);
}
},
};

constructor(options: ApiClientOptions) {
this.options = {
...options,
Expand Down Expand Up @@ -91,7 +83,6 @@ export class ApiClient {
});
this.client.use(this.authMiddleware);
}
this.client.use(this.errorMiddleware);
}

public hasCredentials(): boolean {
Expand Down Expand Up @@ -151,83 +142,152 @@ export class ApiClient {

// DO NOT EDIT. This is auto-generated code.
async listClustersForAllProjects(options?: FetchOptions<operations["listClustersForAllProjects"]>) {
const { data } = await this.client.GET("/api/atlas/v2/clusters", options);
const { data, error, response } = await this.client.GET("/api/atlas/v2/clusters", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async listProjects(options?: FetchOptions<operations["listProjects"]>) {
const { data } = await this.client.GET("/api/atlas/v2/groups", options);
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async createProject(options: FetchOptions<operations["createProject"]>) {
const { data } = await this.client.POST("/api/atlas/v2/groups", options);
const { data, error, response } = await this.client.POST("/api/atlas/v2/groups", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async deleteProject(options: FetchOptions<operations["deleteProject"]>) {
await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
const { error, response } = await this.client.DELETE("/api/atlas/v2/groups/{groupId}", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
}

async getProject(options: FetchOptions<operations["getProject"]>) {
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async listProjectIpAccessLists(options: FetchOptions<operations["listProjectIpAccessLists"]>) {
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/accessList", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async createProjectIpAccessList(options: FetchOptions<operations["createProjectIpAccessList"]>) {
const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/accessList", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async deleteProjectIpAccessList(options: FetchOptions<operations["deleteProjectIpAccessList"]>) {
await this.client.DELETE("/api/atlas/v2/groups/{groupId}/accessList/{entryValue}", options);
const { error, response } = await this.client.DELETE(
"/api/atlas/v2/groups/{groupId}/accessList/{entryValue}",
options
);
if (error) {
throw ApiClientError.fromError(response, error);
}
}

async listClusters(options: FetchOptions<operations["listClusters"]>) {
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
const { data, error, response } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async createCluster(options: FetchOptions<operations["createCluster"]>) {
const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/clusters", options);
const { data, error, response } = await this.client.POST("/api/atlas/v2/groups/{groupId}/clusters", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async deleteCluster(options: FetchOptions<operations["deleteCluster"]>) {
await this.client.DELETE("/api/atlas/v2/groups/{groupId}/clusters/{clusterName}", options);
const { error, response } = await this.client.DELETE(
"/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
options
);
if (error) {
throw ApiClientError.fromError(response, error);
}
}

async getCluster(options: FetchOptions<operations["getCluster"]>) {
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/clusters/{clusterName}", options);
const { data, error, response } = await this.client.GET(
"/api/atlas/v2/groups/{groupId}/clusters/{clusterName}",
options
);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async listDatabaseUsers(options: FetchOptions<operations["listDatabaseUsers"]>) {
const { data } = await this.client.GET("/api/atlas/v2/groups/{groupId}/databaseUsers", options);
const { data, error, response } = await this.client.GET(
"/api/atlas/v2/groups/{groupId}/databaseUsers",
options
);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async createDatabaseUser(options: FetchOptions<operations["createDatabaseUser"]>) {
const { data } = await this.client.POST("/api/atlas/v2/groups/{groupId}/databaseUsers", options);
const { data, error, response } = await this.client.POST(
"/api/atlas/v2/groups/{groupId}/databaseUsers",
options
);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async deleteDatabaseUser(options: FetchOptions<operations["deleteDatabaseUser"]>) {
await this.client.DELETE("/api/atlas/v2/groups/{groupId}/databaseUsers/{databaseName}/{username}", options);
const { error, response } = await this.client.DELETE(
"/api/atlas/v2/groups/{groupId}/databaseUsers/{databaseName}/{username}",
options
);
if (error) {
throw ApiClientError.fromError(response, error);
}
}

async listOrganizations(options?: FetchOptions<operations["listOrganizations"]>) {
const { data } = await this.client.GET("/api/atlas/v2/orgs", options);
const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

async listOrganizationProjects(options: FetchOptions<operations["listOrganizationProjects"]>) {
const { data } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
const { data, error, response } = await this.client.GET("/api/atlas/v2/orgs/{orgId}/groups", options);
if (error) {
throw ApiClientError.fromError(response, error);
}
return data;
}

Expand Down
60 changes: 53 additions & 7 deletions src/common/atlas/apiClientError.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,67 @@
export class ApiClientError extends Error {
response?: Response;
import { ApiError } from "./openapi.js";

constructor(message: string, response: Response | undefined = undefined) {
export class ApiClientError extends Error {
private constructor(
message: string,
public readonly apiError?: ApiError
) {
super(message);
this.name = "ApiClientError";
this.response = response;
}

static async fromResponse(
response: Response,
message: string = `error calling Atlas API`
): Promise<ApiClientError> {
const err = await this.extractError(response);

return this.fromError(response, err, message);
}

static fromError(
response: Response,
error?: ApiError | string | Error,
message: string = `error calling Atlas API`
): ApiClientError {
const errorMessage = this.buildErrorMessage(error);

const apiError = typeof error === "object" && !(error instanceof Error) ? error : undefined;

return new ApiClientError(`[${response.status} ${response.statusText}] ${message}: ${errorMessage}`, apiError);
}

private static async extractError(response: Response): Promise<ApiError | string | undefined> {
try {
const text = await response.text();
return new ApiClientError(`${message}: [${response.status} ${response.statusText}] ${text}`, response);
return (await response.json()) as ApiError;
} catch {
return new ApiClientError(`${message}: ${response.status} ${response.statusText}`, response);
try {
return await response.text();
} catch {
return undefined;
}
}
}

private static buildErrorMessage(error?: string | ApiError | Error): string {
let errorMessage: string = "unknown error";

if (error instanceof Error) {
return error.message;
}

//eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (typeof error) {
case "object":
errorMessage = error.reason || "unknown error";
if (error.detail && error.detail.length > 0) {
errorMessage = `${errorMessage}; ${error.detail}`;
}
break;
case "string":
errorMessage = error;
break;
}

return errorMessage.trim();
}
}
Loading
Loading