Skip to content

Commit 2fc869b

Browse files
committed
feat: add api error handling in new portal generation code
1 parent ad9054d commit 2fc869b

3 files changed

Lines changed: 56 additions & 63 deletions

File tree

src/actions/portal/generatePortalAction.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,18 @@ export class GeneratePortalAction {
2828
zipPortal: boolean
2929
): Promise<ActionResult> => {
3030
if (buildDirectory.isEqual(portalDirectory)) {
31-
return ActionResult.error("build directory and portal directory cannot be the same")
31+
return ActionResult.error("build directory and portal directory cannot be the same.");
3232
}
3333

3434
if (!(await this.validateBuild(buildDirectory))) {
35-
return ActionResult.error("build directory is empty or not valid")
35+
return ActionResult.error("build directory is empty or not valid");
3636
}
3737

3838
if (!(await this.validatePortal(portalDirectory, force))) {
39-
return ActionResult.error("portal directory is empty or not valid")
39+
return ActionResult.error("portal directory is empty or not valid");
4040
}
4141

42-
await withDir(
42+
return await withDir(
4343
async (tempDirResult) => {
4444
this.prompts.displayPortalGenerationMessage();
4545

@@ -48,11 +48,17 @@ export class GeneratePortalAction {
4848
const buildZipPath = new FilePath(tempDirectory, new FileName("build.zip"));
4949
await this.zipArchiver.archive(buildDirectory, buildZipPath);
5050

51-
const buildStream = await this.portalService.generatePortal(buildZipPath, this.configDir, this.authKey);
51+
const response = await this.portalService.generatePortal(buildZipPath, this.configDir, this.authKey);
52+
53+
if (!response.isSuccess()) {
54+
this.prompts.displayPortalGenerationErrorMessage();
55+
return ActionResult.error(await this.parseError(response.error!, portalDirectory, tempDirectory));
56+
}
57+
5258
this.prompts.displayPortalGenerationSuccessMessage();
5359

5460
const tempPortalFilePath = new FilePath(tempDirectory, new FileName("portal.zip"));
55-
await this.fileService.writeFile(tempPortalFilePath, <NodeJS.ReadableStream>buildStream);
61+
await this.fileService.writeFile(tempPortalFilePath, <NodeJS.ReadableStream>response.value);
5662

5763
await this.fileService.cleanDirectory(portalDirectory);
5864
if (zipPortal) {
@@ -61,15 +67,12 @@ export class GeneratePortalAction {
6167
} else {
6268
await this.zipArchiver.unArchive(tempPortalFilePath, portalDirectory);
6369
}
70+
return ActionResult.success();
6471
},
6572
{ unsafeCleanup: true }
6673
);
67-
68-
return ActionResult.success();
6974
}
7075

71-
72-
7376
private async validateBuild(buildDirectory: DirectoryPath) {
7477
// TODO: add more checks here
7578
return await this.fileService.directoryExists(buildDirectory);
@@ -80,4 +83,20 @@ export class GeneratePortalAction {
8083
if (isEmptyOrNotExists) return true;
8184
return forceCleanup || (await this.prompts.overwritePortal(portalDirectory));
8285
}
86+
87+
private async parseError(error: string | NodeJS.ReadableStream, portalDirectory: DirectoryPath, tempDirectory: DirectoryPath): Promise<string> {
88+
if (typeof error === 'string') {
89+
return error;
90+
}
91+
92+
const tempErrorFilePath = new FilePath(tempDirectory, new FileName("error.zip"));
93+
await this.fileService.writeFile(tempErrorFilePath, <NodeJS.ReadableStream>error);
94+
95+
await this.fileService.cleanDirectory(portalDirectory);
96+
await this.zipArchiver.unArchive(tempErrorFilePath, portalDirectory);
97+
98+
const errorReportPath = portalDirectory.join("apimatic-debug");
99+
return "An error occurred during portal generation due to an issue with the input. An error report has been written at the destination path: " +
100+
errorReportPath;
101+
}
83102
}

src/commands/portal/generate.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { Command, Flags } from "@oclif/core";
1+
import { Command, Config, Flags } from "@oclif/core";
22
import { DirectoryPath } from "../../types/file/directoryPath.js";
33
import { GeneratePortalAction } from "../../actions/portal/generatePortalAction.js";
4+
import { PortalGeneratePrompts } from "../../prompts/portal/generate.js";
45

56
const DEFAULT_WORKING_DIRECTORY = "./";
67

@@ -35,6 +36,13 @@ Your portal has been generated at D:/
3536
`
3637
];
3738

39+
private readonly prompts: PortalGeneratePrompts;
40+
41+
constructor(argv: string[], config: Config) {
42+
super(argv, config);
43+
this.prompts = new PortalGeneratePrompts();
44+
}
45+
3846
async run(): Promise<void> {
3947
const {
4048
flags: {
@@ -52,7 +60,7 @@ Your portal has been generated at D:/
5260

5361
const action = new GeneratePortalAction(this.getConfigDir(), authKey);
5462
const result = await action.execute(buildDirectory, portalDirectory, force, zipPortal);
55-
result.map((message) => this.error(message));
63+
result.map((message) => this.prompts.logError(message));
5664
}
5765

5866
private getConfigDir = () => {

src/infrastructure/services/portal-service.ts

Lines changed: 17 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -32,47 +32,24 @@ export class PortalService {
3232
private readonly fileService = new FileService();
3333

3434

35-
async generatePortal(buildPath: FilePath, configDir: DirectoryPath, authKey: string | null): Promise<NodeJS.ReadableStream> {
35+
async generatePortal(buildPath: FilePath, configDir: DirectoryPath, authKey: string | null): Promise<Result<NodeJS.ReadableStream, string | NodeJS.ReadableStream>> {
3636
const buildFileStream = await this.fileService.getStream(buildPath);
3737
const file = new FileWrapper(buildFileStream);
38-
const client = await this.getApiClient(configDir, authKey);
39-
const docsPortalManagementController = new DocsPortalManagementController(client);
40-
const response = await docsPortalManagementController.generateOnPremPortalViaBuildInput(this.CONTENT_TYPE, file);
41-
return response.result as NodeJS.ReadableStream;
42-
}
4338

44-
private async getApiClient(configDir: DirectoryPath, authKey: string | null): Promise<Client> {
4539
const authInfo: AuthInfo | null = await getAuthInfo(configDir.toString());
46-
// TODO: Add auth key
47-
const authorizationHeader = this.createAuthorizationHeader(authInfo, authKey);
48-
return this.createApiClient(authorizationHeader);
49-
}
50-
51-
public async generateOnPremPortal(
52-
params: GeneratePortalParams,
53-
configDir: string
54-
): Promise<Result<NodeJS.ReadableStream, string>> {
55-
if (!(await fsExtra.pathExists(params.sourceBuildInputZipFilePath))) {
56-
return Result.failure("Build file doesn't exist");
40+
if (authInfo === null && !authKey) {
41+
return Result.failure("You are not logged in, please login using `auth:login` first.");
5742
}
5843

59-
const authInfo: AuthInfo | null = await getAuthInfo(configDir);
60-
if (authInfo === null && !params.overrideAuthKey) {
61-
return Result.failure("You are not logged in, please login using `apimatic auth:login` first.");
62-
}
63-
64-
const authorizationHeader = this.createAuthorizationHeader(authInfo, params.overrideAuthKey);
44+
const authorizationHeader = this.createAuthorizationHeader(authInfo, authKey);
6545
const client = this.createApiClient(authorizationHeader);
6646
const docsPortalManagementController = new DocsPortalManagementController(client);
6747

6848
try {
69-
const stream = await this.generatePortalFromSyncEndpoint(
70-
docsPortalManagementController,
71-
params.sourceBuildInputZipFilePath
72-
);
73-
return Result.success(stream);
49+
const response = await docsPortalManagementController.generateOnPremPortalViaBuildInput(this.CONTENT_TYPE, file);
50+
return Result.success(response.result as NodeJS.ReadableStream);
7451
} catch (error) {
75-
return Result.failure(await this.handlePortalGenerationErrors(error, params));
52+
return Result.failure(await this.handlePortalGenerationErrors(error));
7653
}
7754
}
7855

@@ -120,13 +97,13 @@ export class PortalService {
12097
};
12198

12299
private getUserAgent(): string {
123-
const osInfo = `${os.platform()} ${os.release()}`;
124-
const engine = "Node.js";
125-
const engineVersion = process.version;
126-
127-
return `APIMATIC CLI - [OS: ${osInfo}, Engine: ${engine}/${engineVersion}]`;
128-
}
129-
100+
const osInfo = `${os.platform()} ${os.release()}`;
101+
const engine = "Node.js";
102+
const engineVersion = process.version;
103+
104+
return `APIMATIC CLI - [OS: ${osInfo}, Engine: ${engine}/${engineVersion}]`;
105+
}
106+
130107
private createApiClient = (authorizationHeader: string): Client => {
131108
return new Client({
132109
customHeaderAuthenticationCredentials: {
@@ -137,18 +114,7 @@ export class PortalService {
137114
});
138115
};
139116

140-
private generatePortalFromSyncEndpoint = async (
141-
docsPortalManagementController: DocsPortalManagementController,
142-
zippedBuildFilePath: string
143-
): Promise<NodeJS.ReadableStream> => {
144-
const file = new FileWrapper(fs.createReadStream(zippedBuildFilePath));
145-
const response: ApiResponse<NodeJS.ReadableStream | Blob> =
146-
await docsPortalManagementController.generateOnPremPortalViaBuildInput(this.CONTENT_TYPE, file);
147-
148-
return response.result as NodeJS.ReadableStream;
149-
};
150-
151-
private handlePortalGenerationErrors = async (error: unknown, params: GeneratePortalParams): Promise<string> => {
117+
private handlePortalGenerationErrors = async (error: unknown): Promise<string | NodeJS.ReadableStream> => {
152118
if (error instanceof UnauthorizedResponseError) {
153119
//401
154120
const body = await this.parseErrorResponse(error);
@@ -160,7 +126,7 @@ export class PortalService {
160126
return getMessageInRedColor(body.title + "\n- " + message);
161127
} else if (error instanceof ApiError && error.statusCode === 422) {
162128
//422
163-
return await this.saveAndExtractErrorZipFile(error, params);
129+
return error.body as NodeJS.ReadableStream;
164130
} else if (error instanceof InternalServerErrorResponseError) {
165131
//500
166132
const body = await this.parseErrorResponse(error);
@@ -197,7 +163,7 @@ export class PortalService {
197163
resolve(
198164
getMessageInRedColor(
199165
"An error occurred during portal generation due to an issue with the input. An error report has been written at the destination path: " +
200-
path.join(params.generatedPortalArtifactsFolderPath, "apimatic-debug")
166+
path.join(params.generatedPortalArtifactsFolderPath, "apimatic-debug")
201167
)
202168
);
203169
})

0 commit comments

Comments
 (0)