Skip to content

Commit

Permalink
refactor python, typescript
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity committed Aug 1, 2024
1 parent 45fe86c commit 6f07f58
Show file tree
Hide file tree
Showing 10 changed files with 306 additions and 288 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ResolvedEndpointPathParts } from "../../../resolver/types";
import { unknownToString } from "../../utils";

export function buildPath(path: ResolvedEndpointPathParts[], pathParameters?: Record<string, unknown>): string {
return path
.map((part) => {
if (part.type === "pathParameter") {
const stateValue = unknownToString(pathParameters?.[part.key]);
return stateValue.length > 0 ? encodeURIComponent(stateValue) : ":" + part.key;
}
return part.value;
})
.join("");
}

export function indentAfter(str: string, indent: number, afterLine?: number): string {
return str
.split("\n")
.map((line, idx) => {
if (afterLine == null || idx > afterLine) {
return " ".repeat(indent) + line;
}
return line;
})
.join("\n");
}
59 changes: 59 additions & 0 deletions packages/ui/app/src/api-playground/code-snippets/builders/curl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { visitDiscriminatedUnion } from "@fern-ui/core-utils";
import { isEmpty } from "lodash-es";
import { stringifyHttpRequestExampleToCurl } from "../../../api-page/examples/stringifyHttpRequestExampleToCurl";
import { ResolvedExampleEndpointRequest, ResolvedFormValue } from "../../../resolver/types";
import { convertPlaygroundFormDataEntryValueToResolvedExampleEndpointRequest } from "../../types";
import { PlaygroundCodeSnippetBuilder } from "./types";

