Skip to content

Commit

Permalink
fix: OpenApi Parser v2 relax no schemas constraint (#2107)
Browse files Browse the repository at this point in the history
  • Loading branch information
RohinBhargava authored Feb 3, 2025
1 parent 2d5ad51 commit 4a79b06
Show file tree
Hide file tree
Showing 43 changed files with 13,278 additions and 341 deletions.
2 changes: 1 addition & 1 deletion packages/parsers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fern-api/docs-parsers",
"version": "0.0.46",
"version": "0.0.47",
"repository": {
"type": "git",
"url": "https://github.com/fern-api/fern-platform.git",
Expand Down
20 changes: 7 additions & 13 deletions packages/parsers/src/openapi/3.1/OpenApiDocumentConverter.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,7 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode<
);
}

if (this.input.components == null) {
this.context.errors.warning({
message: "Expected 'components' property to be specified",
path: this.accessPath,
});
} else {
if (this.input.components != null) {
this.components = new ComponentsConverterNode({
input: this.input.components,
context: this.context,
Expand Down Expand Up @@ -150,10 +145,6 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode<

const types = this.components?.convert();

if (types == null) {
return undefined;
}

return {
id: FernRegistry.ApiDefinitionId(apiDefinitionId),
endpoints: endpoints ?? {},
Expand All @@ -163,9 +154,12 @@ export class OpenApiDocumentConverterNode extends BaseOpenApiV3_1ConverterNode<
...(this.webhooks?.convert() ?? {}),
...(webhookEndpoints ?? {}),
},
types: Object.fromEntries(
Object.entries(types).map(([id, type]) => [id, type])
),
types:
types != null
? Object.fromEntries(
Object.entries(types).map(([id, type]) => [id, type])
)
: {},
// This is not necessary and will be removed
subpackages,
auths: this.auth?.convert() ?? {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export class RequestMediaTypeObjectConverterNode extends BaseOpenApiV3_1Converte
context: this.context,
accessPath: this.accessPath,
pathId: "schema",
seenSchemas: new Set(),
});
} else if (isObjectSchema(this.input.schema)) {
this.resolvedSchema = this.input.schema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@ export class ComponentsConverterNode extends BaseOpenApiV3_1ConverterNode<
}

parse(): void {
if (this.input.schemas == null) {
this.context.errors.warning({
message: "Expected 'schemas' property to be specified",
path: this.accessPath,
});
} else {
if (this.input.schemas != null) {
this.typeSchemas = Object.fromEntries(
Object.entries(this.input.schemas).map(([key, value]) => {
return [
Expand Down
13 changes: 9 additions & 4 deletions packages/parsers/src/openapi/3.1/schemas/ObjectConverter.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class ObjectConverterNode extends BaseOpenApiV3_1ConverterNodeWithTrackin
input: property,
context: this.context,
accessPath: this.accessPath,
pathId: this.pathId,
pathId: ["properties", key],
seenSchemas: this.seenSchemas,
}),
];
Expand All @@ -68,15 +68,15 @@ export class ObjectConverterNode extends BaseOpenApiV3_1ConverterNodeWithTrackin
input: this.input.additionalProperties,
context: this.context,
accessPath: this.accessPath,
pathId: this.pathId,
pathId: ["additionalProperties"],
seenSchemas: this.seenSchemas,
})
: undefined;

if (this.input.allOf != null) {
this.extends = this.extends.concat(
this.input.allOf
.map((type) => {
.map((type, allOfIndex) => {
if (isReferenceObject(type)) {
return getSchemaIdFromReference(type);
} else {
Expand All @@ -91,7 +91,12 @@ export class ObjectConverterNode extends BaseOpenApiV3_1ConverterNodeWithTrackin
input: property,
context: this.context,
accessPath: this.accessPath,
pathId: this.pathId,
pathId: [
"allOf",
allOfIndex.toString(),
"properties",
key,
],
seenSchemas: this.seenSchemas,
}),
];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import { OpenAPIV3_1 } from "openapi-types";
import { FernRegistry } from "../../../client/generated";
import {
BaseOpenApiV3_1ConverterNode,
BaseOpenApiV3_1ConverterNodeConstructorArgs,
BaseOpenApiV3_1ConverterNodeWithTracking,
BaseOpenApiV3_1ConverterNodeWithTrackingConstructorArgs,
} from "../../BaseOpenApiV3_1Converter.node";
import { getSchemaIdFromReference } from "../../utils/3.1/getSchemaIdFromReference";
import { resolveSchemaReference } from "../../utils/3.1/resolveSchemaReference";
import { SchemaConverterNode } from "./SchemaConverter.node";

export class ReferenceConverterNode extends BaseOpenApiV3_1ConverterNode<
export class ReferenceConverterNode extends BaseOpenApiV3_1ConverterNodeWithTracking<
OpenAPIV3_1.ReferenceObject,
FernRegistry.api.latest.TypeShape.Alias
> {
schemaId: string | undefined;

constructor(
args: BaseOpenApiV3_1ConverterNodeConstructorArgs<OpenAPIV3_1.ReferenceObject>
args: BaseOpenApiV3_1ConverterNodeWithTrackingConstructorArgs<OpenAPIV3_1.ReferenceObject>
) {
super(args);
this.safeParse();
Expand Down Expand Up @@ -58,8 +58,8 @@ export class ReferenceConverterNode extends BaseOpenApiV3_1ConverterNode<
input: schema,
context: this.context,
accessPath: this.accessPath,
pathId: this.input.$ref.split("/").pop() ?? "",
seenSchemas: new Set(),
pathId: this.schemaId ?? "",
seenSchemas: this.seenSchemas,
}).example();
}
}
32 changes: 20 additions & 12 deletions packages/parsers/src/openapi/3.1/schemas/SchemaConverter.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
BaseOpenApiV3_1ConverterNodeWithTracking,
BaseOpenApiV3_1ConverterNodeWithTrackingConstructorArgs,
} from "../../BaseOpenApiV3_1Converter.node";
import { getSchemaIdFromReference } from "../../utils/3.1/getSchemaIdFromReference";
import { maybeSingleValueToArray } from "../../utils/maybeSingleValueToArray";
import { wrapNullable } from "../../utils/wrapNullable";
import { AvailabilityConverterNode } from "../extensions/AvailabilityConverter.node";
Expand Down Expand Up @@ -71,17 +72,6 @@ export class SchemaConverterNode extends BaseOpenApiV3_1ConverterNodeWithTrackin
}

parse(): void {
if (!isReferenceObject(this.input)) {
if (this.accessPath.length >= 100 || this.seenSchemas.has(this.input)) {
this.context.errors.warning({
message: "Circular or deeply nested schema found, terminating",
path: this.accessPath,
});
return;
}
this.seenSchemas.add(this.input);
}

this.description = this.input.description;
this.availability = new AvailabilityConverterNode({
input: this.input,
Expand All @@ -96,11 +86,29 @@ export class SchemaConverterNode extends BaseOpenApiV3_1ConverterNodeWithTrackin

// Check if the input is a reference object
if (isReferenceObject(this.input)) {
const refPath = getSchemaIdFromReference(this.input);
if (refPath == null) {
this.context.errors.error({
message: "Reference object does not have a valid schema ID",
path: this.accessPath,
});
return;
}
if (this.seenSchemas.has(refPath)) {
this.context.errors.warning({
message: "Circular or deeply nested schema found, terminating",
path: this.accessPath,
});
return;
}
this.seenSchemas.add(refPath);

this.typeShapeNode = new ReferenceConverterNode({
input: this.input,
context: this.context,
accessPath: this.accessPath,
pathId: this.pathId,
pathId: refPath,
seenSchemas: this.seenSchemas,
});
} else {
// If the object is not a reference object, then it is a schema object, gather all appropriate variables
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ describe("ComponentsConverterNode", () => {
});

expect(converter.typeSchemas).toBeUndefined();
expect(mockContext.errors.warning).toHaveBeenCalledWith({
message: "Expected 'schemas' property to be specified",
path: ["test"],
});
});

it("should convert schemas correctly", () => {
Expand Down
28 changes: 17 additions & 11 deletions packages/parsers/src/openapi/BaseOpenApiV3_1Converter.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,22 @@ export abstract class BaseOpenApiV3_1ConverterNode<
this.accessPath = [...accessPath];
this.pathId = pathId;

if (
this.pathId != null &&
this.pathId !== this.accessPath[this.accessPath.length - 1]
) {
this.accessPath.push(
...(Array.isArray(this.pathId) ? this.pathId : [this.pathId])
);

context.logger.debug(`Processing ${toOpenApiPath(this.accessPath)}`);
if (pathId != null) {
const pathIdArray = Array.isArray(this.pathId)
? this.pathId
: [this.pathId];
if (
!pathIdArray.every(
(id, index) =>
id ===
this.accessPath[this.accessPath.length - pathIdArray.length + index]
)
) {
this.accessPath.push(...pathIdArray);
}
}

context.logger.debug(`Processing ${toOpenApiPath(this.accessPath)}`);
}

abstract parse(...additionalArgs: unknown[]): void;
Expand All @@ -72,14 +78,14 @@ export abstract class BaseOpenApiV3_1ConverterNodeWithExample<

export type BaseOpenApiV3_1ConverterNodeWithTrackingConstructorArgs<Input> =
BaseOpenApiV3_1ConverterNodeConstructorArgs<Input> & {
seenSchemas: Set<OpenAPIV3_1.SchemaObject>;
seenSchemas: Set<OpenAPIV3_1.ReferenceObject["$ref"]>;
};

export abstract class BaseOpenApiV3_1ConverterNodeWithTracking<
Input,
Output,
> extends BaseOpenApiV3_1ConverterNodeWithExample<Input, Output> {
protected readonly seenSchemas: Set<OpenAPIV3_1.SchemaObject>;
protected readonly seenSchemas: Set<OpenAPIV3_1.ReferenceObject["$ref"]>;

constructor(
args: BaseOpenApiV3_1ConverterNodeWithTrackingConstructorArgs<Input>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import yaml from "js-yaml";
import { OpenAPIV3_1 } from "openapi-types";
import * as path from "path";
import { describe, expect, it } from "vitest";
import { ApiDefinitionId } from "../../client/generated/api";
import { ErrorCollector } from "../../ErrorCollector";
import { OpenApiDocumentConverterNode } from "../3.1/OpenApiDocumentConverter.node";
import { BaseOpenApiV3_1ConverterNodeContext } from "../BaseOpenApiV3_1Converter.node";
Expand Down Expand Up @@ -39,23 +40,20 @@ describe("OpenAPI snapshot tests", () => {
};

// Convert components if they exist
let converted;
const errors = [];
const warnings = [];

// expect(parsed.components?.schemas).toBeDefined();

if (parsed.components?.schemas) {
const converter = new OpenApiDocumentConverterNode({
input: parsed,
context,
accessPath: [],
pathId: undefined,
});
errors.push(...converter.errors());
warnings.push(...converter.warnings());
converted = converter.convert();
}
const converter = new OpenApiDocumentConverterNode({
input: parsed,
context,
accessPath: [],
pathId: undefined,
});
errors.push(...converter.errors());
warnings.push(...converter.warnings());
const converted = converter.convert();

if (errors.length > 0) {
await expect(errors).toMatchFileSnapshot(
Expand All @@ -69,7 +67,7 @@ describe("OpenAPI snapshot tests", () => {
}

if (converted) {
converted.id = "test-uuid-replacement";
converted.id = ApiDefinitionId("test-uuid-replacement");
}
await expect(
replaceEndpointUUIDs(JSON.stringify(converted, null, 2))
Expand Down
Loading

0 comments on commit 4a79b06

Please sign in to comment.