Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Logger } from "@fern-api/logger";
import type { Namespace, SchemaId, SdkGroup, SdkGroupName } from "@fern-api/openapi-ir";
import type { TaskContext } from "@fern-api/task-context";
import { CliError, type TaskContext } from "@fern-api/task-context";

import type { OpenAPIV3 } from "openapi-types";

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

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

const components = (this.document as AsyncAPIV2.DocumentV2 | AsyncAPIV3.DocumentV3).components;
if (components == null || components.schemas == null) {
throw new Error("Document does not have components.schemas.");
throw new CliError({
message: "Document does not have components.schemas.",
code: CliError.Code.ReferenceError
});
}

const [topKey, maybeProps, maybePropKey] = splitSchemaKey;

if (topKey == null || topKey === "") {
throw new Error(`${schema.$ref} cannot be resolved. No schema key provided.`);
throw new CliError({
message: `${schema.$ref} cannot be resolved. No schema key provided.`,
code: CliError.Code.ReferenceError
});
}

let resolvedSchema = components.schemas[topKey];
if (resolvedSchema == null) {
throw new Error(`Schema "${topKey}" is undefined in document.components.schemas.`);
throw new CliError({
message: `Schema "${topKey}" is undefined in document.components.schemas.`,
code: CliError.Code.ReferenceError
});
}

