Skip to content

Commit ee9ca3c

Browse files
committed
chore(cli): migrate @fern-api/openapi-ir-parser to use CliError
1 parent f501e76 commit ee9ca3c

14 files changed

Lines changed: 181 additions & 83 deletions

packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/AbstractAsyncAPIParserContext.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Logger } from "@fern-api/logger";
22
import type { Namespace, SchemaId, SdkGroup, SdkGroupName } from "@fern-api/openapi-ir";
3-
import type { TaskContext } from "@fern-api/task-context";
3+
import { CliError, type TaskContext } from "@fern-api/task-context";
4+
45
import type { OpenAPIV3 } from "openapi-types";
56

67
import type { ParseOpenAPIOptions } from "../options.js";
@@ -76,26 +77,38 @@ export abstract class AbstractAsyncAPIParserContext<TDocument extends object> im
7677
*/
7778
public resolveSchemaReference(schema: OpenAPIV3.ReferenceObject): OpenAPIV3.SchemaObject {
7879
if (!schema.$ref.startsWith(SCHEMA_REFERENCE_PREFIX)) {
79-
throw new Error(`Failed to resolve schema reference: ${schema.$ref}`);
80+
throw new CliError({
81+
message: `Failed to resolve schema reference: ${schema.$ref}`,
82+
code: CliError.Code.ReferenceError
83+
});
8084
}
8185

8286
const schemaKey = schema.$ref.substring(SCHEMA_REFERENCE_PREFIX.length);
8387
const splitSchemaKey = schemaKey.split("/");
8488

8589
const components = (this.document as AsyncAPIV2.DocumentV2 | AsyncAPIV3.DocumentV3).components;
8690
if (components == null || components.schemas == null) {
87-
throw new Error("Document does not have components.schemas.");
91+
throw new CliError({
92+
message: "Document does not have components.schemas.",
93+
code: CliError.Code.ReferenceError
94+
});
8895
}
8996

9097
const [topKey, maybeProps, maybePropKey] = splitSchemaKey;
9198

9299
if (topKey == null || topKey === "") {
93-
throw new Error(`${schema.$ref} cannot be resolved. No schema key provided.`);
100+
throw new CliError({
101+
message: `${schema.$ref} cannot be resolved. No schema key provided.`,
102+
code: CliError.Code.ReferenceError
103+
});
94104
}
95105

96106
let resolvedSchema = components.schemas[topKey];
97107
if (resolvedSchema == null) {
98-
throw new Error(`Schema "${topKey}" is undefined in document.components.schemas.`);
108+
throw new CliError({
109+
message: `Schema "${topKey}" is undefined in document.components.schemas.`,
110+
code: CliError.Code.ReferenceError
111+
});
99112
}
100113

101114
if (isReferenceObject(resolvedSchema)) {
@@ -105,7 +118,10 @@ export abstract class AbstractAsyncAPIParserContext<TDocument extends object> im
105118
if (maybeProps === "properties" && maybePropKey != null) {
106119
const resolvedProperty = resolvedSchema.properties?.[maybePropKey];
107120
if (resolvedProperty == null) {
108-
throw new Error(`Property "${maybePropKey}" not found on "${topKey}". Full ref: ${schema.$ref}`);
121+
throw new CliError({
122+
message: `Property "${maybePropKey}" not found on "${topKey}". Full ref: ${schema.$ref}`,
123+
code: CliError.Code.ReferenceError
124+
});
109125
} else if (isReferenceObject(resolvedProperty)) {
110126
resolvedSchema = this.resolveSchemaReference(resolvedProperty);
111127
} else {

packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/ExampleWebsocketSessionFactory.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
WebsocketMessageExample,
88
WebsocketSessionExample
99
} from "@fern-api/openapi-ir";
10-
10+
import { CliError } from "@fern-api/task-context";
1111
import { isExamplePrimitive } from "../openapi/v3/converters/ExampleEndpointFactory.js";
1212
import { convertSchema } from "../schema/convertSchemas.js";
1313
import { ExampleTypeFactory } from "../schema/examples/ExampleTypeFactory.js";
@@ -245,7 +245,10 @@ export class ExampleWebsocketSessionFactory {
245245
while (schema.type === "reference") {
246246
const resolvedSchema = this.schemas[schema.schema];
247247
if (resolvedSchema == null) {
248-
throw new Error(`Unexpected error: Failed to resolve schema reference: ${schema.schema}`);
248+
throw new CliError({
249+
message: `Unexpected error: Failed to resolve schema reference: ${schema.schema}`,
250+
code: CliError.Code.InternalError
251+
});
249252
}
250253
schema = resolvedSchema;
251254
}

packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/v2/AsyncAPIV2ParserContext.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { CliError } from "@fern-api/task-context";
12
import { OpenAPIV3 } from "openapi-types";
2-
33
import { AbstractAsyncAPIParserContext } from "../AbstractAsyncAPIParserContext.js";
44
import { WebsocketSessionExampleMessage } from "../getFernExamples.js";
55
import { AsyncAPIV2 } from "../v2/index.js";
@@ -14,13 +14,16 @@ export class AsyncAPIV2ParserContext extends AbstractAsyncAPIParserContext<Async
1414
const components = this.document.components;
1515

1616
if (components == null || components.messages == null || !message.$ref.startsWith(MESSAGE_REFERENCE_PREFIX)) {
17-
throw new Error(`Failed to resolve message reference: ${message.$ref} in v2 components`);
17+
throw new CliError({
18+
message: `Failed to resolve message reference: ${message.$ref} in v2 components`,
19+
code: CliError.Code.ReferenceError
20+
});
1821
}
1922

2023
const messageKey = message.$ref.substring(MESSAGE_REFERENCE_PREFIX.length);
2124
const resolvedMessage = components.messages[messageKey];
2225
if (resolvedMessage == null) {
23-
throw new Error(`${message.$ref} is undefined`);
26+
throw new CliError({ message: `${message.$ref} is undefined`, code: CliError.Code.ReferenceError });
2427
}
2528
return resolvedMessage as AsyncAPIV2.MessageV2;
2629
}

packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/v3/AsyncAPIV3ParserContext.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import { CliError } from "@fern-api/task-context";
12
import { OpenAPIV3 } from "openapi-types";
2-
33
import { AbstractAsyncAPIParserContext } from "../AbstractAsyncAPIParserContext.js";
44
import { WebsocketSessionExampleMessage } from "../getFernExamples.js";
55
import { AsyncAPIV3 } from "../v3/index.js";
@@ -8,9 +8,10 @@ export class AsyncAPIV3ParserContext extends AbstractAsyncAPIParserContext<Async
88
public getExampleMessageReference(message: WebsocketSessionExampleMessage): string {
99
const channelId = message.channelId ?? this.getDefaultChannelId();
1010
if (channelId == null) {
11-
throw new Error(
12-
`Cannot resolve example message reference: no channelId provided and no channels found in document`
13-
);
11+
throw new CliError({
12+
message: `Cannot resolve example message reference: no channelId provided and no channels found in document`,
13+
code: CliError.Code.InternalError
14+
});
1415
}
1516
return `#/channels/${channelId}/messages/${message.messageId}`;
1617
}
@@ -32,12 +33,12 @@ export class AsyncAPIV3ParserContext extends AbstractAsyncAPIParserContext<Async
3233
this.document.components.parameters == null ||
3334
!parameter.$ref.startsWith(PARAMETER_REFERENCE_PREFIX)
3435
) {
35-
throw new Error(`Failed to resolve ${parameter.$ref}`);
36+
throw new CliError({ message: `Failed to resolve ${parameter.$ref}`, code: CliError.Code.ReferenceError });
3637
}
3738
const parameterKey = parameter.$ref.substring(PARAMETER_REFERENCE_PREFIX.length);
3839
const resolvedParameter = this.document.components.parameters[parameterKey];
3940
if (resolvedParameter == null) {
40-
throw new Error(`${parameter.$ref} is undefined`);
41+
throw new CliError({ message: `${parameter.$ref} is undefined`, code: CliError.Code.ReferenceError });
4142
}
4243
if ("$ref" in resolvedParameter) {
4344
return this.resolveParameterReference(resolvedParameter as OpenAPIV3.ReferenceObject);
@@ -54,11 +55,17 @@ export class AsyncAPIV3ParserContext extends AbstractAsyncAPIParserContext<Async
5455
const MESSAGE_REFERENCE_PREFIX = "#/components/messages/";
5556

5657
if (message == null) {
57-
throw new Error("Cannot resolve message reference: message is null or undefined");
58+
throw new CliError({
59+
message: "Cannot resolve message reference: message is null or undefined",
60+
code: CliError.Code.InternalError
61+
});
5862
}
5963

6064
if (!message.$ref) {
61-
throw new Error("Cannot resolve message reference: message.$ref is undefined or empty");
65+
throw new CliError({
66+
message: "Cannot resolve message reference: message.$ref is undefined or empty",
67+
code: CliError.Code.ReferenceError
68+
});
6269
}
6370

6471
if (message.$ref.startsWith(CHANNELS_PATH_PART)) {
@@ -68,13 +75,16 @@ export class AsyncAPIV3ParserContext extends AbstractAsyncAPIParserContext<Async
6875
const channelPath = rawChannelPath?.replace(/~1/g, "/").replace(/~0/g, "~");
6976

7077
if (channelPath == null || messageKey == null || !this.document.channels?.[channelPath]) {
71-
throw new Error(`Failed to resolve message reference ${message.$ref} in channel ${channelPath}`);
78+
throw new CliError({
79+
message: `Failed to resolve message reference ${message.$ref} in channel ${channelPath}`,
80+
code: CliError.Code.ReferenceError
81+
});
7282
}
7383

7484
const channel = this.document.channels[channelPath] as AsyncAPIV3.ChannelV3;
7585
const resolvedInChannel = (channel as AsyncAPIV3.ChannelV3).messages?.[messageKey];
7686
if (resolvedInChannel == null) {
77-
throw new Error(`${message.$ref} is undefined`);
87+
throw new CliError({ message: `${message.$ref} is undefined`, code: CliError.Code.ReferenceError });
7888
}
7989
if ("$ref" in resolvedInChannel && !shallow) {
8090
return this.resolveMessageReference(resolvedInChannel as OpenAPIV3.ReferenceObject);
@@ -88,13 +98,16 @@ export class AsyncAPIV3ParserContext extends AbstractAsyncAPIParserContext<Async
8898

8999
const components = this.document.components;
90100
if (!message.$ref.startsWith(MESSAGE_REFERENCE_PREFIX) || !components?.messages) {
91-
throw new Error(`Failed to resolve message reference: ${message.$ref} in v3 components`);
101+
throw new CliError({
102+
message: `Failed to resolve message reference: ${message.$ref} in v3 components`,
103+
code: CliError.Code.ReferenceError
104+
});
92105
}
93106

94107
const messageKey = message.$ref.substring(MESSAGE_REFERENCE_PREFIX.length);
95108
const resolvedInComponents = components.messages[messageKey];
96109
if (resolvedInComponents == null) {
97-
throw new Error(`${message.$ref} is undefined`);
110+
throw new CliError({ message: `${message.$ref} is undefined`, code: CliError.Code.ReferenceError });
98111
}
99112

100113
return {

packages/cli/api-importers/openapi/openapi-ir-parser/src/asyncapi/v3/parseAsyncAPIV3.ts

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
WebsocketMessageSchema,
1212
WebsocketSessionExample
1313
} from "@fern-api/openapi-ir";
14+
import { CliError } from "@fern-api/task-context";
1415
import { camelCase, upperFirst } from "lodash-es";
1516
import { OpenAPIV3 } from "openapi-types";
1617
import { getExtension } from "../../getExtension.js";
@@ -227,17 +228,21 @@ export function parseAsyncAPIV3({
227228

228229
const channelEvent = channelEvents[channelPath];
229230
if (channelEvent == null) {
230-
throw new Error(
231-
`Internal error: channelEvents["${channelPath}"] is unexpectedly undefined for operation ${operationId}`
232-
);
231+
throw new CliError({
232+
message: `Internal error: channelEvents["${channelPath}"] is unexpectedly undefined for operation ${operationId}`,
233+
code: CliError.Code.InternalError
234+
});
233235
}
234236

235237
if (operation.action === "receive") {
236238
channelEvent.subscribe.push(...messagesWithMethodName);
237239
} else if (operation.action === "send") {
238240
channelEvent.publish.push(...messagesWithMethodName);
239241
} else {
240-
throw new Error(`Operation ${operationId} has an invalid action: ${operation.action}`);
242+
throw new CliError({
243+
message: `Operation ${operationId} has an invalid action: ${operation.action}`,
244+
code: CliError.Code.ValidationError
245+
});
241246
}
242247
}
243248

@@ -639,13 +644,22 @@ function decodeJsonPointerSegment(segment: string): string {
639644

640645
function getChannelPathFromOperation(operation: AsyncAPIV3.Operation): string {
641646
if (!operation.channel) {
642-
throw new Error("Operation is missing required 'channel' field");
647+
throw new CliError({
648+
message: "Operation is missing required 'channel' field",
649+
code: CliError.Code.ValidationError
650+
});
643651
}
644652
if (!operation.channel.$ref) {
645-
throw new Error("Operation channel is missing required '$ref' field");
653+
throw new CliError({
654+
message: "Operation channel is missing required '$ref' field",
655+
code: CliError.Code.ValidationError
656+
});
646657
}
647658
if (!operation.channel.$ref.startsWith(CHANNEL_REFERENCE_PREFIX)) {
648-
throw new Error(`Failed to resolve channel path from operation ${operation.channel.$ref}`);
659+
throw new CliError({
660+
message: `Failed to resolve channel path from operation ${operation.channel.$ref}`,
661+
code: CliError.Code.ReferenceError
662+
});
649663
}
650664
return decodeJsonPointerSegment(operation.channel.$ref.substring(CHANNEL_REFERENCE_PREFIX.length));
651665
}
@@ -654,38 +668,50 @@ function convertChannelParameterLocation(location: string): {
654668
type: "header" | "path" | "payload";
655669
parameterKey: string;
656670
} {
657-
try {
658-
const [messageType, parameterKey] = location.split("#/");
659-
if (messageType == null || parameterKey == null) {
660-
throw new Error(`Invalid location format: ${location}; unable to parse message type and parameter key`);
661-
}
662-
if (!messageType.startsWith(LOCATION_PREFIX)) {
663-
throw new Error(`Invalid location format: ${location}; expected ${LOCATION_PREFIX} prefix`);
664-
}
665-
const type = messageType.substring(LOCATION_PREFIX.length);
666-
if (type !== "header" && type !== "path" && type !== "payload") {
667-
throw new Error(`Invalid message type: ${type}. Must be one of: header, path, payload`);
668-
}
669-
return { type, parameterKey };
670-
} catch (error) {
671-
throw new Error(
672-
`Invalid location format: ${location}; see here for more details: ` +
673-
"https://www.asyncapi.com/docs/reference/specification/v3.0.0#runtimeExpression"
674-
);
671+
const ASYNCAPI_RUNTIME_EXPRESSION_DOCS =
672+
"https://www.asyncapi.com/docs/reference/specification/v3.0.0#runtimeExpression";
673+
const [messageType, parameterKey] = location.split("#/");
674+
if (messageType == null || parameterKey == null) {
675+
throw new CliError({
676+
message:
677+
`Invalid location format: ${location}; unable to parse message type and parameter key. ` +
678+
`See ${ASYNCAPI_RUNTIME_EXPRESSION_DOCS} for the expected format.`,
679+
code: CliError.Code.ValidationError
680+
});
681+
}
682+
if (!messageType.startsWith(LOCATION_PREFIX)) {
683+
throw new CliError({
684+
message: `Invalid location format: ${location}; expected ${LOCATION_PREFIX} prefix`,
685+
code: CliError.Code.ValidationError
686+
});
687+
}
688+
const type = messageType.substring(LOCATION_PREFIX.length);
689+
if (type !== "header" && type !== "path" && type !== "payload") {
690+
throw new CliError({
691+
message: `Invalid message type: ${type}. Must be one of: header, path, payload`,
692+
code: CliError.Code.ValidationError
693+
});
675694
}
695+
return { type, parameterKey };
676696
}
677697

678698
function getServerNameFromServerRef(
679699
servers: Record<string, ServerContext>,
680700
serverRef: OpenAPIV3.ReferenceObject
681701
): ServerContext {
682702
if (!serverRef.$ref.startsWith(SERVER_REFERENCE_PREFIX)) {
683-
throw new Error(`Failed to resolve server name from server ref ${serverRef.$ref}`);
703+
throw new CliError({
704+
message: `Failed to resolve server name from server ref ${serverRef.$ref}`,
705+
code: CliError.Code.ReferenceError
706+
});
684707
}
685708
const serverName = serverRef.$ref.substring(SERVER_REFERENCE_PREFIX.length);
686709
const server = servers[serverName];
687710
if (server == null) {
688-
throw new Error(`Failed to find server with name ${serverName}`);
711+
throw new CliError({
712+
message: `Failed to find server with name ${serverName}`,
713+
code: CliError.Code.ReferenceError
714+
});
689715
}
690716
return server;
691717
}

0 commit comments

Comments
 (0)