Skip to content

Commit

Permalink
refactor: getParametersExceptFirst return array instead of tuple (#23)
Browse files Browse the repository at this point in the history
* refactor: `getParametersExceptFirst` return array instead of tuple

* chore: add changeset

* chore: lint
  • Loading branch information
lawvs committed Jul 29, 2024
1 parent 1af4230 commit 336fe84
Show file tree
Hide file tree
Showing 17 changed files with 400 additions and 318 deletions.
6 changes: 6 additions & 0 deletions .changeset/giant-ties-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fn-sphere/filter": patch
"@fn-sphere/core": patch
---

`getParametersExceptFirst` now returns an array instead of a Zod tuple.
4 changes: 2 additions & 2 deletions packages/core/src/filter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ test("basic usage", () => {
const firstFilter = availableFilter[0];
expect(firstFilter.name).toEqual("is admin");
const requiredParameters = getParametersExceptFirst(firstFilter);
expect(requiredParameters.items).toHaveLength(0);
expect(requiredParameters).toHaveLength(0);

const data: Data[] = [
{
Expand Down Expand Up @@ -98,7 +98,7 @@ test("filter nested obj", () => {
const firstFilterSchema = availableFilter[0];
expect(firstFilterSchema.name).toEqual("number equal");
const requiredParameters = getParametersExceptFirst(firstFilterSchema);
expect(requiredParameters.items).toHaveLength(1);
expect(requiredParameters).toHaveLength(1);

const data: Data[] = [
{
Expand Down
65 changes: 64 additions & 1 deletion packages/core/src/filter/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { describe, expect, it } from "vitest";
import * as z from "zod";
import { isSameType } from "zod-compare";
import type { FilterPath } from "./types.js";
import { getSchemaAtPath, getValueAtPath } from "./utils.js";
import {
getFirstParameters,
getParametersExceptFirst,
getSchemaAtPath,
getValueAtPath,
} from "./utils.js";

describe("getValueFromPath", () => {
it("should return the correct value for a given path", () => {
Expand Down Expand Up @@ -65,3 +71,60 @@ describe("getSchemaFromPath", () => {
);
});
});

describe("getFirstParameters getParametersExceptFirst", () => {
it("should return the correct parameters except the first one", () => {
const schema = {
name: "test",
define: z.function().args(z.number(), z.boolean()).returns(z.void()),
implement: () => {},
};
expect(isSameType(getFirstParameters(schema), z.number())).toEqual(true);
expect(
isSameType(
z.tuple(getParametersExceptFirst(schema)),
z.tuple([z.boolean()]),
),
).toEqual(true);
});

it("should return the parameters except the first one", () => {
const schema = {
name: "test",
define: z
.function()
.args(z.number(), z.boolean(), z.string())
.returns(z.void()),
implement: () => {},
};
expect(isSameType(getFirstParameters(schema), z.number())).toEqual(true);
expect(
isSameType(
z.tuple(getParametersExceptFirst(schema)),
z.tuple([z.boolean(), z.string()]),
),
).toEqual(true);
});

it("should return empty array when only one parameter", () => {
const schema = {
name: "test",
define: z.function().args(z.number()).returns(z.void()),
implement: () => {},
};
expect(isSameType(getFirstParameters(schema), z.number())).toEqual(true);
expect(getParametersExceptFirst(schema)).toEqual([]);
});

it("should throw an error when no parameters", () => {
const schema = {
name: "test",
define: z.function().args().returns(z.void()),
implement: () => {},
};
expect(() => getFirstParameters(schema)).toThrowError();
expect(() => getParametersExceptFirst(schema)).toThrowError(
"Invalid fnSchema parameters!",
);
});
});
22 changes: 10 additions & 12 deletions packages/core/src/filter/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ZodAny, ZodObject, z, type ZodType } from "zod";
import { z, type ZodType } from "zod";
import { isSameType } from "zod-compare";
import type { GenericFnSchema, StandardFnSchema } from "../types.js";
import { isFilterFn, unreachable } from "../utils.js";
Expand Down Expand Up @@ -43,7 +43,7 @@ export const getFirstParameters = (fnSchema: StandardFnSchema) => {
throw new Error("Invalid filter parameters!");
}

return fullParameters.items.at(0) as ZodAny;
return fullParameters.items.at(0) as z.ZodTypeAny;
};

/**
Expand All @@ -56,7 +56,9 @@ export const getFirstParameters = (fnSchema: StandardFnSchema) => {
* **Parameter** is the variable in the declaration of the function.
* **Argument** is the actual value of this variable that gets passed to the function.
*/
export const getParametersExceptFirst = (fnSchema: StandardFnSchema) => {
export const getParametersExceptFirst = (
fnSchema: StandardFnSchema,
): [] | [z.ZodTypeAny, ...z.ZodTypeAny[]] => {
const fullParameters = fnSchema.define.parameters();
if (!fullParameters.items.length) {
console.error(
Expand All @@ -67,25 +69,21 @@ export const getParametersExceptFirst = (fnSchema: StandardFnSchema) => {
throw new Error("Invalid fnSchema parameters!");
}

const stillNeed = z.tuple(fullParameters.items.slice(1));
const stillNeed = fullParameters.items.slice(1);
// zod not support function rest parameter yet
// See https://github.com/colinhacks/zod/issues/2859
// https://github.com/colinhacks/zod/blob/a5a9d31018f9c27000461529c582c50ade2d3937/src/types.ts#L3268
const rest = fullParameters._def.rest;
// ZodFunction will always have a unknown rest parameter
// See https://github.com/colinhacks/zod/blob/4641f434f3bb3dab1bb8cb07f44dd2693c72e35e/src/types.ts#L3991
if (isSameType(rest, z.unknown())) {
return stillNeed;
}
if (!rest) {
if (!isSameType(rest, z.unknown())) {
console.warn(
"No rest parameter found, try to report this issue to developer.",
"Rest parameter is not supported yet, try to report this issue to developer.",
fnSchema,
fullParameters,
);
return stillNeed;
}
return stillNeed.rest(rest);
return stillNeed;
};

export const countNumberOfRules = (
Expand Down Expand Up @@ -155,7 +153,7 @@ export const getSchemaAtPath = <T extends ZodType = ZodType>(
if (result == null) {
return defaultValue as T;
}
if (!(result instanceof ZodObject)) {
if (!(result instanceof z.ZodObject)) {
return defaultValue as T;
}
result = result.shape[path[i]];
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/filter/validation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type ZodTypeAny } from "zod";
import { z, type ZodTypeAny } from "zod";
import { isSameType } from "zod-compare";
import type { FnSchema, StandardFnSchema } from "../types.js";
import { isGenericFilter, unreachable } from "../utils.js";
Expand Down Expand Up @@ -124,7 +124,7 @@ const validateStandardFnRule = ({

const requiredParameters = getParametersExceptFirst(fnSchema);
if (!fnSchema.skipValidate) {
const parseResult = requiredParameters.safeParse(rule.arguments);
const parseResult = z.tuple(requiredParameters).safeParse(rule.arguments);
return parseResult;
}
return {
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/fn-sphere.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { z } from "zod";
import { isSameType } from "zod-compare";
import { createFilterSphere } from "./filter/index.js";
import type { GenericFnSchema, StandardFnSchema, ZodAnyFn } from "./types.js";
import type {
GenericFnSchema,
StandardFnSchema,
ZodAnyFunction,
} from "./types.js";
import { isFilterFn as isFilterSchema } from "./utils.js";

export function defineTypedFn<
Expand Down Expand Up @@ -44,7 +48,7 @@ export const createFnSphere = () => {
state.fnMap[fn.name] = fn;
};

const registerFnList = <T extends ZodAnyFn>(
const registerFnList = <T extends ZodAnyFunction>(
fnList: StandardFnSchema<NoInfer<T>>[],
) => {
fnList.forEach((fn) => {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import type { TypeOf, ZodFunction, ZodTuple, ZodType, ZodTypeAny } from "zod";
/**
* @internal
*/
export type ZodAnyFn = ZodFunction<ZodTuple<any, any>, ZodTypeAny>;
export type ZodAnyFunction = ZodFunction<ZodTuple<any, any>, ZodTypeAny>;

export type StandardFnSchema<T extends ZodAnyFn = ZodAnyFn> = {
export type StandardFnSchema<T extends ZodAnyFunction = ZodAnyFunction> = {
name: string;
define: T;
implement: TypeOf<T>;
Expand All @@ -15,7 +15,7 @@ export type StandardFnSchema<T extends ZodAnyFn = ZodAnyFn> = {

export type GenericFnSchema<
Generic extends ZodType = any,
Fn extends ZodAnyFn = ZodAnyFn,
Fn extends ZodAnyFunction = ZodAnyFunction,
> = {
name: string;
genericLimit: (t: ZodType) => t is Generic;
Expand Down
7 changes: 2 additions & 5 deletions packages/filter/src/filter-rule/filter-data-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
type StandardFnSchema,
getParametersExceptFirst,
} from "@fn-sphere/core";
import { z } from "zod";
import { useDataInputView, useView } from "../specs/index.js";

type FilterDataInputProps = {
Expand All @@ -21,9 +20,7 @@ export const FilterDataInput = ({
const requiredArguments = filterSchema
? getParametersExceptFirst(filterSchema)
: undefined;
const DataInputView = useDataInputView(
requiredArguments ?? z.tuple([z.never()]),
);
const DataInputView = useDataInputView(requiredArguments);

if (!requiredArguments) {
return <Placeholder />;
Expand All @@ -32,7 +29,7 @@ export const FilterDataInput = ({
return (
<DataInputView
rule={rule}
inputSchema={requiredArguments}
requiredDataSchema={requiredArguments}
onChange={onChange}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion packages/filter/src/specs/context.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createContext, type ReactNode } from "react";
import { presetUiSpec } from "./preset.js";
import { presetUiSpec } from "./presets/index.js";
import type { UiSpec } from "./types.js";

export const ViewContext = createContext<UiSpec>(presetUiSpec);
Expand Down
11 changes: 4 additions & 7 deletions packages/filter/src/specs/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,25 @@ export const useView = <T extends keyof UiSpec["views"]>(view: T) => {
};

export const useDataInputView = (
schema: z.ZodTuple,
schema?: [] | [z.ZodTypeAny, ...z.ZodTypeAny[]],
): ComponentType<DataInputViewProps> => {
const specs = useContext(ViewContext);
const dataInputViews = specs.dataInputViews;
if (isSameType(schema, z.tuple([z.never()]))) {
if (!schema) {
return () => null;
}
const targetSpec = dataInputViews.find((spec) => {
if (typeof spec.match === "function") {
return spec.match(schema);
}
return isSameType(spec.match, schema);
return isSameType(z.tuple(spec.match), z.tuple(schema));
});
if (!targetSpec) {
console.error("no view spec found for", schema, specs);
return () => (
<div>
No view spec found for&nbsp;
{schema._def.typeName +
"<" +
schema.items.map((i) => i._def.typeName).join(", ") +
">"}
{"<" + schema.map((i) => i._def.typeName).join(", ") + ">"}
</div>
);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/filter/src/specs/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { FilterUiProvider } from "./context.js";
export { useDataInputView, useView } from "./hooks.js";
export { presetDataInputSpecs, presetUiSpec } from "./preset.js";
export { presetDataInputSpecs, presetUiSpec } from "./presets/index.js";
Loading

0 comments on commit 336fe84

Please sign in to comment.