Skip to content

Commit

Permalink
fix: api reference switching issue (#1530)
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity committed Sep 23, 2024
1 parent d87272f commit aa0d005
Show file tree
Hide file tree
Showing 30 changed files with 850 additions and 809 deletions.
38 changes: 38 additions & 0 deletions packages/commons/core-utils/src/__test__/unknownToString.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { unknownToString } from "../unknownToString";

describe("unknownToString", () => {
it("should preserve strings", () => {
expect(unknownToString("foo")).toBe("foo");
});

it("should convert booleans to strings", () => {
expect(unknownToString(true)).toBe("true");
expect(unknownToString(false)).toBe("false");
});

it("should convert numbers to strings", () => {
expect(unknownToString(42)).toBe("42");
expect(unknownToString(3.14)).toBe("3.14");
expect(unknownToString(1_000_000_000_000_000_000)).toBe("1000000000000000000");
});

it("should convert nulls", () => {
expect(unknownToString(null)).toBe("");
expect(unknownToString(undefined)).toBe("");
expect(unknownToString(null, { renderNull: true })).toBe("null");
expect(unknownToString(undefined, { renderNull: true })).toBe("null");
});

it("should convert objects to JSON strings", () => {
expect(unknownToString({ foo: "bar" })).toBe('{"foo":"bar"}');
});

it("should convert arrays to JSON strings", () => {
expect(unknownToString([1, 2, 3])).toBe("[1,2,3]");
});

it("should not render functions", () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
expect(unknownToString(() => {})).toBe("");
});
});
1 change: 1 addition & 0 deletions packages/commons/core-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@ export { values, type Values } from "./objects/values";
export { PLATFORM, type Platform } from "./platform";
export { titleCase } from "./titleCase";
export type { Digit, Letter, LowercaseLetter, UppercaseLetter } from "./types";
export { unknownToString } from "./unknownToString";
export { visitDiscriminatedUnion } from "./visitDiscriminatedUnion";
export { withDefaultProtocol } from "./withDefaultProtocol";
12 changes: 12 additions & 0 deletions packages/commons/core-utils/src/unknownToString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
interface Opts {
renderNull?: boolean;
}

