Skip to content

Commit c8c8c80

Browse files
committed
Fixed error mapping for deeply nested errors where no default value existed.
1 parent 3f7c3bf commit c8c8c80

File tree

5 files changed

+163
-10
lines changed

5 files changed

+163
-10
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ Headlines: Added, Changed, Deprecated, Removed, Fixed, Security
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [Unreleased]
9+
10+
### Fixed
11+
12+
- Fixed error mapping for deeply nested errors where no default value existed.
13+
814
## [2.24.0] - 2025-03-09
915

1016
### Added

src/lib/errors.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -234,22 +234,28 @@ export function replaceInvalidDefaults<T extends Record<string, unknown>>(
234234
for (const error of Errors) {
235235
if (!error.path) continue;
236236

237-
Defaults_traverseAndReplace({
238-
path: error.path,
239-
value: pathExists(Defaults, error.path)?.value
240-
});
237+
Defaults_traverseAndReplace(
238+
{
239+
path: error.path,
240+
value: pathExists(Defaults, error.path)?.value
241+
},
242+
true
243+
);
241244
}
242245
}
243246

244247
//#endregion
245248

246249
//#region Defaults
247250

248-
function Defaults_traverseAndReplace(defaultPath: {
249-
path: (string | number | symbol)[];
250-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
251-
value: any;
252-
}): void {
251+
function Defaults_traverseAndReplace(
252+
defaultPath: {
253+
path: (string | number | symbol)[];
254+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
255+
value: any;
256+
},
257+
traversingErrors = false
258+
): void {
253259
const currentPath = defaultPath.path;
254260
if (!currentPath || !currentPath[0]) return;
255261
if (typeof currentPath[0] === 'string' && preprocessed?.includes(currentPath[0])) return;
@@ -279,9 +285,11 @@ export function replaceInvalidDefaults<T extends Record<string, unknown>>(
279285
const typePath = currentPath.filter((p) => /\D/.test(String(p)));
280286
const pathTypes = traversePath(Types, typePath, (path) => {
281287
//console.log(path.path, path.value); //debug
282-
return '__items' in path.value ? path.value.__items : path.value;
288+
return path.value && '__items' in path.value ? path.value.__items : path.value;
283289
});
284290
if (!pathTypes) {
291+
// Return if checking for errors, as there may be deep errors that doesn't exist in the defaults.
292+
if (traversingErrors) return;
285293
throw new SchemaError('No types found for defaults', currentPath);
286294
}
287295

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { Actions, PageServerLoad } from './$types.js';
2+
3+
import { superValidate, message } from '$lib/index.js';
4+
import { zod } from '$lib/adapters/zod.js';
5+
import { fail } from '@sveltejs/kit';
6+
import { schema } from './schema.js';
7+
8+
const now = new Date();
9+
const pastDate = new Date();
10+
pastDate.setDate(now.getDate() - 7); // Subtract 7 days
11+
12+
export const load: PageServerLoad = async () => {
13+
const data = {
14+
updates: [
15+
{
16+
updates: {
17+
dueDate: pastDate
18+
}
19+
}
20+
]
21+
};
22+
const form = await superValidate(data, zod(schema), { errors: false });
23+
return { form };
24+
};
25+
26+
export const actions: Actions = {
27+
default: async ({ request }) => {
28+
const form = await superValidate(request, zod(schema));
29+
console.dir(form, { depth: 5 });
30+
31+
if (!form.valid) return fail(400, { form });
32+
33+
return message(form, 'Form posted successfully!');
34+
}
35+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<script lang="ts">
2+
import { page } from '$app/state';
3+
import { superForm } from '$lib/index.js';
4+
import SuperDebug from '$lib/index.js';
5+
6+
let { data } = $props();
7+
8+
const { form, errors, message, enhance } = superForm(data.form, { dataType: 'json' });
9+
</script>
10+
11+
<h3>Superforms testing ground - Zod</h3>
12+
13+
{#if $message}
14+
<!-- eslint-disable-next-line svelte/valid-compile -->
15+
<div class="status" class:error={page.status >= 400} class:success={page.status == 200}>
16+
{$message}
17+
</div>
18+
{/if}
19+
20+
<form method="POST" use:enhance>
21+
<p>Submit and check the error</p>
22+
{#if $errors.updates?.[0].updates?.dueDate}
23+
<p class="invalid">{$errors.updates[0].updates.dueDate}</p>
24+
{/if}
25+
<button>Submit</button>
26+
</form>
27+
28+
<hr />
29+
<p>
30+
💥 <a target="_blank" href="https://superforms.rocks">Created with Superforms for SvelteKit</a> 💥
31+
</p>
32+
33+
<style>
34+
.invalid {
35+
color: red;
36+
}
37+
38+
.status {
39+
color: white;
40+
padding: 4px;
41+
padding-left: 8px;
42+
border-radius: 2px;
43+
font-weight: 500;
44+
}
45+
46+
.status.success {
47+
background-color: seagreen;
48+
}
49+
50+
.status.error {
51+
background-color: #ff2a02;
52+
}
53+
54+
input {
55+
background-color: #ddd;
56+
}
57+
58+
a {
59+
text-decoration: underline;
60+
}
61+
62+
hr {
63+
margin-top: 4rem;
64+
}
65+
66+
form {
67+
padding-top: 1rem;
68+
padding-bottom: 1rem;
69+
}
70+
</style>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { z } from 'zod';
2+
3+
const dueDateValueSchema = z
4+
.date()
5+
.refine(
6+
(v) => {
7+
if (v) {
8+
return v.getTime() > Date.now();
9+
}
10+
return true;
11+
},
12+
{ message: 'Due date must be in the future' }
13+
)
14+
.nullable()
15+
.optional();
16+
17+
export const itemUpdatePropertiesSchema = z.object({
18+
dueDate: dueDateValueSchema
19+
});
20+
21+
const itemUpdateSchema = z.object({
22+
//itemId: z.string().superRefine(zodSuperRefiners.textLaxed()),
23+
//childId: z.string().nullable().superRefine(zodSuperRefiners.textLaxed()),
24+
updates: itemUpdatePropertiesSchema
25+
});
26+
const riskImprovementItemUpdateSchema = z.object({
27+
itemid: z.string()
28+
});
29+
30+
export const schema = z.object({
31+
updates: z.array(itemUpdateSchema.or(riskImprovementItemUpdateSchema))
32+
});
33+
34+
export type Schema = z.infer<typeof schema>;

0 commit comments

Comments
 (0)