if (isReferenceObject(resolvedSchema)) {
Expand All @@ -105,7 +118,10 @@ export abstract class AbstractAsyncAPIParserContext<TDocument extends object> im
if (maybeProps === "properties" && maybePropKey != null) {
const resolvedProperty = resolvedSchema.properties?.[maybePropKey];
if (resolvedProperty == null) {
throw new Error(`Property "${maybePropKey}" not found on "${topKey}". Full ref: ${schema.$ref}`);
throw new CliError({
message: `Property "${maybePropKey}" not found on "${topKey}". Full ref: ${schema.$ref}`,
code: CliError.Code.ReferenceError
});
} else if (isReferenceObject(resolvedProperty)) {
resolvedSchema = this.resolveSchemaReference(resolvedProperty);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
WebsocketMessageExample,
WebsocketSessionExample
} from "@fern-api/openapi-ir";

import { CliError } from "@fern-api/task-context";
import { isExamplePrimitive } from "../openapi/v3/converters/ExampleEndpointFactory.js";
import { convertSchema } from "../schema/convertSchemas.js";
import { ExampleTypeFactory } from "../schema/examples/ExampleTypeFactory.js";
Expand Down Expand Up @@ -245,7 +245,10 @@ export class ExampleWebsocketSessionFactory {
while (schema.type === "reference") {
const resolvedSchema = this.schemas[schema.schema];
if (resolvedSchema == null) {
throw new Error(`Unexpected error: Failed to resolve schema reference: ${schema.schema}`);
throw new CliError({
message: `Unexpected error: Failed to resolve schema reference: ${schema.schema}`,
code: CliError.Code.InternalError
});
}
schema = resolvedSchema;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CliError } from "@fern-api/task-context";
import { OpenAPIV3 } from "openapi-types";

import { AbstractAsyncAPIParserContext } from "../AbstractAsyncAPIParserContext.js";
import { WebsocketSessionExampleMessage } from "../getFernExamples.js";
import { AsyncAPIV2 } from "../v2/index.js";
Expand All @@ -14,13 +14,16 @@ export class AsyncAPIV2ParserContext extends AbstractAsyncAPIParserContext<Async
const components = this.document.components;

if (components == null || components.messages == null || !message.$ref.startsWith(MESSAGE_REFERENCE_PREFIX)) {
throw new Error(`Failed to resolve message reference: ${message.$ref} in v2 components`);
throw new CliError({
message: `Failed to resolve message reference: ${message.$ref} in v2 components`,
code: CliError.Code.ReferenceError
});
}

const messageKey = message.$ref.substring(MESSAGE_REFERENCE_PREFIX.length);
const resolvedMessage = components.messages[messageKey];
if (resolvedMessage == null) {
throw new Error(`${message.$ref} is undefined`);
throw new CliError({ message: `${message.$ref} is undefined`, code: CliError.Code.ReferenceError });
}
return resolvedMessage as AsyncAPIV2.MessageV2;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { CliError } from "@fern-api/task-context";
import { OpenAPIV3 } from "openapi-types";

import { AbstractAsyncAPIParserContext } from "../AbstractAsyncAPIParserContext.js";
import { WebsocketSessionExampleMessage } from "../getFernExamples.js";
import { AsyncAPIV3 } from "../v3/index.js";
Expand All @@ -8,9 +8,10 @@ export class AsyncAPIV3ParserContext extends AbstractAsyncAPIParserContext<Async
public getExampleMessageReference(message: WebsocketSessionExampleMessage): string {
const channelId = message.channelId ?? this.getDefaultChannelId();
if (channelId == null) {
throw new Error(
`Cannot resolve example message reference: no channelId provided and no channels found in document`
);
throw new CliError({
message: `Cannot resolve example message reference: no channelId provided and no channels found in document`,
code: CliError.Code.InternalError
});
}
return `#/channels/${channelId}/messages/${message.messageId}`;
}
Expand All @@ -32,12 +33,12 @@ export class AsyncAPIV3ParserContext extends AbstractAsyncAPIParserContext<Async
this.document.components.parameters == null ||
!parameter.$ref.startsWith(PARAMETER_REFERENCE_PREFIX)
) {
throw new Error(`Failed to resolve ${parameter.$ref}`);
throw new CliError({ message: `Failed to resolve ${parameter.$ref}`, code: CliError.Code.ReferenceError });
}
const parameterKey = parameter.$ref.substring(PARAMETER_REFERENCE_PREFIX.length);
const resolvedParameter = this.document.components.parameters[parameterKey];
if (resolvedParameter == null) {
throw new Error(`${parameter.$ref} is undefined`);
throw new CliError({ message: `${parameter.$ref} is undefined`, code: CliError.Code.ReferenceError });
}
if ("$ref" in resolvedParameter) {
return this.resolveParameterReference(resolvedParameter as OpenAPIV3.ReferenceObject);
Expand All @@ -54,11 +55,17 @@ export class AsyncAPIV3ParserContext extends AbstractAsyncAPIParserContext<Async
const MESSAGE_REFERENCE_PREFIX = "#/components/messages/";

if (message == null) {
throw new Error("Cannot resolve message reference: message is null or undefined");
throw new CliError({
message: "Cannot resolve message reference: message is null or undefined",
code: CliError.Code.InternalError
});
}

if (!message.$ref) {
throw new Error("Cannot resolve message reference: message.$ref is undefined or empty");
throw new CliError({
message: "Cannot resolve message reference: message.$ref is undefined or empty",
code: CliError.Code.ReferenceError
});
}

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

if (channelPath == null || messageKey == null || !this.document.channels?.[channelPath]) {
throw new Error(`Failed to resolve message reference ${message.$ref} in channel ${channelPath}`);
throw new CliError({
message: `Failed to resolve message reference ${message.$ref} in channel ${channelPath}`,
code: CliError.Code.ReferenceError
});
}

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

const components = this.document.components;
if (!message.$ref.startsWith(MESSAGE_REFERENCE_PREFIX) || !components?.messages) {
throw new Error(`Failed to resolve message reference: ${message.$ref} in v3 components`);
throw new CliError({
message: `Failed to resolve message reference: ${message.$ref} in v3 components`,
code: CliError.Code.ReferenceError
});
}

const messageKey = message.$ref.substring(MESSAGE_REFERENCE_PREFIX.length);
const resolvedInComponents = components.messages[messageKey];
if (resolvedInComponents == null) {
throw new Error(`${message.$ref} is undefined`);
throw new CliError({ message: `${message.$ref} is undefined`, code: CliError.Code.ReferenceError });
}

return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
WebsocketMessageSchema,
WebsocketSessionExample
} from "@fern-api/openapi-ir";
import { CliError } from "@fern-api/task-context";
import { camelCase, upperFirst } from "lodash-es";
import { OpenAPIV3 } from "openapi-types";
import { getExtension } from "../../getExtension.js";
Expand Down Expand Up @@ -227,17 +228,21 @@ export function parseAsyncAPIV3({

const channelEvent = channelEvents[channelPath];
if (channelEvent == null) {
throw new Error(
`Internal error: channelEvents["${channelPath}"] is unexpectedly undefined for operation ${operationId}`
);
throw new CliError({
message: `Internal error: channelEvents["${channelPath}"] is unexpectedly undefined for operation ${operationId}`,
code: CliError.Code.InternalError
});
}

if (operation.action === "receive") {
channelEvent.subscribe.push(...messagesWithMethodName);
} else if (operation.action === "send") {
channelEvent.publish.push(...messagesWithMethodName);
} else {
throw new Error(`Operation ${operationId} has an invalid action: ${operation.action}`);
throw new CliError({
message: `Operation ${operationId} has an invalid action: ${operation.action}`,
code: CliError.Code.ValidationError
});
}
}

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

function getChannelPathFromOperation(operation: AsyncAPIV3.Operation): string {
if (!operation.channel) {
throw new Error("Operation is missing required 'channel' field");
throw new CliError({
message: "Operation is missing required 'channel' field",
code: CliError.Code.ValidationError
});
}
if (!operation.channel.$ref) {
throw new Error("Operation channel is missing required '$ref' field");
throw new CliError({
message: "Operation channel is missing required '$ref' field",
code: CliError.Code.ValidationError
});
}
if (!operation.channel.$ref.startsWith(CHANNEL_REFERENCE_PREFIX)) {
throw new Error(`Failed to resolve channel path from operation ${operation.channel.$ref}`);
throw new CliError({
message: `Failed to resolve channel path from operation ${operation.channel.$ref}`,
code: CliError.Code.ReferenceError
});
}
return decodeJsonPointerSegment(operation.channel.$ref.substring(CHANNEL_REFERENCE_PREFIX.length));
}
Expand All @@ -654,38 +668,50 @@ function convertChannelParameterLocation(location: string): {
type: "header" | "path" | "payload";
parameterKey: string;
} {
try {
const [messageType, parameterKey] = location.split("#/");
if (messageType == null || parameterKey == null) {
throw new Error(`Invalid location format: ${location}; unable to parse message type and parameter key`);
}
if (!messageType.startsWith(LOCATION_PREFIX)) {
throw new Error(`Invalid location format: ${location}; expected ${LOCATION_PREFIX} prefix`);
}
const type = messageType.substring(LOCATION_PREFIX.length);
if (type !== "header" && type !== "path" && type !== "payload") {
throw new Error(`Invalid message type: ${type}. Must be one of: header, path, payload`);
}
return { type, parameterKey };
} catch (error) {
throw new Error(
`Invalid location format: ${location}; see here for more details: ` +
"https://www.asyncapi.com/docs/reference/specification/v3.0.0#runtimeExpression"
);
const ASYNCAPI_RUNTIME_EXPRESSION_DOCS =
"https://www.asyncapi.com/docs/reference/specification/v3.0.0#runtimeExpression";
const [messageType, parameterKey] = location.split("#/");
if (messageType == null || parameterKey == null) {
throw new CliError({
message:
`Invalid location format: ${location}; unable to parse message type and parameter key. ` +
`See ${ASYNCAPI_RUNTIME_EXPRESSION_DOCS} for the expected format.`,
code: CliError.Code.ValidationError
});
}
if (!messageType.startsWith(LOCATION_PREFIX)) {
throw new CliError({
message: `Invalid location format: ${location}; expected ${LOCATION_PREFIX} prefix`,
code: CliError.Code.ValidationError
});
}
const type = messageType.substring(LOCATION_PREFIX.length);
if (type !== "header" && type !== "path" && type !== "payload") {
throw new CliError({
message: `Invalid message type: ${type}. Must be one of: header, path, payload`,
code: CliError.Code.ValidationError
});
}
return { type, parameterKey };
}

function getServerNameFromServerRef(
servers: Record<string, ServerContext>,
serverRef: OpenAPIV3.ReferenceObject
): ServerContext {
if (!serverRef.$ref.startsWith(SERVER_REFERENCE_PREFIX)) {
throw new Error(`Failed to resolve server name from server ref ${serverRef.$ref}`);
throw new CliError({
message: `Failed to resolve server name from server ref ${serverRef.$ref}`,
code: CliError.Code.ReferenceError
});
}
const serverName = serverRef.$ref.substring(SERVER_REFERENCE_PREFIX.length);
const server = servers[serverName];
if (server == null) {
throw new Error(`Failed to find server with name ${serverName}`);
throw new CliError({
message: `Failed to find server with name ${serverName}`,
code: CliError.Code.ReferenceError
});
}
return server;
}
Expand Down
Loading
Loading