Skip to content

Commit

Permalink
feat(typescript): Add DynamicTypeLiteralMapper (#5551)
Browse files Browse the repository at this point in the history
  • Loading branch information
amckinney authored Jan 8, 2025
1 parent f6b0a81 commit 65113d2
Show file tree
Hide file tree
Showing 14 changed files with 1,178 additions and 36 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertNever } from "@fern-api/core-utils";
import { assertNever, keys } from "@fern-api/core-utils";
import { FernIr } from "@fern-api/dynamic-ir-sdk";
import { HttpEndpointReferenceParser } from "@fern-api/fern-definition-schema";

Expand Down Expand Up @@ -245,6 +245,54 @@ export abstract class AbstractDynamicSnippetsGeneratorContext {
return typeof environment === "object";
}

public validateMultiEnvironmentUrlValues(
multiEnvironmentUrlValues: FernIr.dynamic.MultipleEnvironmentUrlValues
): boolean {
if (this._ir.environments == null) {
this.errors.add({
severity: Severity.Critical,
message:
"Multiple environments are not supported for single base URL environments; use the baseUrl option instead"
});
return false;
}
const environments = this._ir.environments.environments;
switch (environments.type) {
case "singleBaseUrl": {
this.errors.add({
severity: Severity.Critical,
message:
"Multiple environments are not supported for single base URL environments; use the baseUrl option instead"
});
return false;
}
case "multipleBaseUrls": {
const firstEnvironment = environments.environments[0];
if (firstEnvironment == null) {
this.errors.add({
severity: Severity.Critical,
message: "Multiple environments are not supported; use the baseUrl option instead"
});
return false;
}
const expectedKeys = new Set(keys(firstEnvironment.urls));
for (const key of keys(multiEnvironmentUrlValues)) {
if (expectedKeys.has(key)) {
expectedKeys.delete(key);
}
}
if (expectedKeys.size > 0) {
this.errors.add({
severity: Severity.Critical,
message: `The provided environments are invalid; got: [${Object.keys(multiEnvironmentUrlValues).join(", ")}], expected: [${keys(firstEnvironment.urls).join(", ")}]`
});
return false;
}
return true;
}
}
}