export function unknownToString(value: unknown, { renderNull = false }: Opts = {}): string {
if (value == null || typeof value === "function") {
return renderNull ? "null" : "";
} else if (typeof value === "string") {
return value;
}
return JSON.stringify(value);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import jq from "jsonpath";
import { getJsonLineNumbers } from "../useHighlightJsonLines";

const MOCK_JSON = {
Expand Down Expand Up @@ -43,12 +42,12 @@ const MOCK_JSON = {

describe("useHighlightJsonLines", () => {
it("should return all range of all lines if path is empty", () => {
expect(getJsonLineNumbers(jq, MOCK_JSON, [])).toEqual([[0, 27]]);
expect(getJsonLineNumbers(MOCK_JSON, [])).toEqual([[0, 27]]);
});

it("should return nothing with invalid selector", () => {
expect(
getJsonLineNumbers(jq, MOCK_JSON, [
getJsonLineNumbers(MOCK_JSON, [
{ type: "objectProperty", propertyName: "data" },
{ type: "listItem" },
{ type: "objectProperty", propertyName: "d" },
Expand All @@ -58,7 +57,7 @@ describe("useHighlightJsonLines", () => {

it("should return line numbers with valid selector", () => {
expect(
getJsonLineNumbers(jq, MOCK_JSON, [
getJsonLineNumbers(MOCK_JSON, [
{ type: "objectProperty", propertyName: "data" },
{ type: "listItem" },
{ type: "objectProperty", propertyName: "a" },
Expand All @@ -69,7 +68,7 @@ describe("useHighlightJsonLines", () => {

it("should return line numbers with valid selector and filter", () => {
expect(
getJsonLineNumbers(jq, MOCK_JSON, [
getJsonLineNumbers(MOCK_JSON, [
{ type: "objectProperty", propertyName: "data" },
{ type: "listItem" },
{ type: "objectProperty", propertyName: "a" },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { visitDiscriminatedUnion } from "@fern-ui/core-utils";
import { unknownToString, visitDiscriminatedUnion } from "@fern-ui/core-utils";
import { captureSentryError } from "../../analytics/sentry";
import { unknownToString } from "../../util/unknownToString";
import { HttpRequestExample } from "./HttpRequestExample";

function requiresUrlEncode(str: string): boolean {
Expand Down
62 changes: 17 additions & 45 deletions packages/ui/app/src/api-reference/examples/useHighlightJsonLines.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { isPlainObject } from "@fern-ui/core-utils";
import { captureException } from "@sentry/nextjs";
import { useAtomValue } from "jotai";
import { atomWithLazy, loadable } from "jotai/utils";
import { useMemoOne } from "use-memo-one";
import jsonpath from "jsonpath";
import { useMemo } from "react";
import { JsonPropertyPath, JsonPropertyPathPart } from "./JsonPropertyPath";
import { lineNumberOf } from "./utils";

Expand All @@ -23,18 +22,12 @@ type HighlightLineResult = number | [number, number];
* It assumes that the json object uses `JSON.stringify(json, undefined, 2)` to format the json object, and
* works by incrementally string-matching each part of the json path and merging the result of each part.
*
* @param jq jsonpath module (which we can't import directly because it dramatically increases bundle size)
* @param json unknown json object to query
* @param path json path, which is constructed from the api reference hover state
* @param start line number where the json object starts
* @returns a list of line numbers that match the json path
*/
export function getJsonLineNumbers(
jq: Awaited<typeof import("jsonpath")>,
json: unknown,
path: JsonPropertyPath,
start = 0,
): HighlightLineResult[] {
export function getJsonLineNumbers(json: unknown, path: JsonPropertyPath, start = 0): HighlightLineResult[] {
const jsonString = JSON.stringify(json, undefined, INDENT_SPACES);

const part = path[0];
Expand All @@ -45,10 +38,10 @@ export function getJsonLineNumbers(

const query = "$" + getQueryPart(part);

const results: unknown[] = jq.query(json, query);
const results: unknown[] = jsonpath.query(json, query);
if (part.type === "objectFilter") {
if (isPlainObject(json) && json[part.propertyName] === part.requiredStringValue) {
return getJsonLineNumbers(jq, json, path.slice(1), start);
return getJsonLineNumbers(json, path.slice(1), start);
}
}

Expand All @@ -65,7 +58,7 @@ export function getJsonLineNumbers(
return [];
}

const jsonLineNumbers = getJsonLineNumbers(jq, result, path.slice(1), startLine);
const jsonLineNumbers = getJsonLineNumbers(result, path.slice(1), startLine);

return jsonLineNumbers.map(
(line): HighlightLineResult =>
Expand Down Expand Up @@ -98,42 +91,21 @@ function getQueryPart(path: JsonPropertyPathPart) {
}
}

function createHoveredJsonLinesAtom(json: unknown, hoveredPropertyPath: JsonPropertyPath = [], jsonStartLine = 0) {
const atom = atomWithLazy(async () => {
if (hoveredPropertyPath.length === 0 || jsonStartLine < 0 || typeof window === "undefined") {
return [];
}
/**
* dynamically import jsonpath on the client-side
*/
const jq = await import("jsonpath");
return getJsonLineNumbers(jq, json, hoveredPropertyPath, jsonStartLine + 1);
});

/**
* Loadable has built-in try-catch for the async function
*/
return loadable(atom);
}

export function useHighlightJsonLines(
json: unknown,
hoveredPropertyPath: JsonPropertyPath = [],
jsonStartLine = 0,
): HighlightLineResult[] {
const atom = useMemoOne(
() => createHoveredJsonLinesAtom(json, hoveredPropertyPath, jsonStartLine),
[hoveredPropertyPath, jsonStartLine, json],
);

const value = useAtomValue(atom);
if (value.state === "hasData") {
return value.data;
} else if (value.state === "hasError") {
captureException(value.error, {
extra: { json, hoveredPropertyPath, jsonStartLine },
});
}
return useMemo(() => {
if (hoveredPropertyPath.length === 0 || jsonStartLine < 0 || typeof window === "undefined") {
return [];
}

return [];
try {
return getJsonLineNumbers(json, hoveredPropertyPath, jsonStartLine + 1);
} catch (error) {
captureException(error);
return [];
}
}, [hoveredPropertyPath, json, jsonStartLine]);
}
1 change: 0 additions & 1 deletion packages/ui/app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@ export { getRegistryServiceWithToken, provideRegistryService } from "./services/
export { renderThemeStylesheet } from "./themes/stylesheet/renderThemeStylesheet";
export { getGitHubInfo, getGitHubRepo } from "./util/github";
export { resolveDocsContent } from "./util/resolveDocsContent";
export { unknownToString } from "./util/unknownToString";
2 changes: 1 addition & 1 deletion packages/ui/app/src/mdx/plugins/rehypeFernCode.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { unknownToString } from "@fern-ui/core-utils";
import type { Element, Root } from "hast";
import type { MdxJsxAttribute, MdxJsxFlowElementHast } from "mdast-util-mdx-jsx";
import rangeParser from "parse-numeric-range";
import { visit } from "unist-util-visit";
import type { FernSyntaxHighlighterProps } from "../../syntax-highlighting/FernSyntaxHighlighter";
import { unknownToString } from "../../util/unknownToString";
import type { CodeGroup } from "../components/code";
import { isElement, isMdxJsxFlowElement, isText, toAttribute } from "./utils";

Expand Down
3 changes: 1 addition & 2 deletions packages/ui/app/src/mdx/plugins/utils.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { isPlainObject } from "@fern-ui/core-utils";
import { isPlainObject, unknownToString } from "@fern-ui/core-utils";
import type { Element, ElementContent, Node, Root, RootContent, Text } from "hast";
import type {
MdxJsxAttribute,
MdxJsxAttributeValueExpression,
MdxJsxExpressionAttribute,
MdxJsxFlowElementHast,
} from "mdast-util-mdx-jsx";
import { unknownToString } from "../../util/unknownToString";
import { valueToEstree } from "./to-estree";

export function isMdxJsxFlowElement(node: Node): node is MdxJsxFlowElementHast {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { useApiKeyInjectionConfig } from "../services/useApiKeyInjectionConfig";
import { PasswordInputGroup } from "./PasswordInputGroup";
import { PlaygroundEndpointForm } from "./endpoint/PlaygroundEndpointForm";
import { PlaygroundAuthState } from "./types";
import { oAuthClientCredentialReferencedEndpointLoginFlow } from "./utils";
import { oAuthClientCredentialReferencedEndpointLoginFlow } from "./utils/oauth";

interface PlaygroundAuthorizationFormProps {
auth: APIV1Read.ApiAuth;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { unknownToString } from "@fern-ui/core-utils";
import { ResolvedEndpointPathParts } from "../../../resolver/types";
import { unknownToString } from "../../../util/unknownToString";
// import { unknownToString } from "../../utils";

export function buildPath(path: ResolvedEndpointPathParts[], pathParameters?: Record<string, unknown>): string {
return path
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FernTooltipProvider } from "@fern-ui/components";
import { unknownToString } from "@fern-ui/core-utils";
import { Loadable, failed, loaded, loading, notStartedLoading } from "@fern-ui/loadable";
import { SendSolid } from "iconoir-react";
import { useSetAtom } from "jotai";
Expand Down Expand Up @@ -30,7 +31,6 @@ import {
getInitialEndpointRequestFormState,
getInitialEndpointRequestFormStateWithExample,
serializeFormStateBody,
unknownToString,
} from "../utils";
import { PlaygroundEndpointContent } from "./PlaygroundEndpointContent";
import { PlaygroundEndpointPath } from "./PlaygroundEndpointPath";
Expand Down Expand Up @@ -89,7 +89,7 @@ export const PlaygroundEndpoint: FC<PlaygroundEndpointProps> = ({ endpoint, type
);
const headers = {
...authHeaders,
...mapValues(formState.headers ?? {}, unknownToString),
...mapValues(formState.headers ?? {}, (value) => unknownToString(value)),
};

if (endpoint.method !== "GET" && endpoint.requestBody?.contentType != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { APIV1Read } from "@fern-api/fdr-sdk/client/types";
import { CopyToClipboardButton, FernButton } from "@fern-ui/components";
import { visitDiscriminatedUnion } from "@fern-ui/core-utils";
import { unknownToString, visitDiscriminatedUnion } from "@fern-ui/core-utils";
import * as Dialog from "@radix-ui/react-dialog";
import cn from "clsx";
import { Xmark } from "iconoir-react";
Expand All @@ -12,7 +12,7 @@ import { MaybeEnvironmentDropdown } from "../../components/MaybeEnvironmentDropd
import { ResolvedEndpointPathParts, ResolvedObjectProperty } from "../../resolver/types";
import { PlaygroundSendRequestButton } from "../PlaygroundSendRequestButton";
import { PlaygroundRequestFormState } from "../types";
import { buildRequestUrl, unknownToString } from "../utils";
import { buildRequestUrl } from "../utils";

interface PlaygroundEndpointPathProps {
method: APIV1Read.HttpMethod | undefined;
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/app/src/playground/form/PlaygroundMapForm.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { FernButton } from "@fern-ui/components";
import { isPlainObject } from "@fern-ui/core-utils";
import { isPlainObject, unknownToString } from "@fern-ui/core-utils";
import { Plus, Xmark } from "iconoir-react";
import { memo, useCallback, useEffect, useState } from "react";
import { ResolvedTypeDefinition, ResolvedTypeShape, unwrapOptional } from "../../resolver/types";
import { getDefaultValueForType, unknownToString } from "../utils";
import { getDefaultValueForType } from "../utils";
import { PlaygroundTypeReferenceForm } from "./PlaygroundTypeReferenceForm";

interface PlaygroundMapFormProps {
Expand Down
Loading

0 comments on commit aa0d005

Please sign in to comment.