-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
45fe86c
commit 6f07f58
Showing
10 changed files
with
306 additions
and
288 deletions.
There are no files selected for viewing
35 changes: 0 additions & 35 deletions
35
packages/ui/app/src/api-playground/code-snippets/apiDefinitionCache.ts
This file was deleted.
Oops, something went wrong.
26 changes: 26 additions & 0 deletions
26
packages/ui/app/src/api-playground/code-snippets/builders/common.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
59
packages/ui/app/src/api-playground/code-snippets/builders/curl.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}); | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
packages/ui/app/src/api-playground/code-snippets/builders/python.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
17
packages/ui/app/src/api-playground/code-snippets/builders/types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
70 changes: 70 additions & 0 deletions
70
packages/ui/app/src/api-playground/code-snippets/builders/typescript.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
}); | ||
} | ||
} |
Oops, something went wrong.