Skip to content

Commit

Permalink
hack: add fileforge content-type everywhere (#847)
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity authored May 10, 2024
1 parent cc4719e commit e16f726
Show file tree
Hide file tree
Showing 13 changed files with 81 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,18 @@ function unsafeStringifyHttpRequestExampleToCurl({

return ` \\\n -F ${key}='${stringValue.replace(/'/g, "\\'")}${contentType != null ? `;type=${contentType}` : ""}'`;
},
file: ({ fileName }) => {
file: ({ fileName, contentType }) => {
if (fileName == null) {
return "";
}
return ` \\\n -F ${key}=@${fileName.includes(" ") ? `"${fileName}"` : fileName}`;
return ` \\\n -F ${key}=@${fileName.includes(" ") || contentType != null ? `"${fileName}${contentType != null ? `;type=${contentType}` : ""}"` : fileName}`;
},
fileArray: ({ files }) =>
files
.filter((file) => file.fileName != null)
.map(
({ fileName }) =>
` \\\n -F "${key}[]"=@${fileName.includes(" ") ? `"${fileName}"` : fileName}`,
({ fileName, contentType }) =>
` \\\n -F "${key}[]"=@${fileName.includes(" ") || contentType != null ? `"${fileName}${contentType != null ? `;type=${contentType}` : ""}"` : fileName}`,
)
.join(""),
_other: () => "",
Expand Down
16 changes: 6 additions & 10 deletions packages/ui/app/src/api-playground/PlaygroundEndpoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { assertNever, isNonNullish } from "@fern-ui/core-utils";
import { joinUrlSlugs } from "@fern-ui/fdr-utils";
import { Loadable, failed, loaded, loading, notStartedLoading } from "@fern-ui/loadable";
import { PaperPlaneIcon } from "@radix-ui/react-icons";
import { compact } from "lodash-es";
import { Dispatch, FC, ReactElement, SetStateAction, useCallback, useState } from "react";
import { resolve } from "url";
import { capturePosthogEvent } from "../analytics/posthog";
Expand Down Expand Up @@ -325,24 +326,19 @@ async function serializeFormStateBody(
| undefined;
formDataValue[key] = {
...value,
// this is a hack to allow the API Playground to send JSON blobs in form data
// revert this once we have a better solution
contentType:
typeof property?.contentType === "string"
? property.contentType
: Array.isArray(property?.contentType)
? property.contentType.find((value) => value.includes("json")) ??
property.contentType[0]
: undefined,
compact(property?.contentType)[0] ??
(domain.includes("fileforge") ? "application/json" : undefined),
};
break;
}
default:
assertNever(value);
}
}
// this is a hack to allow the API Playground to send JSON blobs in form data
// revert this once we have a better solution
const isJsonBlob = domain.includes("fileforge");
return { type: "form-data", value: formDataValue, isJsonBlob };
return { type: "form-data", value: formDataValue };
}
case "octet-stream":
return { type: "octet-stream", value: await serializeFile(body.value, basePath) };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export const PlaygroundEndpointContent: FC<PlaygroundEndpointContentProps> = ({
endpoint,
formState,
redacted: false,
domain,
})
: requestType === "javascript"
? stringifyFetch({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FC, useMemo } from "react";
import { useFeatureFlags } from "../contexts/FeatureFlagContext";
import { useDocsContext } from "../contexts/docs-context/useDocsContext";
import { ResolvedEndpointDefinition } from "../resolver/types";
import { FernSyntaxHighlighter } from "../syntax-highlighting/FernSyntaxHighlighter";
import { PlaygroundEndpointRequestFormState } from "./types";
Expand All @@ -13,13 +14,15 @@ interface PlaygroundRequestPreviewProps {

export const PlaygroundRequestPreview: FC<PlaygroundRequestPreviewProps> = ({ endpoint, formState, requestType }) => {
const { isSnippetTemplatesEnabled } = useFeatureFlags();
const { domain } = useDocsContext();
const code = useMemo(
() =>
requestType === "curl"
? stringifyCurl({
endpoint,
formState,
redacted: true,
domain,
})
: requestType === "javascript"
? stringifyFetch({
Expand All @@ -36,7 +39,7 @@ export const PlaygroundRequestPreview: FC<PlaygroundRequestPreviewProps> = ({ en
isSnippetTemplatesEnabled,
})
: "",
[endpoint, formState, isSnippetTemplatesEnabled, requestType],
[domain, endpoint, formState, isSnippetTemplatesEnabled, requestType],
);
return (
<FernSyntaxHighlighter
Expand Down
22 changes: 17 additions & 5 deletions packages/ui/app/src/api-playground/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { assertNever } from "@fern-ui/core-utils";
import { ResolvedFormValue } from "../../resolver/types";
import { compact } from "lodash-es";
import { ResolvedFormDataRequestProperty, ResolvedFormValue } from "../../resolver/types";
import { PlaygroundRequestFormAuth } from "./auth";
import { PlaygroundFormDataEntryValue } from "./formDataEntryValue";
import { JsonVariant } from "./jsonVariant";
Expand All @@ -20,6 +21,8 @@ export declare namespace PlaygroundFormStateBody {

export function convertPlaygroundFormDataEntryValueToResolvedExampleEndpointRequest(
value: PlaygroundFormDataEntryValue,
property: ResolvedFormDataRequestProperty | undefined,
domain: string,
): ResolvedFormValue | undefined {
switch (value.type) {
case "file":
Expand All @@ -30,19 +33,28 @@ export function convertPlaygroundFormDataEntryValueToResolvedExampleEndpointRequ
type: "file",
fileName: value.value?.name,
fileId: undefined,
contentType: value.value.type,
};
case "fileArray":
return {
type: "fileArray",
files: value.value.map((file) => ({ type: "file", fileName: file.name, fileId: undefined })),
files: value.value.map((file) => ({
type: "file",
fileName: file.name,
fileId: undefined,
contentType: file.type,
})),
};
case "json":
case "json": {
const contentType = property?.type === "bodyProperty" ? compact(property.contentType)[0] : undefined;
return {
type: "json",
value: value.value,
// TODO: bring in content type from the shape of the form data
contentType: undefined,
// this is a hack to allow the API Playground to send JSON blobs in form data
// revert this once we have a better solution
contentType: contentType ?? (domain.includes("fileforge") ? "application/json" : undefined),
};
}
default:
assertNever(value);
}
Expand Down
1 change: 0 additions & 1 deletion packages/ui/app/src/api-playground/types/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export declare namespace ProxyRequest {
export interface SerializableFormData {
type: "form-data";
value: Record<string, SerializableFormDataEntryValue>;
isJsonBlob?: boolean; // this is a hack to allow the API Playground to send JSON blobs in form data
}

export interface SerializableOctetStream {
Expand Down
13 changes: 12 additions & 1 deletion packages/ui/app/src/api-playground/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,10 +476,12 @@ export function stringifyCurl({
endpoint,
formState,
redacted,
domain,
}: {
endpoint: ResolvedEndpointDefinition | undefined;
formState: PlaygroundEndpointRequestFormState;
redacted: boolean;
domain: string;
}): string {
if (endpoint == null) {
return "";
Expand All @@ -497,9 +499,18 @@ export function stringifyCurl({
: visitDiscriminatedUnion(formState.body, "type")._visit<ResolvedExampleEndpointRequest | undefined>({
json: ({ value }) => ({ type: "json", value }),
"form-data": ({ value }): ResolvedExampleEndpointRequest.Form | undefined => {
const properties =
endpoint.requestBody?.shape.type === "formData"
? endpoint.requestBody.shape.properties
: [];
const newValue: Record<string, ResolvedFormValue> = {};
for (const [key, v] of Object.entries(value)) {
const convertedV = convertPlaygroundFormDataEntryValueToResolvedExampleEndpointRequest(v);
const property = properties.find((property) => property.key === key);
const convertedV = convertPlaygroundFormDataEntryValueToResolvedExampleEndpointRequest(
v,
property,
domain,
);
if (convertedV != null) {
newValue[key] = convertedV;
}
Expand Down
44 changes: 32 additions & 12 deletions packages/ui/app/src/resolver/ApiDefinitionResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
FlattenedWebSocketChannel,
FlattenedWebhookDefinition,
} from "@fern-ui/fdr-utils";
import { mapValues } from "lodash-es";
import { compact, mapValues } from "lodash-es";
import { captureSentryError } from "../analytics/sentry";
import { sortKeysByShape } from "../api-page/examples/sortKeysByShape";
import { FeatureFlags } from "../contexts/FeatureFlagContext";
Expand Down Expand Up @@ -53,8 +53,9 @@ export class ApiDefinitionResolver {
pages: Record<string, DocsV1Read.PageContent>,
mdxOptions: FernSerializeMdxOptions | undefined,
featureFlags: FeatureFlags,
domain: string,
): Promise<ResolvedRootPackage> {
const resolver = new ApiDefinitionResolver(apiDefinition, pages, featureFlags);
const resolver = new ApiDefinitionResolver(apiDefinition, pages, featureFlags, domain);
return resolver.resolveApiDefinition(title, mdxOptions);
}

Expand All @@ -65,6 +66,7 @@ export class ApiDefinitionResolver {
private apiDefinition: FlattenedApiDefinition,
private pages: Record<string, DocsV1Read.PageContent>,
private featureFlags: FeatureFlags,
private domain: string,
// filteredTypes?: string[],
) {
this.apiDefinition = apiDefinition;
Expand Down Expand Up @@ -814,24 +816,42 @@ export class ApiDefinitionResolver {
shape?.type === "formData"
? shape.properties.find((p) => p.key === key && p.type === "bodyProperty")
: undefined;
// this is a hack to allow the API Playground to send JSON blobs in form data
// revert this once we have a better solution
const contentType =
typeof property?.contentType === "string"
? property.contentType
: Array.isArray(property?.contentType)
? property.contentType.find((ct) => ct.includes("json")) ??
property.contentType[0]
: undefined;
compact(property?.contentType)[0] ??
(this.domain.includes("fileforge") ? "application/json" : undefined);
return { type: "json" as const, value: value.value, contentType };
},
filename: (value) => ({ type: "file", fileName: value.value, fileId: undefined }),
filename: (value) => ({
type: "file",
fileName: value.value,
fileId: undefined,
contentType: undefined,
}),
filenames: (value) => ({
type: "fileArray",
files: value.value.map((v) => ({ type: "file", fileName: v, fileId: undefined })),
files: value.value.map((v) => ({
type: "file",
fileName: v,
fileId: undefined,
contentType: undefined,
})),
}),
filenameWithData: (value) => ({
type: "file",
fileName: value.filename,
fileId: value.data,
contentType: undefined,
}),
filenameWithData: (value) => ({ type: "file", fileName: value.filename, fileId: value.data }),
filenamesWithData: (value) => ({
type: "fileArray",
files: value.value.map((v) => ({ type: "file", fileName: v.filename, fileId: v.data })),
files: value.value.map((v) => ({
type: "file",
fileName: v.filename,
fileId: v.data,
contentType: undefined,
})),
}),
_other: () => ({ type: "json", value: undefined, contentType: undefined }),
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe("resolveApiDefinition", () => {
{},
undefined,
DEFAULT_FEATURE_FLAGS,
"fern.docs.buildwithfern.com",
);
expect(resolved).toMatchSnapshot();
});
Expand Down
1 change: 1 addition & 0 deletions packages/ui/app/src/resolver/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ export declare namespace ResolvedFormValue {
type: "file";
fileName: string;
fileId: string | undefined; // lookup file by UUID
contentType: string | undefined;
}

interface MultipleFiles {
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/app/src/util/convertNavigatableToResolvedPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export async function convertNavigatableToResolvedPath({
pages,
mdxOptions,
featureFlags,
domain,
);
return {
type: "api-page",
Expand Down Expand Up @@ -181,6 +182,7 @@ export async function convertNavigatableToResolvedPath({
pages,
mdxOptions,
featureFlags,
domain,
),
];
}),
Expand Down
5 changes: 0 additions & 5 deletions packages/ui/docs-bundle/src/pages/api/fern-docs/proxy/rest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@ export async function buildRequestBody(body: ProxyRequest.SerializableBody | und
{ type: value.contentType },
),
] as const;
} else if (body.isJsonBlob) {
return [
key,
new Blob([JSON.stringify(value.value)], { type: "application/json" }),
] as const;
}
return [key, JSON.stringify(value.value)] as const;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ const resolveApiHandler: NextApiHandler = async (req, res: NextApiResponse<Resol
pages,
undefined,
featureFlags,
docs.body.baseUrl.domain,
),
);
} catch (err) {
Expand Down

0 comments on commit e16f726

Please sign in to comment.