Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: OpenApi Parser v2 Subpackage filtering #1863

Merged
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
887 changes: 481 additions & 406 deletions packages/parsers/src/__test__/__snapshots__/openapi/cohere.json

Large diffs are not rendered by default.

35 changes: 28 additions & 7 deletions packages/parsers/src/__test__/__snapshots__/openapi/deeptune.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{
"id": "test-uuid-replacement",
"endpoints": {
"test-uuid-replacement": {
"post-v-1-text-to-speech": {
"description": "API that converts text into lifelike speech with best-in-class latency & uses the most advanced AI audio model ever. Create voiceovers for your videos, audiobooks, or create AI chatbots for free.",
"namespace": [
"text_to_speech"
],
"id": "post-v-1-text-to-speech",
"method": "POST",
"path": [
Expand Down Expand Up @@ -35,8 +38,11 @@
"errors": [],
"examples": []
},
"test-uuid-replacement": {
"post-v-1-text-to-speech-from-prompt": {
"description": "If you prefer to manage voices on your own, you can use your own audio file as a reference for the voice clone.x",
"namespace": [
"text_to_speech"
],
"id": "post-v-1-text-to-speech-from-prompt",
"method": "POST",
"path": [
Expand Down Expand Up @@ -73,8 +79,11 @@
"errors": [],
"examples": []
},
"test-uuid-replacement": {
"get-v-1-voices": {
"description": "Retrieve all voices associated with the current workspace.",
"namespace": [
"voices"
],
"id": "get-v-1-voices",
"method": "GET",
"path": [
Expand Down Expand Up @@ -108,8 +117,11 @@
"errors": [],
"examples": []
},
"test-uuid-replacement": {
"post-v-1-voices": {
"description": "Create a new voice with a name, optional description, and audio file.",
"namespace": [
"voices"
],
"id": "post-v-1-voices",
"method": "POST",
"path": [
Expand Down Expand Up @@ -153,8 +165,11 @@
"errors": [],
"examples": []
},
"test-uuid-replacement": {
"get-v-1-voices-voice-id": {
"description": "Retrieve a specific voice by its ID.",
"namespace": [
"voices"
],
"id": "get-v-1-voices-voice-id",
"method": "GET",
"path": [
Expand Down Expand Up @@ -207,8 +222,11 @@
"errors": [],
"examples": []
},
"test-uuid-replacement": {
"put-v-1-voices-voice-id": {
"description": "Update an existing voice with new name, description, or audio file.",
"namespace": [
"voices"
],
"id": "put-v-1-voices-voice-id",
"method": "PUT",
"path": [
Expand Down Expand Up @@ -271,8 +289,11 @@
"errors": [],
"examples": []
},
"test-uuid-replacement": {
"delete-v-1-voices-voice-id": {
"description": "Delete an existing voice by its ID.",
"namespace": [
"voices"
],
"id": "delete-v-1-voices-voice-id",
"method": "DELETE",
"path": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "test-uuid-replacement",
"endpoints": {
"test-uuid-replacement": {
"post-pet": {
"description": "Add a new pet to the store",
"id": "post-pet",
"method": "POST",
Expand Down Expand Up @@ -43,7 +43,7 @@
"errors": [],
"examples": []
},
"test-uuid-replacement": {
"put-pet": {
"description": "Update an existing pet by Id",
"id": "put-pet",
"method": "PUT",
Expand Down Expand Up @@ -85,7 +85,7 @@
"errors": [],
"examples": []
},
"test-uuid-replacement": {
"get-pet-pet-id": {
"description": "Returns a pet when 0 < ID <= 10. ID > 10 or nonintegers will simulate API error conditions",
"id": "get-pet-pet-id",
"method": "GET",
Expand Down Expand Up @@ -144,7 +144,7 @@
}
},
"description": "Invalid ID supplied",
"name": "dummy error"
"name": "Bad Request"
},
{
"statusCode": 404,
Expand All @@ -156,7 +156,7 @@
}
},
"description": "Pet not found",
"name": "dummy error"
"name": "Not Found"
}
],
"examples": []
Expand Down
44 changes: 29 additions & 15 deletions packages/parsers/src/openapi/3.1/OpenApiDocumentConverter.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,22 @@ import {
} from "../BaseOpenApiV3_1Converter.node";
import { coalesceServers } from "../utils/3.1/coalesceServers";
import { SecurityRequirementObjectConverterNode } from "./auth/SecurityRequirementObjectConverter.node";
import { XFernGroupsConverterNode } from "./extensions/XFernGroupsConverter.node";
import { PathsObjectConverterNode } from "./paths/PathsObjectConverter.node";
import { ServerObjectConverterNode } from "./paths/ServerObjectConverter.node";
import { WebhooksObjectConverterNode } from "./paths/WebhooksObjectConverter.node";
import { ComponentsConverterNode } from "./schemas/ComponentsConverter.node";

export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode<
OpenAPIV3_1.Document,
FernRegistry.api.latest.ApiDefinition
> {
paths: PathsObjectConverterNode | undefined;
// webhooks: WebhooksObjectConverterNode | undefined;
webhooks: WebhooksObjectConverterNode | undefined;
components: ComponentsConverterNode | undefined;
servers: ServerObjectConverterNode[] | undefined;
auth: SecurityRequirementObjectConverterNode | undefined;
fernGroups: XFernGroupsConverterNode | undefined;

constructor(args: BaseOpenApiV3_1ConverterNodeConstructorArgs<OpenAPIV3_1.Document>) {
super(args);
Expand All @@ -29,12 +32,14 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode<
parse(): void {
this.servers = coalesceServers(this.servers, this.input.servers, this.context, this.accessPath);

if (this.input.paths == null) {
if (this.input.paths == null && this.input.webhooks == null) {
this.context.errors.warning({
message: "Expected 'paths' property to be specified",
message: "Expected 'paths' or 'webhooks' property to be specified",
path: this.accessPath,
});
} else {
}

if (this.input.paths != null) {
this.paths = new PathsObjectConverterNode(
{
input: this.input.paths,
Expand All @@ -46,7 +51,14 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode<
);
}

// TODO: Webhook disambiguation
if (this.input.webhooks != null) {
this.webhooks = new WebhooksObjectConverterNode({
input: this.input.webhooks,
context: this.context,
accessPath: this.accessPath,
pathId: "webhooks",
});
}

if (this.input.components == null) {
this.context.errors.warning({
Expand All @@ -70,14 +82,19 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode<
pathId: "security",
});
}

this.fernGroups = new XFernGroupsConverterNode({
input: this.input,
context: this.context,
accessPath: this.accessPath,
pathId: "x-fern-groups",
});
}

convert(): FernRegistry.api.latest.ApiDefinition | undefined {
const apiDefinitionId = v4();

const endpoints = this.paths?.convert();
// TODO: Implement webhooks
// const webhooks = this.webhooks?.convert();
const { webhookEndpoints, endpoints } = this.paths?.convert() ?? {};
const types = this.components?.convert();

if (types == null) {
Expand All @@ -88,14 +105,11 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode<
id: FernRegistry.ApiDefinitionId(apiDefinitionId),
endpoints: endpoints ?? {},
// Websockets are not implemented in OAS, but are in AsyncAPI
websockets: {} as Record<FernRegistry.WebSocketId, FernRegistry.api.latest.WebSocketChannel>,
// TODO: implement webhooks
// webhooks,
webhooks: {} as Record<FernRegistry.WebhookId, FernRegistry.api.latest.WebhookDefinition>,
websockets: {},
webhooks: { ...(this.webhooks?.convert() ?? {}), ...(webhookEndpoints ?? {}) },
types,
// TODO: check if we ever have subpackages
subpackages: {} as Record<FernRegistry.api.latest.SubpackageId, FernRegistry.api.latest.SubpackageMetadata>,
// TODO: Implement auths
// This is not necessary and will be removed
subpackages: {},
auths: this.auth?.convert() ?? {},
// TODO: Implement globalHeaders
globalHeaders: undefined,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { OpenAPIV3_1 } from "openapi-types";
import { UnreachableCaseError } from "ts-essentials";
import { BaseOpenApiV3_1ConverterNode, BaseOpenApiV3_1ConverterNodeConstructorArgs } from "../..";
import { FernRegistry } from "../../../client/generated";
import {
BaseOpenApiV3_1ConverterNode,
BaseOpenApiV3_1ConverterNodeConstructorArgs,
} from "../../BaseOpenApiV3_1Converter.node";
import { XFernBasicAuthNode } from "../extensions/auth/XFernBasicAuth.node";
import { XFernBasicPasswordVariableNameConverterNode } from "../extensions/auth/XFernBasicPasswordVariableNameConverter.node";
import { XFernBasicUsernameVariableNameConverterNode } from "../extensions/auth/XFernBasicUsernameVariableNameConverter.node";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { FernRegistry } from "../../../client/generated";
import {
BaseOpenApiV3_1ConverterNode,
BaseOpenApiV3_1ConverterNodeConstructorArgs,
} from "../../BaseOpenApiV3_1Converter.node";
import { extendType } from "../../utils/extendType";

const xFernGroupNameKey = "x-fern-sdk-group-name";

export declare namespace XFernGroupNameConverterNode {
export interface Input {
"x-fern-group-name"?: string;
[xFernGroupNameKey]?: string | string[];
}
}

export class XFernGroupNameConverterNode extends BaseOpenApiV3_1ConverterNode<unknown, string> {
groupName?: string;
export class XFernGroupNameConverterNode extends BaseOpenApiV3_1ConverterNode<
unknown,
FernRegistry.api.latest.SubpackageId[]
> {
groupName?: string | string[];

constructor(args: BaseOpenApiV3_1ConverterNodeConstructorArgs<unknown>) {
super(args);
Expand All @@ -20,10 +26,21 @@ export class XFernGroupNameConverterNode extends BaseOpenApiV3_1ConverterNode<un

// This would be used to set a member on the node
parse(): void {
this.groupName = extendType<XFernGroupNameConverterNode.Input>(this.input)["x-fern-group-name"];
this.groupName = extendType<XFernGroupNameConverterNode.Input>(this.input)[xFernGroupNameKey];
}

convert(): string | undefined {
return this.groupName;
convert(): FernRegistry.api.latest.SubpackageId[] | undefined {
if (this.groupName == null) {
return undefined;
}

let subpackagePath: string[];
if (Array.isArray(this.groupName)) {
subpackagePath = this.groupName;
} else {
subpackagePath = [this.groupName];
}

return subpackagePath.map((path) => FernRegistry.api.v1.SubpackageId(path));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { noop } from "ts-essentials";
import {
BaseOpenApiV3_1ConverterNode,
BaseOpenApiV3_1ConverterNodeConstructorArgs,
} from "../../BaseOpenApiV3_1Converter.node";
import { extendType } from "../../utils/extendType";

const xFernGroupsKey = "x-fern-groups";
export declare namespace XFernGroupsConverterNode {
export interface Group {
description?: string;
summary?: string;
}

export interface Input {
[xFernGroupsKey]: Record<string, Group>[];
}
}

export class XFernGroupsConverterNode extends BaseOpenApiV3_1ConverterNode<unknown, void> {
groups?: Record<string, XFernGroupsConverterNode.Group>[];

constructor(args: BaseOpenApiV3_1ConverterNodeConstructorArgs<unknown>) {
super(args);
this.safeParse();
}

parse(): void {
this.groups = extendType<XFernGroupsConverterNode.Input>(this.input)[xFernGroupsKey];
}

convert(): void | undefined {
noop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BaseOpenApiV3_1ConverterNode } from "../../BaseOpenApiV3_1Converter.node";
import { extendType } from "../../utils/extendType";

const xFernWebhookKey = "x-fern-webhook";

export declare namespace XFernWebhookConverterNode {
export interface Input {
[xFernWebhookKey]: boolean;
}
}

export class XFernWebhookConverterNode extends BaseOpenApiV3_1ConverterNode<unknown, boolean> {
isWebhook?: boolean;

parse(): void {
this.isWebhook = extendType<XFernWebhookConverterNode.Input>(this.input)[xFernWebhookKey];
}
convert(): boolean | undefined {
return this.isWebhook ? undefined : undefined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ describe("XFernGroupNameConverterNode", () => {
describe("parse", () => {
it("sets groupName from x-fern-group-name when present", () => {
const converter = new XFernGroupNameConverterNode({
input: { "x-fern-group-name": "test-group" },
input: { "x-fern-sdk-group-name": "test-group" },
context: mockContext,
accessPath: [],
pathId: "",
});
expect(converter.groupName).toBe("test-group");
expect(converter.groupName).toEqual("test-group");
});

it("sets groupName to undefined when x-fern-group-name is not present", () => {
Expand All @@ -29,12 +29,12 @@ describe("XFernGroupNameConverterNode", () => {
describe("convert", () => {
it("returns the groupName value", () => {
const converter = new XFernGroupNameConverterNode({
input: { "x-fern-group-name": "test-group" },
input: { "x-fern-sdk-group-name": "test-group" },
context: mockContext,
accessPath: [],
pathId: "",
});
expect(converter.convert()).toBe("test-group");
expect(converter.convert()).toEqual(["test-group"]);
});

it("returns undefined when groupName is not set", () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { FernRegistry } from "../../../client/generated";

export function isWebhookDefinition(
definition: FernRegistry.api.latest.EndpointDefinition | FernRegistry.api.latest.WebhookDefinition,
): definition is FernRegistry.api.latest.WebhookDefinition {
return "payload" in definition;
}
Loading
Loading