export class CurlSnippetBuilder extends PlaygroundCodeSnippetBuilder {
private isFileForgeHackEnabled: boolean = false;

public setFileForgeHackEnabled(isFileForgeHackEnabled: boolean): CurlSnippetBuilder {
this.isFileForgeHackEnabled = isFileForgeHackEnabled;
return this;
}

public override build(): string {
return stringifyHttpRequestExampleToCurl({
method: this.endpoint.method,
url: this.url,
urlQueries: this.formState.queryParameters,
headers: this.headers,
body: this.#convertFormStateToBody(),
});
}

#convertFormStateToBody(): ResolvedExampleEndpointRequest | undefined {
if (this.formState.body == null) {
return undefined;
}
return visitDiscriminatedUnion(this.formState.body, "type")._visit<ResolvedExampleEndpointRequest | undefined>({
json: ({ value }) => ({ type: "json", value }),
"form-data": ({ value }): ResolvedExampleEndpointRequest.Form | undefined => {
const properties =
this.endpoint.requestBody?.shape.type === "formData"
? this.endpoint.requestBody.shape.properties
: [];
const newValue: Record<string, ResolvedFormValue> = {};
for (const [key, v] of Object.entries(value)) {
const property = properties.find((property) => property.key === key);
const convertedV = convertPlaygroundFormDataEntryValueToResolvedExampleEndpointRequest(
v,
property,
this.isFileForgeHackEnabled,
);
if (convertedV != null) {
newValue[key] = convertedV;
}
}
if (isEmpty(newValue)) {
return undefined;
}
return { type: "form", value: newValue };
},
"octet-stream": ({ value }): ResolvedExampleEndpointRequest.Bytes | undefined =>
value != null ? { type: "bytes", fileName: value.name, value: undefined } : undefined,
_other: () => undefined,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { isNonNullish, visitDiscriminatedUnion } from "@fern-ui/core-utils";
import { PlaygroundFormDataEntryValue } from "../../types";
import { buildPath, indentAfter } from "./common";
import { PlaygroundCodeSnippetBuilder } from "./types";

interface PythonRequestParams {
json?: string;
data?: string;
files?: string;
}

export class PythonRequestSnippetBuilder extends PlaygroundCodeSnippetBuilder {
#buildRequests({ json, data, files }: PythonRequestParams) {
if (this.endpoint == null) {
return "";
}
return `# ${this.endpoint.title} (${this.endpoint.method} ${buildPath(this.endpoint.path)})
response = requests.${this.endpoint.method.toLowerCase()}(
"${this.url}",
headers=${indentAfter(JSON.stringify(this.headers, undefined, 2), 2, 0)},${json != null ? `\n json=${indentAfter(json, 2, 0)},` : ""}${
data != null ? `\n data=${indentAfter(data, 2, 0)},` : ""
}${files != null ? `\n files=${indentAfter(files, 2, 0)},` : ""}
)
print(response.json())`;
}

public override build(): string {
const imports = ["requests"];

if (this.formState.body == null) {
return `${imports.map((pkg) => `import ${pkg}`).join("\n")}
${this.#buildRequests({})}`;
}

return visitDiscriminatedUnion(this.formState.body, "type")._visit<string>({
json: ({ value }) => `${imports.map((pkg) => `import ${pkg}`).join("\n")}
${this.#buildRequests({ json: JSON.stringify(value, undefined, 2) })}`,
"form-data": ({ value }) => {
const singleFiles = Object.entries(value)
.filter((entry): entry is [string, PlaygroundFormDataEntryValue.SingleFile] =>
PlaygroundFormDataEntryValue.isSingleFile(entry[1]),
)
.map(([k, v]) => {
if (v.value == null) {
return undefined;
}
return `'${k}': ('${v.value.name}', open('${v.value.name}', 'rb')),`;
})
.filter(isNonNullish);
const fileArrays = Object.entries(value)
.filter((entry): entry is [string, PlaygroundFormDataEntryValue.MultipleFiles] =>
PlaygroundFormDataEntryValue.isMultipleFiles(entry[1]),
)
.map(([k, v]) => {
const fileStrings = v.value.map((file) => `('${file.name}', open('${file.name}', 'rb'))`);
if (fileStrings.length === 0) {
return;
}
return `'${k}': [${fileStrings.length === 0 ? fileStrings[0] : indentAfter(`\n${fileStrings.join(",\n")},\n`, 2, 0)}],`;
})
.filter(isNonNullish);

const fileEntries = [...singleFiles, ...fileArrays].join("\n");
const files = fileEntries.length > 0 ? `{\n${indentAfter(fileEntries, 2)}\n}` : undefined;

const dataEntries = Object.entries(value)
.filter((entry): entry is [string, PlaygroundFormDataEntryValue.Json] =>
PlaygroundFormDataEntryValue.isJson(entry[1]),
)
.map(([k, v]) =>
v.value == null
? undefined
: `'${k}': json.dumps(${indentAfter(JSON.stringify(v.value, undefined, 2), 2, 0)}),`,
)
.filter(isNonNullish)
.join("\n");

const data = dataEntries.length > 0 ? `{\n${indentAfter(dataEntries, 2)}\n}` : undefined;

if (data != null) {
imports.push("json");
}

return `${imports.map((pkg) => `import ${pkg}`).join("\n")}
${this.#buildRequests({ data, files })}`;
},
"octet-stream": (f) => `${imports.map((pkg) => `import ${pkg}`).join("\n")}
${this.#buildRequests({ data: f.value != null ? `open('${f.value?.name}', 'rb').read()` : undefined })}`,
_other: () => `${imports.map((pkg) => `import ${pkg}`).join("\n")}
${this.#buildRequests({})}`,
});
}
}
17 changes: 17 additions & 0 deletions packages/ui/app/src/api-playground/code-snippets/builders/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ResolvedEndpointDefinition } from "../../../resolver/types";
import { PlaygroundEndpointRequestFormState } from "../../types";
import { buildEndpointUrl } from "../../utils";

export abstract class PlaygroundCodeSnippetBuilder {
protected url: string;
constructor(
protected endpoint: ResolvedEndpointDefinition,
protected headers: Record<string, unknown>,
protected formState: PlaygroundEndpointRequestFormState,
) {
// TODO: wire through the environment from hook
this.url = buildEndpointUrl(endpoint, formState);
}

public abstract build(): string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { visitDiscriminatedUnion } from "@fern-ui/core-utils";
import { isEmpty } from "lodash-es";
import { buildPath, indentAfter } from "./common";
import { PlaygroundCodeSnippetBuilder } from "./types";

export class TypescriptFetchSnippetBuilder extends PlaygroundCodeSnippetBuilder {
#buildFetch(body: string | undefined): string {
if (this.endpoint == null) {
return "";
}
return `// ${this.endpoint.title} (${this.endpoint.method} ${buildPath(this.endpoint.path)})
const response = await fetch("${this.url}", {
method: "${this.endpoint.method}",
headers: ${indentAfter(JSON.stringify(this.headers, undefined, 2), 2, 0)},${!isEmpty(body) ? `\n body: ${body},` : ""}
});
const body = await response.json();
console.log(body);`;
}

public override build(): string {
if (this.formState.body == null) {
return this.#buildFetch(undefined);
}

return visitDiscriminatedUnion(this.formState.body, "type")._visit<string>({
"octet-stream": () => this.#buildFetch('document.querySelector("input[type=file]").files[0]'), // TODO: implement this
json: ({ value }) =>
this.#buildFetch(
value != null
? indentAfter(`JSON.stringify(${JSON.stringify(value, undefined, 2)})`, 2, 0)
: undefined,
),
"form-data": ({ value }) => {
const file = Object.entries(value)
.filter(([, v]) => v.type === "file")
.map(([k]) => {
return `const ${k}File = document.getElementById("${k}").files[0];
formData.append("${k}", ${k}File);`;
})
.join("\n\n");

const fileArrays = Object.entries(value)
.filter(([, v]) => v.type === "fileArray")
.map(([k]) => {
return `const ${k}Files = document.getElementById("${k}").files;
${k}Files.forEach((file) => {
formData.append("${k}", file);
});`;
})
.join("\n\n");

const jsons = Object.entries(value)
.filter(([, v]) => v.type === "json")
.map(([k, v]) => {
return `formData.append("${k}", ${indentAfter(`JSON.stringify(${JSON.stringify(v.value, undefined, 2)})`, 2, 0)});`;
})
.join("\n\n");

const appendStatements = [file, fileArrays, jsons].filter((v) => v.length > 0).join("\n\n");

return `// Create a new FormData instance
const formData = new FormData();${appendStatements.length > 0 ? "\n\n" + appendStatements : ""}
${this.#buildFetch("formData")}`;
},
_other: () => this.#buildFetch(undefined),
});
}
}
Loading

0 comments on commit 6f07f58

Please sign in to comment.