Skip to content

Commit

Permalink
feat(openrpc): parse methods into fdr latest endpoints (#2003)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi authored Jan 14, 2025
1 parent 4520edf commit 7783034
Show file tree
Hide file tree
Showing 11 changed files with 6,373 additions and 4 deletions.
213 changes: 213 additions & 0 deletions packages/parsers/src/openrpc/1.x/MethodConverter.node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { isNonNullish } from "@fern-api/ui-core-utils";
import { MethodObject } from "@open-rpc/meta-schema";
import { UnreachableCaseError } from "ts-essentials";
import { FernRegistry } from "../../client/generated";
import { SchemaConverterNode } from "../../openapi";
import { maybeSingleValueToArray } from "../../openapi/utils/maybeSingleValueToArray";
import {
BaseOpenrpcConverterNode,
BaseOpenrpcConverterNodeConstructorArgs,
} from "../BaseOpenrpcConverter.node";
import { resolveContentDescriptorObject } from "../utils/resolveContentDescriptorObject";
import { resolveExample } from "../utils/resolveExample";
import { resolveExamplePairingOrReference } from "../utils/resolveExamplePairing";

export class MethodConverterNode extends BaseOpenrpcConverterNode<
MethodObject,
FernRegistry.api.latest.EndpointDefinition
> {
private method: MethodObject;

constructor(args: BaseOpenrpcConverterNodeConstructorArgs<MethodObject>) {
super(args);
this.method = args.input;
this.safeParse();
}

parse(): void {
// Parse method object
}

convert(): FernRegistry.api.latest.EndpointDefinition | undefined {
try {
const resolvedResult = this.method.result
? resolveContentDescriptorObject(
this.method.result,
this.context.openrpc
)
: undefined;

const response = resolvedResult
? new SchemaConverterNode({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input: resolvedResult.schema as any,
context: this.context,
accessPath: this.accessPath,
pathId: "result",
}).convert()
: undefined;

const resolvedParams = this.method.params
?.map((param) =>
resolveContentDescriptorObject(param, this.context.openrpc)
)
.filter(isNonNullish);

const requestParameters: FernRegistry.api.latest.ObjectProperty[] =
resolvedParams
?.map((param): FernRegistry.api.latest.ObjectProperty | undefined => {
const schema = new SchemaConverterNode({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input: param.schema as any,
context: this.context,
accessPath: this.accessPath,
pathId: `params/${param.name}`,
}).convert();

if (!schema) return undefined;

const valueShape = Array.isArray(schema) ? schema[0] : schema;
if (!valueShape) {
return undefined;
}

return {
key: FernRegistry.PropertyKey(param.name),
valueShape,
description: param.description,
availability: undefined,
};
})
.filter(isNonNullish);

const request: FernRegistry.api.latest.HttpRequest | undefined =
requestParameters.length > 0
? {
contentType: "application/json",
body: {
type: "object",
extends: [],
properties: requestParameters,
extraProperties: undefined,
},
description: undefined,
}
: undefined;

// Convert method to HTTP endpoint
// This is a basic implementation that needs to be expanded
return {
id: FernRegistry.EndpointId(this.input.name),
displayName: this.input.summary ?? this.input.name,
method: "POST",
path: [{ type: "literal", value: "" }],
auth: undefined,
pathParameters: [],
queryParameters: [],
requests: request != null ? [request] : undefined,
responses:
response != null
? [
this.convertToHttpResponse(response, this.input.description),
].filter(isNonNullish)
: [],
errors: [],
examples: this.method.examples
?.map(
(
example
): FernRegistry.api.latest.ExampleEndpointCall | undefined => {
const resolvedExample = resolveExamplePairingOrReference(
example,
this.context.openrpc
);
if (!resolvedExample) return undefined;
return {
name: resolvedExample.name ?? "Example",
path: "",
description: undefined,
snippets: undefined,
pathParameters: {},
queryParameters: {},
headers: {},
requestBody:
resolvedExample.params.length > 0
? {
type: "json",
value: resolvedExample.params.map((param) => {
const resolvedParam = resolveExample(
param,
this.context.openrpc
);
if (!resolvedParam) return undefined;
return resolvedParam.value;
}),
}
: undefined,
responseStatusCode: 200,
responseBody: resolvedExample.result
? { type: "json", value: resolvedExample.result }
: undefined,
};
}
)
.filter(isNonNullish),
description: this.input.description,
operationId: this.input.name,
defaultEnvironment: undefined,
environments: [],
availability: this.method.deprecated ? "Deprecated" : undefined,
requestHeaders: [],
responseHeaders: [],
snippetTemplates: undefined,
namespace: [],
};
} catch (_error) {
this.context.errors.error({
message: "Failed to convert method",
path: this.accessPath,
});
return undefined;
}
}

private convertToHttpResponse(
shape:
| FernRegistry.api.latest.TypeShape
| FernRegistry.api.latest.TypeShape[],
description?: string
): FernRegistry.api.latest.HttpResponse | undefined {
if (shape == null) {
return undefined;
}
const maybeShapes = maybeSingleValueToArray(shape);
const validShape = maybeShapes
?.map((shape) => {
const type = shape.type;
switch (type) {
case "alias":
return shape;
case "discriminatedUnion":
case "undiscriminatedUnion":
case "enum":
return undefined;
case "object":
return shape;
default:
new UnreachableCaseError(type);
return undefined;
}
})
.filter(isNonNullish)[0];

if (!validShape) {
return undefined;
}

return {
statusCode: 200,
body: validShape,
description,
};
}
}
39 changes: 36 additions & 3 deletions packages/parsers/src/openrpc/1.x/OpenrpcDocumentConverter.node.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isNonNullish } from "@fern-api/ui-core-utils";
import { OpenrpcDocument } from "@open-rpc/meta-schema";
import { v4 } from "uuid";
import { FernRegistry } from "../../client/generated";
Expand All @@ -6,11 +7,14 @@ import {
BaseOpenrpcConverterNode,
BaseOpenrpcConverterNodeConstructorArgs,
} from "../BaseOpenrpcConverter.node";
import { resolveMethodReference } from "../utils/resolveMethodReference";
import { MethodConverterNode } from "./MethodConverter.node";

