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

wip: foundation for OpenApi Parser #1814

Open
wants to merge 17 commits into
base: main
Choose a base branch
from

Conversation

RohinBhargava
Copy link
Member

@RohinBhargava RohinBhargava commented Nov 14, 2024

Note: Ignore any of the files in temporary, those are there so that boilerplate does not need to be implemented again.

This PR introduces the base nodes that are necessary for parsing OpenApi Spec.

It introduces 3 abstractions:

  • (private) ApiNode: This defines the domain model for the parser. It contains a context, containing an errorCollector and a Logger. It defines an interface for how to model outputting FDR shapes.
  • InputNodes: These extend from ApiNodes map to OpenApi specific nodes, and can be incrementally updated, so that pathing and location is maintained.
  • OutputNodes: These extend from ApiNodes and do not do anything differently (aside from defining how the constructor is shaped).

The nodes created are:
Primitives:

  • String --> these correspond to any type: "string" nodes we encounter in OpenApi spec

  • Number --> these correspond to any type: "integer | number" nodes we encounter in OpenApi spec

    • Integer: These map to "integer" type nodes and handle discrimination into the right FDR shapes.
    • Float: These map to "float" type nodes and handle discrimination into the right FDR shapes.
  • Schema: An input node that helps us recognize schemas/references as we encounter them in OpenApi spec.

  • Reference: An input node that helps us to resolve references or pass them as Alias types.

  • Object (and all derivatives): An output node that helps us to parse any schema from OpenApi into the correct Fdr shapes.

Additionally, there are a few of types files introduced for cleaner organization:

  • OpenApi types: this helps us share types between OpenApi 2, 3, 3_1 parsers
  • OpenApi formats: this keeps an exhaustive inventory of potential formats encountered on different primitives
  • FdrShapes: Groups FdrShapes that are closely related, so that top level nodes can share logic for all children

Copy link

vercel bot commented Nov 14, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
rbac.ferndocs.com ❌ Failed (Inspect) Nov 14, 2024 0:27am
1 Skipped Deployment
Name Status Preview Updated (UTC)
fern-shell ⬜️ Ignored (Inspect) Nov 14, 2024 0:27am

Copy link

github-actions bot commented Nov 14, 2024

📦 Next.js Bundle Analysis for fern-platform-monorepo

This analysis was generated by the Next.js Bundle Analysis action. 🤖

This PR introduced no changes to the JavaScript bundle! 🙌

Copy link

github-actions bot commented Nov 14, 2024

PR Preview

Copy link

github-actions bot commented Nov 14, 2024

Playwright test results

passed  95 passed
flaky  3 flaky
skipped  3 skipped

Details

stats  101 tests across 10 suites
duration  1 minute, 58 seconds
commit  c3768ba

Flaky tests

chromium › forward-proxy/nextjs.spec.ts › capture the flag
chromium › smoke/favicon.spec.ts › Check if favicon exists and URL does not return 404 for api.qdrant.tech
chromium › smoke/favicon.spec.ts › Check if favicon exists and URL does not return 404 for docs.getkard.com

Skipped tests

chromium › posthog.spec.ts › Posthog loads successfully
chromium › proxy.spec.ts › multipart-form upload
chromium › proxy.spec.ts › json request

import { SchemaObject } from "../../../openapi/shared/openapi.types";
import { createMockContext } from "../../createMockContext.util";

describe("ObjectNode", () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mega nit: i think it might be nicer to put these test files through out the code base as opposed to the top, that way you can see object.node.ts and object.node.test.ts side-by-side

}
}

function convertExtraPropertiesBoolean(displayName: string | undefined): {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this pattern is interesting -- it is done for extensibility. If we want to add a new type of output to a node in the future, can be as simple as adding a new member to the return type of this function. @dsinghvi

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool -- it feels a little complex but happy to defer to you

) {
super(input, context, accessPath, pathId);

if (typeof input.default !== "boolean") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if it equals null, wont this error?

| StringConverterNode.Input;

export class SchemaConverterNode extends BaseOpenApiV3_1Node<
OpenAPIV3_1.SchemaObject,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this probably should handle Schema | Reference

// break;
// }

if (input != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can the input here even be null?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, this was just to satisfy the type checker in an intermediate fashion, but can be removed

maxLength: number | undefined;

private mapToFdrType = (
format: ConstArrayToType<typeof OPENAPI_STRING_TYPE_FORMAT>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if format is undefined

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it returns "string". The property should be set to default to "string", however

return new ReferenceConverterNode(input, context, accessPath, pathId);
}
return new SchemaConverterNode(input, context, accessPath, pathId);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: thoughts on putting this in the SchemaNode that way regardless of whatever schema you have: a reference or an inlined shape, it will work

import { OpenAPIV3_1 } from "openapi-types";

export function mapReferenceObject(referenceObject: OpenAPIV3_1.ReferenceObject): OpenAPIV3_1.SchemaObject {
return referenceObject.$ref as OpenAPIV3_1.SchemaObject;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels suspect, what's the utility of this coercion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, this is a placeholder for now -- need to migrate the resolution logic in from fern main repo, and it should live in this function. I'll leave a TODO comment

// if (input.allOf != null) {
// this.extends = input.allOf
// .map((type) => chooseReferenceOrSchemaNode(type, context, accessPath, pathId))
// .filter(isNonNullish);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does chooseReferenceOrSchemaNode do? I could see a SchemaOrReferenceConverterNode.ts so that you can just directly pass the input in

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

discussed offline -- this might be confusing for folks if there are two types of nodes that accept ReferenceObjects, but is also easily portable into the SchemaNode


if (this.valueShape == null) {
this.context.errors.error({
message: `Unprocessable type shape: ${this.propertyKey}`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors should speak the language of the input to make it easier for the user to understand

import { ReferenceConverterNode } from "./ReferenceConverter.node";
import { SchemaConverterNode } from "./SchemaConverter.node";

export class ObjectPropertyConverterNode extends BaseOpenApiV3_1Node<
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need this entire class at all? ideally you should just be able to redirect all object properties back to the schema node (example: https://github.com/fern-api/fern/blob/5ab95904f1a1d1a0fb4a950d1f046b8d603b78c6/generators/openapi/src/converters/convertObject.ts#L30)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a good point, this logic should live in schema node, yeah

OpenAPIV3_1.SchemaObject,
FdrAPI.api.latest.TypeShape | undefined
> {
typeShapeNode: { convert: () => FdrAPI.api.latest.TypeShape | undefined } | undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same pattern as above, it encapsulates the conversion function so that it's easy to reason about how the domain object functions should convert -- if singular functions, those will be splayed into the constructor (just a way to group for different exports)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants