Skip to content

Commit

Permalink
feat: standard-schema compliant
Browse files Browse the repository at this point in the history
  • Loading branch information
GauBen committed Jan 29, 2025
1 parent 37cf8af commit 12c8a90
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-rings-dream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"formgator": minor
---

Formgator is now standard-schema compliant! Read more on https://standardschema.dev/
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"prettier.singleQuote": false,
"[svelte]": {
"editor.defaultFormatter": "svelte.svelte-vscode"
},
}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@changesets/cli": "^2.27.12",
"@standard-schema/spec": "^1.0.0",
"@sveltejs/kit": "^2.16.1",
"@types/node": "^22.12.0",
"pkgroll": "^2.6.1",
Expand Down
49 changes: 48 additions & 1 deletion src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { describe, it } from "node:test";
import type { StandardSchemaV1 } from "@standard-schema/spec";
import assert from "./assert.ts";
import { fail, failures } from "./definitions.ts";
import { type ReadonlyFormData, fail, failures } from "./definitions.ts";
import * as fg from "./index.ts";

describe("form()", () => {
Expand Down Expand Up @@ -58,4 +59,50 @@ describe("form()", () => {
}
});
});

describe(".~standard()", () => {
it("should accept valid inputs", () => {
const schema = fg.form({
text: fg.text(),
number: fg.range(),
}) satisfies StandardSchemaV1<ReadonlyFormData, { text: string | null; number: number }>;

const data = new FormData();
data.append("text", "Hello World!");
data.append("number", "50");
assert.deepEqualTyped(schema["~standard"].validate(data), {
value: { text: "Hello World!", number: 50 },
});
});

it("should reject invalid inputs", () => {
const schema = fg.form({
text: fg.text(),
number: fg.range(),
}) satisfies StandardSchemaV1<ReadonlyFormData, { text: string | null; number: number }>;

const data = new FormData();
data.append("number", "123");
assert.deepEqualTyped(schema["~standard"].validate(data), {
issues: [
{
message: "Invalid type",
path: ["text"],
},
{
message: "Too big, maximum value is 100",
path: ["number"],
},
],
});

assert.deepEqualTyped(schema["~standard"].validate({}), {
issues: [
{
message: "value must be FormData or URLSearchParams",
},
],
});
});
});
});
54 changes: 36 additions & 18 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,15 @@
* @module
*/

import type { StandardSchemaV1 } from "@standard-schema/spec";
import {
type FormInput,
type ReadonlyFormData,
type Result,
type ValidationIssue,
fail,
safeParse,
succeed,
safeParse as symbol,
} from "./definitions.ts";

export { checkbox } from "./validators/checkbox.ts";
Expand Down Expand Up @@ -159,29 +160,46 @@ export function form<T extends Record<string, FormInput<unknown>>>(
accepted: Partial<Output<T>>;
}
>;
"~standard": StandardSchemaV1.Props<ReadonlyFormData, Output<T>>;
} {
const safeParse = (data: ReadonlyFormData) => {
const entries: Array<[string, unknown]> = [];
const errorEntries: Array<[string, ValidationIssue | null]> = [];
for (const [name, input] of Object.entries(inputs)) {
const result = input[symbol](data, name);
if (result.success === false) errorEntries.push([name, result.error]);
else entries.push([name, result.data]);
}
return errorEntries.length === 0
? succeed(Object.fromEntries(entries) as Output<T>)
: fail({
issues: Object.fromEntries(errorEntries) as Issues<T>,
accepted: Object.fromEntries(entries) as Partial<Output<T>>,
});
};
return {
inputs,
safeParse: (data) => {
const entries: Array<[string, unknown]> = [];
const errorEntries: Array<[string, ValidationIssue | null]> = [];
for (const [name, input] of Object.entries(inputs)) {
const result = input[safeParse](data, name);
if (result.success === false) errorEntries.push([name, result.error]);
else entries.push([name, result.data]);
}
return errorEntries.length === 0
? succeed(Object.fromEntries(entries) as Output<T>)
: fail({
issues: Object.fromEntries(errorEntries) as Issues<T>,
accepted: Object.fromEntries(entries) as Partial<Output<T>>,
});
},
parse(data) {
const result = this.safeParse(data);
safeParse,
parse: (data) => {
const result = safeParse(data);
if (result.success === false)
throw new FormgatorError(result.error.issues, result.error.accepted);
return result.data;
},
"~standard": {
version: 1,
vendor: "formgator",
validate: (value) => {
if (!(value instanceof URLSearchParams) && !(value instanceof FormData))
return { issues: [{ message: "value must be FormData or URLSearchParams" }] };
const result = safeParse(value);
if (result.success) return { value: result.data };
return {
issues: Object.entries<ValidationIssue>(result.error.issues).map(
([key, { message }]) => ({ message, path: [key] }),
),
};
},
},
};
}
8 changes: 8 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,13 @@ __metadata:
languageName: node
linkType: hard

"@standard-schema/spec@npm:^1.0.0":
version: 1.0.0
resolution: "@standard-schema/spec@npm:1.0.0"
checksum: 10c0/a1ab9a8bdc09b5b47aa8365d0e0ec40cc2df6437be02853696a0e377321653b0d3ac6f079a8c67d5ddbe9821025584b1fb71d9cc041a6666a96f1fadf2ece15f
languageName: node
linkType: hard

"@sveltejs/kit@npm:^2.16.1":
version: 2.16.1
resolution: "@sveltejs/kit@npm:2.16.1"
Expand Down Expand Up @@ -1646,6 +1653,7 @@ __metadata:
dependencies:
"@biomejs/biome": "npm:1.9.4"
"@changesets/cli": "npm:^2.27.12"
"@standard-schema/spec": "npm:^1.0.0"
"@sveltejs/kit": "npm:^2.16.1"
"@types/node": "npm:^22.12.0"
pkgroll: "npm:^2.6.1"
Expand Down

0 comments on commit 12c8a90

Please sign in to comment.