export class OpenrpcDocumentConverterNode extends BaseOpenrpcConverterNode<
OpenrpcDocument,
FernRegistry.api.latest.ApiDefinition
> {
methods: MethodConverterNode[] = [];
components: ComponentsConverterNode | undefined;

constructor(args: BaseOpenrpcConverterNodeConstructorArgs<OpenrpcDocument>) {
Expand All @@ -19,9 +23,28 @@ export class OpenrpcDocumentConverterNode extends BaseOpenrpcConverterNode<
}

parse(): void {
if (this.context.document.components != null) {
if (this.input.methods != null) {
for (const method of this.input.methods) {
const resolvedMethod = resolveMethodReference(
method,
this.context.openrpc
);
if (resolvedMethod == null) {
continue;
}
this.methods.push(
new MethodConverterNode({
input: resolvedMethod,
context: this.context,
accessPath: this.accessPath,
pathId: "methods",
})
);
}
}
if (this.context.openrpc.components != null) {
this.components = new ComponentsConverterNode({
input: this.context.document.components,
input: this.context.openrpc.components,
context: this.context,
accessPath: this.accessPath,
pathId: "components",
Expand All @@ -33,12 +56,22 @@ export class OpenrpcDocumentConverterNode extends BaseOpenrpcConverterNode<
const apiDefinitionId = v4();
const types = this.components?.convert();

console.log(this.methods?.length);

const methods = this.methods
?.map((method) => {
return method.convert();
})
.filter(isNonNullish);

return {
id: FernRegistry.ApiDefinitionId(apiDefinitionId),
types: Object.fromEntries(
Object.entries(types ?? {}).map(([id, type]) => [id, type])
),
endpoints: {},
endpoints: Object.fromEntries(
methods?.map((method) => [method.id, method]) ?? []
),
websockets: {},
webhooks: {},
subpackages: {},
Expand Down
Loading

0 comments on commit 7783034

Please sign in to comment.