Skip to content

Commit 7c23d5c

Browse files
committed
Support for ZodBranded.
Closes #286.
1 parent cb2eec6 commit 7c23d5c

File tree

4 files changed

+57
-41
lines changed

4 files changed

+57
-41
lines changed

CHANGELOG.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Support for Zod [branded types](https://zod.dev/?id=brand) in schemas. ([#286](https://github.com/ciscoheat/sveltekit-superforms/pull/286))
13+
1014
### Fixed
1115

12-
- Tainted fields were set to undefined when not needed, unnecessarily triggering client-side validation.
13-
- Schema transformations now updates the form data on the client depending on input type. Checkboxes, radio buttons and selects updates the data immediately. Other inputs waits until blurred. ([#298](https://github.com/ciscoheat/sveltekit-superforms/issues/298))
14-
- If in [SPA mode](https://superforms.rocks/concepts/spa), the `novalidate` attribute now only disables the browser validation constraints, not the entire client-side validation.
16+
- Tainted fields were set to undefined when not needed, unwantingly triggering client-side validation.
17+
- Schema transformations now updates the form data depending on input type. Checkboxes, radio buttons and selects updates the data immediately. Other inputs waits until blurred. ([#298](https://github.com/ciscoheat/sveltekit-superforms/issues/298))
18+
- In [SPA mode](https://superforms.rocks/concepts/spa), the `novalidate` attribute now only disables the browser validation constraints, not the entire client-side validation. ([#297](https://github.com/ciscoheat/sveltekit-superforms/discussions/297))
1519

1620
## [1.11.0] - 2023-11-28
1721

src/index.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,14 @@ test('Optional values', async () => {
221221

222222
test('Branded values', async () => {
223223
const schema = z.object({
224-
name: z.string().brand('name'),
224+
name: z.string().brand('name')
225225
});
226226

227227
const data = new FormData();
228228
data.append('name', 'Name');
229+
229230
const output = await superValidate(data, schema);
231+
230232
expect(output.valid).equals(true);
231233
expect(output.message).toBeUndefined();
232234
expect(output.data.name).equals('Name');

src/lib/schemaEntity.ts

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import type {
2020
ZodNumber,
2121
ZodDate,
2222
ZodArray,
23-
ZodPipeline
23+
ZodPipeline,
24+
ZodBranded
2425
} from 'zod';
2526

2627
import type { SuperValidateOptions } from './superValidate.js';
@@ -97,23 +98,34 @@ export function unwrapZodType(zodType: ZodTypeAny): ZodTypeInfo {
9798
//let i = 0;
9899
while (_wrapped) {
99100
//console.log(' '.repeat(++i * 2) + zodType.constructor.name);
100-
if (zodType._def.typeName == 'ZodNullable') {
101-
isNullable = true;
102-
zodType = (zodType as ZodNullable<ZodTypeAny>).unwrap();
103-
} else if (zodType._def.typeName == 'ZodDefault') {
104-
hasDefault = true;
105-
defaultValue = zodType._def.defaultValue();
106-
zodType = zodType._def.innerType;
107-
} else if (zodType._def.typeName == 'ZodOptional') {
108-
isOptional = true;
109-
zodType = (zodType as ZodOptional<ZodTypeAny>).unwrap();
110-
} else if (zodType._def.typeName == 'ZodEffects') {
111-
if (!effects) effects = zodType as ZodEffects<ZodTypeAny>;
112-
zodType = zodType._def.schema;
113-
} else if (zodType._def.typeName == 'ZodPipeline') {
114-
zodType = (zodType as ZodPipeline<ZodTypeAny, ZodTypeAny>)._def.out;
115-
} else {
116-
_wrapped = false;
101+
switch (zodType._def.typeName) {
102+
case 'ZodNullable':
103+
isNullable = true;
104+
zodType = (zodType as ZodNullable<ZodTypeAny>).unwrap();
105+
break;
106+
case 'ZodDefault':
107+
hasDefault = true;
108+
defaultValue = zodType._def.defaultValue();
109+
zodType = zodType._def.innerType;
110+
break;
111+
case 'ZodOptional':
112+
isOptional = true;
113+
zodType = (zodType as ZodOptional<ZodTypeAny>).unwrap();
114+
break;
115+
case 'ZodEffects':
116+
if (!effects) effects = zodType as ZodEffects<ZodTypeAny>;
117+
zodType = zodType._def.schema;
118+
break;
119+
case 'ZodPipeline':
120+
zodType = (zodType as ZodPipeline<ZodTypeAny, ZodTypeAny>)._def.out;
121+
break;
122+
case 'ZodBranded':
123+
zodType = (
124+
zodType as ZodBranded<ZodTypeAny, string | number | symbol>
125+
).unwrap();
126+
break;
127+
default:
128+
_wrapped = false;
117129
}
118130
}
119131

src/lib/superValidate.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -196,40 +196,38 @@ function formDataToValidation<T extends AnyZodObject>(
196196
typeInfo: ZodTypeInfo
197197
): unknown {
198198
const newValue = valueOrDefault(value, false, true, typeInfo);
199-
const zodType: ZodTypeAny =
200-
typeInfo.zodType._def.typeName === 'ZodBranded'
201-
? (typeInfo.zodType as ZodBranded<any, any>).unwrap()
202-
: typeInfo.zodType;
199+
const zodType = typeInfo.zodType;
200+
const typeName = zodType._def.typeName;
203201

204202
// If the value was empty, it now contains the default value,
205203
// so it can be returned immediately, unless it's boolean, which
206204
// means it could have been posted as a checkbox.
207-
if (!value && zodType._def.typeName != 'ZodBoolean') {
205+
if (!value && typeName != 'ZodBoolean') {
208206
return newValue;
209207
}
210208

211-
//console.log(`FormData field "${field}" (${zodType._def.typeName}): ${value}`
209+
//console.log(`FormData field "${field}" (${typeName}): ${value}`
212210

213-
if (zodType._def.typeName == 'ZodString') {
211+
if (typeName == 'ZodString') {
214212
return value;
215-
} else if (zodType._def.typeName == 'ZodNumber') {
213+
} else if (typeName == 'ZodNumber') {
216214
return (zodType as ZodNumber).isInt
217215
? parseInt(value ?? '', 10)
218216
: parseFloat(value ?? '');
219-
} else if (zodType._def.typeName == 'ZodBoolean') {
217+
} else if (typeName == 'ZodBoolean') {
220218
return Boolean(value == 'false' ? '' : value).valueOf();
221-
} else if (zodType._def.typeName == 'ZodDate') {
219+
} else if (typeName == 'ZodDate') {
222220
return new Date(value ?? '');
223-
} else if (zodType._def.typeName == 'ZodArray') {
221+
} else if (typeName == 'ZodArray') {
224222
const arrayType = unwrapZodType(zodType._def.type);
225223
return parseFormDataEntry(field, value, arrayType);
226-
} else if (zodType._def.typeName == 'ZodBigInt') {
224+
} else if (typeName == 'ZodBigInt') {
227225
try {
228226
return BigInt(value ?? '.');
229227
} catch {
230228
return NaN;
231229
}
232-
} else if (zodType._def.typeName == 'ZodLiteral') {
230+
} else if (typeName == 'ZodLiteral') {
233231
const literalType = typeof (zodType as ZodLiteral<unknown>).value;
234232

235233
if (literalType === 'string') return value;
@@ -241,12 +239,12 @@ function formDataToValidation<T extends AnyZodObject>(
241239
);
242240
}
243241
} else if (
244-
zodType._def.typeName == 'ZodUnion' ||
245-
zodType._def.typeName == 'ZodEnum' ||
246-
zodType._def.typeName == 'ZodAny'
242+
typeName == 'ZodUnion' ||
243+
typeName == 'ZodEnum' ||
244+
typeName == 'ZodAny'
247245
) {
248246
return value;
249-
} else if (zodType._def.typeName == 'ZodNativeEnum') {
247+
} else if (typeName == 'ZodNativeEnum') {
250248
const zodEnum = zodType as ZodNativeEnum<EnumLike>;
251249

252250
if (value !== null && value in zodEnum.enum) {
@@ -260,11 +258,11 @@ function formDataToValidation<T extends AnyZodObject>(
260258
return value;
261259
}
262260
return undefined;
263-
} else if (zodType._def.typeName == 'ZodSymbol') {
261+
} else if (typeName == 'ZodSymbol') {
264262
return Symbol(String(value));
265263
}
266264

267-
if (zodType._def.typeName == 'ZodObject') {
265+
if (typeName == 'ZodObject') {
268266
throw new SuperFormError(
269267
`Object found in form field "${field}". ` +
270268
`Set the dataType option to "json" and add use:enhance on the client to use nested data structures. ` +

0 commit comments

Comments
 (0)