public newAuthMismatchError({
auth,
values
Expand Down
11 changes: 8 additions & 3 deletions generators/typescript-v2/ast/src/ast/Reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,31 @@ export declare namespace Reference {
interface Args {
/* The name of the reference */
name: string;
/* The member name within the imported reference, if any (e.g. 'Address' in 'User.Address') */
memberName?: string;
/* The module it's from, if it's imported */
importFrom?: ModuleImport;
}
}

export class Reference extends AstNode {
public readonly name: string;
public readonly importFrom?: Reference.ModuleImport;
public readonly importFrom: Reference.ModuleImport | undefined;
public readonly memberName: string | undefined;

constructor({ name, importFrom }: Reference.Args) {
constructor({ name, importFrom, memberName }: Reference.Args) {
super();
this.name = name;
this.importFrom = importFrom;
this.memberName = memberName;
}

public write(writer: Writer): void {
if (this.importFrom != null) {
writer.addImport(this);
}
const prefix = this.importFrom?.type === "star" ? `${this.importFrom.starImportAlias}.` : "";
writer.write(`${prefix}${this.name}`);
const suffix = this.memberName != null ? `.${this.memberName}` : "";
writer.write(`${prefix}${this.name}${suffix}`);
}
}
113 changes: 108 additions & 5 deletions generators/typescript-v2/ast/src/ast/TypeLiteral.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ import { assertNever } from "@fern-api/core-utils";

import { AstNode, Writer } from "./core";

type InternalTypeLiteral = Array_ | Boolean_ | BigInt_ | Number_ | Object_ | String_ | Tuple | Nop;
type InternalTypeLiteral =
| Array_
| Boolean_
| BigInt_
| Number_
| Object_
| Reference
| String_
| Tuple
| Unkonwn_
| Nop;

interface Array_ {
type: "array";
Expand Down Expand Up @@ -34,6 +44,11 @@ export interface ObjectField {
value: TypeLiteral;
}

interface Reference {
type: "reference";
value: AstNode;
}

interface String_ {
type: "string";
value: string;
Expand All @@ -44,6 +59,11 @@ interface Tuple {
values: TypeLiteral[];
}

interface Unkonwn_ {
type: "unknown";
value: unknown;
}

interface Nop {
type: "nop";
}
Expand All @@ -64,18 +84,21 @@ export class TypeLiteral extends AstNode {
break;
}
case "number": {
// N.B. Defaults to decimal; further work needed to support alternatives like hex, binary, octal, etc.
writer.write(this.internalType.value.toString());
break;
}
case "bigint": {
writer.write(this.internalType.value.toString());
writer.write(`BigInt(${this.internalType.value.toString()})`);
break;
}
case "object": {
this.writeObject({ writer, object: this.internalType });
break;
}
case "reference": {
writer.writeNode(this.internalType.value);
break;
}
case "string": {
if (this.internalType.value.includes("\n")) {
this.writeStringWithBackticks({ writer, value: this.internalType.value });
Expand All @@ -88,6 +111,10 @@ export class TypeLiteral extends AstNode {
this.writeIterable({ writer, iterable: this.internalType });
break;
}
case "unknown": {
this.writeUnknown({ writer, value: this.internalType.value });
break;
}
case "nop":
break;
default: {
Expand Down Expand Up @@ -149,6 +176,10 @@ export class TypeLiteral extends AstNode {
});
}

public static bigint(value: bigint): TypeLiteral {
return new this({ type: "bigint", value });
}

public static boolean(value: boolean): TypeLiteral {
return new this({ type: "boolean", value });
}
Expand All @@ -164,6 +195,13 @@ export class TypeLiteral extends AstNode {
});
}

public static reference(value: AstNode): TypeLiteral {
return new this({
type: "reference",
value
});
}

public static string(value: string): TypeLiteral {
return new this({
type: "string",
Expand All @@ -178,8 +216,8 @@ export class TypeLiteral extends AstNode {
});
}

public static bigint(value: bigint): TypeLiteral {
return new this({ type: "bigint", value });
public static unknown(value: unknown): TypeLiteral {
return new this({ type: "unknown", value });
}

public static nop(): TypeLiteral {
Expand All @@ -189,6 +227,71 @@ export class TypeLiteral extends AstNode {
public static isNop(typeLiteral: TypeLiteral): boolean {
return typeLiteral.internalType.type === "nop";
}

private writeUnknown({ writer, value }: { writer: Writer; value: unknown }): void {
switch (typeof value) {
case "boolean":
writer.write(value.toString());
return;
case "string":
writer.write(value.includes('"') ? `\`${value}\`` : `"${value}"`);
return;
case "number":
writer.write(value.toString());
return;
case "object":
if (value == null) {
writer.write("null");
return;
}
if (Array.isArray(value)) {
this.writeUnknownArray({ writer, value });
return;
}
this.writeUnknownObject({ writer, value });
return;
default:
throw new Error(`Internal error; unsupported unknown type: ${typeof value}`);
}
}

private writeUnknownArray({
writer,
value
}: {
writer: Writer;
value: any[]; // eslint-disable-line @typescript-eslint/no-explicit-any
}): void {
if (value.length === 0) {
writer.write("[]");
return;
}
writer.writeLine("[");
writer.indent();
for (const element of value) {
writer.writeNode(TypeLiteral.unknown(element));
writer.writeLine(",");
}
writer.dedent();
writer.write("]");
}

private writeUnknownObject({ writer, value }: { writer: Writer; value: object }): void {
const entries = Object.entries(value);
if (entries.length === 0) {
writer.write("{}");
return;
}
writer.writeLine("{");
writer.indent();
for (const [key, val] of entries) {
writer.write(`${key}: `);
writer.writeNode(TypeLiteral.unknown(val));
writer.writeLine(",");
}
writer.dedent();
writer.write("}");
}
}

function filterNopObjectFields({ fields }: { fields: ObjectField[] }): ObjectField[] {
Expand Down
1 change: 1 addition & 0 deletions generators/typescript-v2/dynamic-snippets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@fern-api/browser-compatible-base-generator": "workspace:*",
"@fern-api/core-utils": "workspace:*",
"@fern-api/dynamic-ir-sdk": "^53.23.0",
"@fern-api/path-utils": "workspace:*",
"@fern-api/typescript-ast": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
AbstractDynamicSnippetsGenerator,
AbstractFormatter,
FernGeneratorExec,
Result
} from "@fern-api/browser-compatible-base-generator";
Expand Down
Loading

0 comments on commit 65113d2

Please sign in to comment.