summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Billy Le <hi@billyle.dev> 2024-08-13 18:21:12 +0800
committerGravatar GitHub <noreply@github.com> 2024-08-13 11:21:12 +0100
commitdc0a297e2a4bea3db8310cc98c51b2f94ede5fde (patch)
tree1f9492d43e8f4faacadf0eeed0bef6dc589a6d45
parent242be516183e689fd93d3f2596e8cc807ec0a6a2 (diff)
downloadastro-dc0a297e2a4bea3db8310cc98c51b2f94ede5fde.tar.gz
astro-dc0a297e2a4bea3db8310cc98c51b2f94ede5fde.tar.zst
astro-dc0a297e2a4bea3db8310cc98c51b2f94ede5fde.zip
Set action input default values from zod if FormData key is not present (#11655)
* fix: remove duplicate while loop. use correct boolean values on validation * chore: rephrase changeset
-rw-r--r--.changeset/nervous-garlics-beam.md5
-rw-r--r--packages/astro/src/actions/runtime/virtual/server.ts16
-rw-r--r--packages/astro/test/units/actions/form-data-to-object.test.js57
3 files changed, 75 insertions, 3 deletions
diff --git a/.changeset/nervous-garlics-beam.md b/.changeset/nervous-garlics-beam.md
new file mode 100644
index 000000000..2e5413d74
--- /dev/null
+++ b/.changeset/nervous-garlics-beam.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fixes Astro Actions `input` validation when using `default` values with a form input.
diff --git a/packages/astro/src/actions/runtime/virtual/server.ts b/packages/astro/src/actions/runtime/virtual/server.ts
index 7aea22b2f..e334cb632 100644
--- a/packages/astro/src/actions/runtime/virtual/server.ts
+++ b/packages/astro/src/actions/runtime/virtual/server.ts
@@ -134,11 +134,21 @@ export function formDataToObject<T extends z.AnyZodObject>(
const obj: Record<string, unknown> = {};
for (const [key, baseValidator] of Object.entries(schema.shape)) {
let validator = baseValidator;
- while (validator instanceof z.ZodOptional || validator instanceof z.ZodNullable) {
+
+ while (validator instanceof z.ZodOptional || validator instanceof z.ZodNullable || validator instanceof z.ZodDefault) {
+ // use default value when key is undefined
+ if(validator instanceof z.ZodDefault && !formData.has(key)) {
+ obj[key] = validator._def.defaultValue();
+ }
validator = validator._def.innerType;
}
- if (validator instanceof z.ZodBoolean) {
- obj[key] = formData.has(key);
+
+ if (!formData.has(key) && key in obj) {
+ // continue loop if form input is not found and default value is set
+ continue;
+ } else if (validator instanceof z.ZodBoolean) {
+ const val = formData.get(key);
+ obj[key] = val === 'true' ? true : val === 'false' ? false : formData.has(key)
} else if (validator instanceof z.ZodArray) {
obj[key] = handleFormDataGetAll(key, formData, validator);
} else {
diff --git a/packages/astro/test/units/actions/form-data-to-object.test.js b/packages/astro/test/units/actions/form-data-to-object.test.js
index 6cce8f3d7..b27d50c8c 100644
--- a/packages/astro/test/units/actions/form-data-to-object.test.js
+++ b/packages/astro/test/units/actions/form-data-to-object.test.js
@@ -46,15 +46,24 @@ describe('formDataToObject', () => {
it('should handle boolean checks', () => {
const formData = new FormData();
formData.set('isCool', 'yes');
+ formData.set('isTrue', true)
+ formData.set("isFalse", false);
+ formData.set("falseString", 'false')
const input = z.object({
isCool: z.boolean(),
isNotCool: z.boolean(),
+ isTrue: z.boolean(),
+ isFalse: z.boolean(),
+ falseString: z.boolean(),
});
const res = formDataToObject(formData, input);
assert.equal(res.isCool, true);
assert.equal(res.isNotCool, false);
+ assert.equal(res.isTrue, true);
+ assert.equal(res.isFalse, false);
+ assert.equal(res.falseString, false)
});
it('should handle optional values', () => {
@@ -91,6 +100,37 @@ describe('formDataToObject', () => {
assert.equal(res.age, null);
});
+ it('should handle zod default values', () => {
+ const formData = new FormData();
+
+ const input = z.object({
+ name: z.string().default("test"),
+ email: z.string().default('test@test.test'),
+ favoriteNumbers: z.array(z.number()).default([1,2])
+ });
+
+ const res = formDataToObject(formData, input);
+ assert.equal(res.name, 'test');
+ assert.equal(res.email, 'test@test.test');
+ assert.deepEqual(res.favoriteNumbers, [1, 2]);
+ })
+
+ it('should handle zod chaining of optional, default, and nullish values', () => {
+ const formData = new FormData();
+ formData.set('email', 'test@test.test')
+
+ const input = z.object({
+ name: z.string().default("test").optional(),
+ email: z.string().optional().nullish(),
+ favoriteNumbers: z.array(z.number()).default([1,2]).nullish().optional()
+ });
+
+ const res = formDataToObject(formData, input);
+ assert.equal(res.name, 'test');
+ assert.equal(res.email, 'test@test.test');
+ assert.deepEqual(res.favoriteNumbers, [1, 2])
+ })
+
it('should handle File objects', () => {
const formData = new FormData();
formData.set('file', new File([''], 'test.txt'));
@@ -135,4 +175,21 @@ describe('formDataToObject', () => {
assert.ok(Array.isArray(res.age), 'age is not an array');
assert.deepEqual(res.age.sort(), [25, 30, 35]);
});
+
+ it('should handle an array of File objects', () => {
+ const formData = new FormData();
+ const file1 = new File([''], 'test1.txt');
+ const file2 = new File([''], 'test2.txt')
+ formData.append('files', file1);
+ formData.append('files', file2);
+
+ const input = z.object({
+ files: z.array(z.instanceof(File))
+ });
+
+ const res = formDataToObject(formData, input);
+
+ assert.equal(res.files instanceof Array, true);
+ assert.deepEqual(res.files, [file1, file2])
+ });
});