Use zod preprocess to coerce string to number and string to boolean before performing zod validations. Using error messages on UI.

This commit is contained in:
Bradley Shellnut 2022-12-04 17:29:56 -08:00
parent 4bda219c22
commit 0c295c66b4
5 changed files with 54 additions and 32 deletions

View file

@ -3,20 +3,28 @@
import { boredState } from '$lib/stores/boredState';
export let form: ActionData;
const errors = form?.errors;
let submitting = $boredState?.loading;
let minAge = +form?.minAge || 1;
let minPlayers = +form?.minPlayers || 1;
let maxPlayers = +form?.maxPlayers || 1;
let exactMinPlayers = form?.exactMinPlayers || false;
let exactMaxPlayers = form?.exactMaxPlayers || false;
let minAge = +form?.data?.minAge || 1;
let minPlayers = +form?.data?.minPlayers || 1;
let maxPlayers = +form?.data?.maxPlayers || 1;
let exactMinPlayers = Boolean(form?.data?.exactMinPlayers) || false;
let exactMaxPlayers = Boolean(form?.data?.exactMaxPlayers) || false;
</script>
<fieldset class="advanced-search" aria-busy={submitting} disabled={submitting}>
<div>
<label for="minAge">
Min Age
<input id="minAge" name="minAge" bind:value={minAge} type="number" min="1" max="120" />
<input id="minAge" name="minAge" bind:value={minAge} type="number" min={1} max={120} />
</label>
{#if form?.errors?.minAge}
<div id="minPlayers-error" class="error">
<p aria-label={`Error: ${form?.errors?.minAge}`} class="center">
{form?.errors?.minAge}
</p>
</div>
{/if}
</div>
<div>
<label for="minPlayers">
@ -26,8 +34,8 @@
name="minPlayers"
bind:value={minPlayers}
type="number"
min="1"
max="50"
min={1}
max={50}
/>
</label>
<label for="exactMinPlayers" style="display: flex; gap: 1rem; place-items: center;">
@ -37,12 +45,13 @@
type="checkbox"
name="exactMinPlayers"
bind:checked={exactMinPlayers}
bind:value={exactMinPlayers}
/>
</label>
{#if form?.error?.id === 'minPlayers'}
{#if form?.errors?.minPlayers}
<div id="minPlayers-error" class="error">
<p aria-label={`Error: ${form.error.message}`} class="center">
Error: {form.error.message}
<p aria-label={`Error: ${form?.errors?.minPlayers}`} class="center">
{form?.errors?.minPlayers}
</p>
</div>
{/if}
@ -55,8 +64,8 @@
name="maxPlayers"
bind:value={maxPlayers}
type="number"
min="1"
max="50"
min={1}
max={50}
/>
</label>
<label for="exactMaxPlayers" style="display: flex; gap: 1rem; place-items: center;">
@ -66,6 +75,7 @@
type="checkbox"
name="exactMaxPlayers"
bind:checked={exactMaxPlayers}
bind:value={exactMaxPlayers}
/>
</label>
{#if form?.error?.id === 'maxPlayers'}

View file

@ -25,6 +25,7 @@
// console.log('search page data', data);
export let form: ActionData;
// console.log('search page form', form);
const errors = form?.errors;
export let showButton: boolean = false;
export let advancedSearch: boolean = false;
@ -37,7 +38,7 @@
let totalItems = form?.totalCount || data?.totalCount || 0;
let submitting = $boredState?.loading;
let name = form?.name || '';
let disclosureOpen = false;
let disclosureOpen = errors || false;
$: skip = (page - 1) * pageSize;
$: showPagination = $gameStore?.length > 1;

View file

@ -1,4 +1,4 @@
import { z } from 'zod';
import { z, ZodNumber, ZodOptional } from 'zod';
import zodToJsonSchema from 'zod-to-json-schema';
export const BoardGameSearch = z.object({
@ -16,14 +16,30 @@ export const saved_game_schema = z.object({
playtime: z.string()
});
// https://github.com/colinhacks/zod/discussions/330
// function IntegerString
// <schema extends (ZodNumber | ZodOptional<ZodNumber>)>
// (schema: schema)
// {
// return (
// z.preprocess((value) => (
// ( (typeof value === "string") ? parseInt(value, 10)
// : (typeof value === "number") ? value
// : undefined
// )), schema)
// )
// }
// minPlayers: IntegerString(z.number().min(1).max(50).optional());
export const search_schema = z.object({
name: z.string().trim().optional(),
minAge: z.number().min(1).max(120).optional(),
minPlayers: z.number().min(1).max(50).optional(),
maxPlayers: z.number().min(1).max(50).optional(),
exactMinAge: z.boolean().optional(),
exactMinPlayers: z.boolean().optional(),
exactMaxPlayers: z.boolean().optional(),
minAge: z.preprocess((a) => parseInt(z.string().parse(a), 10), z.number().min(1).max(120).optional()),
minPlayers: z.preprocess((a) => parseInt(z.string().parse(a), 10), z.number().min(1).max(50).optional()),
maxPlayers: z.preprocess((a) => parseInt(z.string().parse(a), 10), z.number().min(1).max(50).optional()),
exactMinAge: z.preprocess((a) => Boolean(a), z.boolean().optional()),
exactMinPlayers: z.preprocess((a) => Boolean(a), z.boolean().optional()),
exactMaxPlayers: z.preprocess((a) => Boolean(a), z.boolean().optional())
})
.superRefine(({ minPlayers, maxPlayers, minAge, exactMinAge, exactMinPlayers, exactMaxPlayers }, ctx) => {
console.log({ minPlayers, maxPlayers });

View file

@ -5,7 +5,9 @@
import Random from '$lib/components/random/index.svelte';
export let data: PageData;
console.log('data', data);
export let form: ActionData;
console.log('form', form);
</script>
<svelte:head>

View file

@ -18,6 +18,7 @@ export const actions: Actions = {
console.log("In search action specific")
// Do things in here
const formData = Object.fromEntries(await request.formData());
console.log('formData', formData);
console.log('passed in limit:', formData?.limit)
console.log('passed in skip:', formData?.skip)
const limit = formData?.limit || 10;
@ -26,8 +27,8 @@ export const actions: Actions = {
const queryParams: SearchQuery = {
order_by: 'rank',
ascending: false,
limit: parseInt(limit),
skip: parseInt(skip),
limit: +limit,
skip: +skip,
client_id: BOARD_GAME_ATLAS_CLIENT_ID,
fuzzy_match: true,
name: ''
@ -40,14 +41,8 @@ export const actions: Actions = {
if (random) {
queryParams.random = random;
} else {
// const minAge = form.get('minAge');
// const minPlayers = form.get('minPlayers');
// const maxPlayers = form.get('maxPlayers');
// const exactMinAge = form.get('exactMinAge') || false;
// const exactMinPlayers = form.get('exactMinPlayers') || false;
// const exactMaxPlayers = form.get('exactMaxPlayers') || false;
try {
console.log('Parsed', search_schema.parse(formData))
const {
name,
minAge,
@ -58,7 +53,6 @@ export const actions: Actions = {
exactMaxPlayers
} = search_schema.parse(formData);
console.log('Parsed', search_schema.parse(formData))
if (minAge) {
if (exactMinAge) {
@ -87,7 +81,6 @@ export const actions: Actions = {
queryParams.name = name;
}
} catch (error: unknown) {
if (error instanceof ZodError) {
const { fieldErrors: errors } = error.flatten();
return invalid(400, { data: formData, errors });