-
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.
fix: async snippets in api playground (#1233)
- Loading branch information
1 parent
5b11a42
commit f61b9ca
Showing
21 changed files
with
660 additions
and
426 deletions.
There are no files selected for viewing
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
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
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
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
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
54 changes: 54 additions & 0 deletions
54
...ui/app/src/api-playground/code-snippets/__test__/__snapshots__/code-snippets.test.ts.snap
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,54 @@ | ||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html | ||
|
||
exports[`PlaygroundCodeSnippetBuilder > should render curl 1`] = ` | ||
"curl -X POST https://example.com/test/hello%40example \\ | ||
-H "Accept: application/json" \\ | ||
-H "Test: test" \\ | ||
-d '{ | ||
"test": "hello", | ||
"deeply": { | ||
"nested": 1 | ||
} | ||
}'" | ||
`; | ||
|
||
exports[`PlaygroundCodeSnippetBuilder > should render python 1`] = ` | ||
"import requests | ||
# My endpoint (POST /test/:test) | ||
response = requests.post( | ||
"https://example.com/test/hello%40example", | ||
headers={ | ||
"Accept": "application/json", | ||
"Test": "test" | ||
}, | ||
json={ | ||
"test": "hello", | ||
"deeply": { | ||
"nested": 1 | ||
} | ||
}, | ||
) | ||
print(response.json())" | ||
`; | ||
|
||
exports[`PlaygroundCodeSnippetBuilder > should render typescript 1`] = ` | ||
"// My endpoint (POST /test/:test) | ||
const response = await fetch("https://example.com/test/hello%40example", { | ||
method: "POST", | ||
headers: { | ||
"Accept": "application/json", | ||
"Test": "test" | ||
}, | ||
body: JSON.stringify({ | ||
"test": "hello", | ||
"deeply": { | ||
"nested": 1 | ||
} | ||
}), | ||
}); | ||
const body = await response.json(); | ||
console.log(body);" | ||
`; |
96 changes: 96 additions & 0 deletions
96
packages/ui/app/src/api-playground/code-snippets/__test__/code-snippets.test.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,96 @@ | ||
import { FernNavigation } from "@fern-api/fdr-sdk"; | ||
import { ResolvedEndpointDefinition } from "../../../resolver/types"; | ||
import { PlaygroundEndpointRequestFormState } from "../../types"; | ||
import { CurlSnippetBuilder } from "../builders/curl"; | ||
import { PythonRequestSnippetBuilder } from "../builders/python"; | ||
import { TypescriptFetchSnippetBuilder } from "../builders/typescript"; | ||
|
||
describe("PlaygroundCodeSnippetBuilder", () => { | ||
const endpoint: ResolvedEndpointDefinition = { | ||
type: "endpoint", | ||
nodeId: FernNavigation.NodeId(""), | ||
id: "", | ||
apiDefinitionId: "", | ||
slug: FernNavigation.Slug(""), | ||
auth: undefined, | ||
availability: undefined, | ||
defaultEnvironment: { | ||
id: "Prod", | ||
baseUrl: "https://example.com", | ||
}, | ||
environments: [], | ||
method: "POST", | ||
title: "My endpoint", | ||
path: [ | ||
{ type: "literal", value: "/test/" }, | ||
{ | ||
key: "test", | ||
type: "pathParameter", | ||
valueShape: { | ||
type: "primitive", | ||
value: { type: "string" }, | ||
description: undefined, | ||
availability: undefined, | ||
}, | ||
hidden: false, | ||
description: undefined, | ||
availability: undefined, | ||
}, | ||
], | ||
pathParameters: [ | ||
{ | ||
key: "test", | ||
valueShape: { | ||
type: "primitive", | ||
value: { type: "string" }, | ||
description: undefined, | ||
availability: undefined, | ||
}, | ||
hidden: false, | ||
description: undefined, | ||
availability: undefined, | ||
}, | ||
], | ||
queryParameters: [], | ||
headers: [], | ||
requestBody: undefined, | ||
responseBody: undefined, | ||
errors: [], | ||
examples: [], | ||
snippetTemplates: undefined, | ||
stream: undefined, | ||
description: undefined, | ||
}; | ||
const formState: PlaygroundEndpointRequestFormState = { | ||
type: "endpoint", | ||
headers: { | ||
Accept: "application/json", | ||
Test: "test", | ||
}, | ||
pathParameters: { | ||
test: "hello@example", | ||
}, | ||
queryParameters: {}, | ||
body: { | ||
type: "json", | ||
value: { | ||
test: "hello", | ||
deeply: { | ||
nested: 1, | ||
}, | ||
}, | ||
}, | ||
}; | ||
|
||
it("should render curl", () => { | ||
expect(new CurlSnippetBuilder(endpoint, formState).build()).toMatchSnapshot(); | ||
}); | ||
|
||
it("should render python", () => { | ||
expect(new PythonRequestSnippetBuilder(endpoint, formState).build()).toMatchSnapshot(); | ||
}); | ||
|
||
it("should render typescript", () => { | ||
expect(new TypescriptFetchSnippetBuilder(endpoint, formState).build()).toMatchSnapshot(); | ||
}); | ||
}); |
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.formState.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, | ||
}); | ||
} | ||
} |
Oops, something went wrong.