Skip to content

Commit

Permalink
Enhance path param detection
Browse files Browse the repository at this point in the history
  • Loading branch information
dan-lee committed Jan 28, 2025
1 parent 11b7e96 commit ef70951
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 64 deletions.
47 changes: 47 additions & 0 deletions packages/zudoku/src/lib/components/PathRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Fragment, type ReactNode } from "react";

type PathParamProps = {
name: string;
index: number;
originalValue?: string;
};

export const PathRenderer = ({
path,
renderParam,
}: {
path: string;
renderParam: (props: PathParamProps) => ReactNode;
}) =>
path.split("/").map((part, i, arr) => {
const matches = Array.from(part.matchAll(/{([^}]+)}/g));
const elements: ReactNode[] = [];
let lastIndex = 0;

matches.forEach((match, matchIndex) => {
const [originalValue, name] = match;
if (!name) return;
const startIndex = match.index!;

if (startIndex > lastIndex) {
elements.push(part.slice(lastIndex, startIndex));
}

elements.push(renderParam({ name, originalValue, index: matchIndex }));

lastIndex = startIndex + originalValue.length;
});

if (lastIndex < part.length) {
elements.push(part.slice(lastIndex));
}

return (
// eslint-disable-next-line react/no-array-index-key
<Fragment key={`${part}-${i}`}>
{elements}
{i < arr.length - 1 && "/"}
<wbr />
</Fragment>
);
});
6 changes: 3 additions & 3 deletions packages/zudoku/src/lib/plugins/openapi/ColorizedParam.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ export const ColorizedParam = ({
<span
{...{ [DATA_ATTR]: normalizedSlug }}
className={cn(
"relative inline-block rounded transition-all duration-100",
"rounded-lg",
// This may not contain (inline-)flex or (inline-)block otherwise it breaks the browser's full text search
"relative rounded transition-all duration-100 rounded-lg",
"border border-[--border-color] p-0.5 text-[--param-color] bg-[--background-color]",
"data-[active=true]:border-[--param-color] data-[active=true]:shadow data-[active=true]:-translate-y-px",
"data-[active=true]:border-[--param-color] data-[active=true]:shadow data-[active=true]:bottom-px",
className,
)}
title={title}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const ParameterListItem = ({
name={parameter.name}
backgroundOpacity="15%"
className="px-1"
slug={id + "-" + parameter.name.toLocaleLowerCase()}
slug={`${id}-${parameter.name}`}
/>
) : (
parameter.name
Expand Down
43 changes: 17 additions & 26 deletions packages/zudoku/src/lib/plugins/openapi/Sidecar.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useSuspenseQuery } from "@tanstack/react-query";
import { HTTPSnippet } from "@zudoku/httpsnippet";
import { Fragment, useMemo, useState, useTransition } from "react";
import { useMemo, useState, useTransition } from "react";
import { useSearchParams } from "react-router";
import { useSelectedServerStore } from "../../authentication/state.js";
import { PathRenderer } from "../../components/PathRenderer.js";
import { SyntaxHighlight } from "../../components/SyntaxHighlight.js";
import type { SchemaObject } from "../../oas/parser/index.js";
import { cn } from "../../util/cn.js";
Expand Down Expand Up @@ -114,31 +115,21 @@ export const Sidecar = ({

const requestBodyContent = operation.requestBody?.content;

const path = operation.path.split("/").map((part, i, arr) => {
const isParam =
(part.startsWith("{") && part.endsWith("}")) || part.startsWith(":");
const paramName = isParam ? part.replace(/[:{}]/g, "") : undefined;

return (
// eslint-disable-next-line react/no-array-index-key
<Fragment key={part + i}>
{paramName ? (
<ColorizedParam
name={paramName}
backgroundOpacity="0"
// same as in `ParameterListItem`
slug={`${operation.slug}-${paramName.toLocaleLowerCase()}`}
>
{part}
</ColorizedParam>
) : (
part
)}
{i < arr.length - 1 ? "/" : null}
<wbr />
</Fragment>
);
});
const path = (
<PathRenderer
path={operation.path}
renderParam={({ name }) => (
<ColorizedParam
name={name}
backgroundOpacity="0"
// same as in `ParameterListItem`
slug={`${operation.slug}-${name}`}
>
{`{${name}}`}
</ColorizedParam>
)}
/>
);

const { selectedServer } = useSelectedServerStore();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const PathParams = ({
<>
<Controller
control={control}
name={`pathParams.${i}.value`}
name={`pathParams.${i}.name`}
render={() => (
<div>
<ColorizedParam
Expand Down
56 changes: 23 additions & 33 deletions packages/zudoku/src/lib/plugins/openapi/playground/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { InfoIcon } from "lucide-react";
import { Fragment, useEffect, useRef, useTransition } from "react";
import { FormProvider, useForm } from "react-hook-form";
import { Alert, AlertDescription, AlertTitle } from "zudoku/ui/Alert.js";
import { PathRenderer } from "../../../components/PathRenderer.js";

import { Label } from "zudoku/ui/Label.js";
import { RadioGroup, RadioGroupItem } from "zudoku/ui/RadioGroup.js";
Expand Down Expand Up @@ -222,36 +223,27 @@ export const Playground = ({
},
});

const path = url.split("/").map((part, i, arr) => {
const isPathParam =
(part.startsWith("{") && part.endsWith("}")) || part.startsWith(":");
const replaced = part.replace(/[:{}]/g, "");
const value = formState.pathParams.find((p) => p.name === replaced)?.value;
const path = (
<PathRenderer
path={url}
renderParam={({ name, originalValue, index }) => {
const formValue = formState.pathParams.find(
(param) => param.name === name,
)?.value;

const pathParamValue = (
<ColorizedParam
backgroundOpacity="25%"
name={part}
slug={part}
title={
!value
? `Missing value for path parameter \`${replaced}\``
: undefined
}
>
{value ? encodeURIComponent(value) : part}
</ColorizedParam>
);

return (
// eslint-disable-next-line react/no-array-index-key
<Fragment key={part + i}>
{isPathParam ? pathParamValue : part}
{i < arr.length - 1 && "/"}
<wbr />
</Fragment>
);
});
return (
<ColorizedParam
name={name}
backgroundOpacity="0"
slug={name}
onClick={() => form.setFocus(`pathParams.${index}.value`)}
>
{formValue || originalValue}
</ColorizedParam>
);
}}
/>
);

const urlQueryParams = formState.queryParams
.filter((p) => p.active)
Expand All @@ -268,9 +260,7 @@ export const Playground = ({
{servers && servers.length > 1 ? (
<Select
onValueChange={(value) => {
startTransition(() => {
setSelectedServer(value);
});
startTransition(() => setSelectedServer(value));
}}
value={selectedServer}
defaultValue={selectedServer}
Expand Down Expand Up @@ -307,7 +297,7 @@ export const Playground = ({
<div className="border-r p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono flex items-center">
{method.toUpperCase()}
</div>
<div className="flex items-center flex-wrap p-2 font-mono text-xs break-all">
<div className="items-center p-2 font-mono text-xs break-words">
{serverSelect}
{path}
{urlQueryParams.length > 0 ? "?" : ""}
Expand Down

0 comments on commit ef70951

Please sign in to comment.