Skip to content

Commit

Permalink
Encode action result in cookie (#12016)
Browse files Browse the repository at this point in the history
* Encode action result in cookie

* Add a changeset
  • Loading branch information
matthewp authored Sep 17, 2024
1 parent 6860beb commit 837ee3a
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/rotten-phones-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fixes actions with large amount of validation errors
19 changes: 19 additions & 0 deletions packages/astro/e2e/actions-blog.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ test.describe('Astro Actions - Blog', () => {
await expect(form.locator('p[data-error="body"]')).toBeVisible();
});

test('Comment action - progressive fallback lots of validation errors', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/lots-of-fields/'));

const form = page.getByTestId('lots');
const submitButton = form.getByRole('button');
await submitButton.click();

const expectedText = 'Expected string, received null';

const fields = [
'one', 'two', 'three', 'four', 'five',
'six', 'seven', 'eight', 'nine', 'ten'
];

for await(const field of fields) {
await expect(form.locator(`.${field}.error`)).toHaveText(expectedText);
}
});

test('Comment action - progressive fallback success', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/blog/first-post/'));

Expand Down
19 changes: 19 additions & 0 deletions packages/astro/e2e/fixtures/actions-blog/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,24 @@ export const server = {
return comment;
},
}),

lotsOfStuff: defineAction({
accept: 'form',
input: z.object({
one: z.string().min(3),
two: z.string().min(3),
three: z.string().min(3),
four: z.string().min(3),
five: z.string().min(3),
six: z.string().min(3),
seven: z.string().min(3),
eight: z.string().min(3),
nine: z.string().min(3),
ten: z.string().min(3)
}),
handler(form) {
return form;
}
})
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
export const prerender = false;
import { actions } from 'astro:actions';
const result = Astro.getActionResult(actions.blog.lotsOfStuff);
---

<html>
<head>
<title>Actions</title>
<style>
form {
display: grid;
grid-row-gap: 10px;
}
</style>
</head>
<body>
<form method="POST" action={actions.blog.lotsOfStuff} data-testid="lots">
<input type="text" name="one" value="">
<span class="one error">{result?.error?.fields.one}</span>
<input type="text" name="two" value="">
<span class="two error">{result?.error?.fields.two}</span>
<input type="text" name="three" value="">
<span class="three error">{result?.error?.fields.three}</span>
<input type="text" name="four" value="">
<span class="four error">{result?.error?.fields.four}</span>
<input type="text" name="five" value="">
<span class="five error">{result?.error?.fields.five}</span>
<input type="text" name="six" value="">
<span class="six error">{result?.error?.fields.six}</span>
<input type="text" name="seven" value="">
<span class="seven error">{result?.error?.fields.seven}</span>
<input type="text" name="eight" value="">
<span class="eight error">{result?.error?.fields.eight}</span>
<input type="text" name="nine" value="">
<span class="nine error">{result?.error?.fields.nine}</span>
<input type="text" name="ten" value="">
<span class="ten error">{result?.error?.fields.ten}</span>
<button type="submit">Submit</button>
</form>
</body>
</html>
17 changes: 12 additions & 5 deletions packages/astro/src/actions/runtime/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type SerializedActionResult,
serializeActionResult,
} from './virtual/shared.js';
import { encodeBase64, decodeBase64 } from '@oslojs/encoding';

export type ActionPayload = {
actionResult: SerializedActionResult;
Expand All @@ -20,6 +21,9 @@ export type Locals = {
_actionPayload: ActionPayload;
};

const decoder = new TextDecoder();
const encoder = new TextEncoder();

export const onRequest = defineMiddleware(async (context, next) => {
if (context.isPrerendered) {
if (context.request.method === 'POST') {
Expand All @@ -39,8 +43,10 @@ export const onRequest = defineMiddleware(async (context, next) => {
// so short circuit if already defined.
if (locals._actionPayload) return next();

const actionPayload = context.cookies.get(ACTION_QUERY_PARAMS.actionPayload)?.json();
if (actionPayload) {
const actionPayloadCookie = context.cookies.get(ACTION_QUERY_PARAMS.actionPayload)?.value;
if (actionPayloadCookie) {
const actionPayload = JSON.parse(decoder.decode(decodeBase64(actionPayloadCookie)));

if (!isActionPayload(actionPayload)) {
throw new Error('Internal: Invalid action payload in cookie.');
}
Expand Down Expand Up @@ -124,10 +130,11 @@ async function redirectWithResult({
actionName: string;
actionResult: SafeResult<any, any>;
}) {
context.cookies.set(ACTION_QUERY_PARAMS.actionPayload, {
actionName,
const cookieValue = encodeBase64(encoder.encode(JSON.stringify({
actionName: actionName,
actionResult: serializeActionResult(actionResult),
});
})));
context.cookies.set(ACTION_QUERY_PARAMS.actionPayload, cookieValue);

if (actionResult.error) {
const referer = context.request.headers.get('Referer');
Expand Down
21 changes: 17 additions & 4 deletions packages/astro/src/actions/runtime/virtual/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,14 +204,26 @@ export function serializeActionResult(res: SafeResult<any, any>): SerializedActi
if (import.meta.env?.DEV) {
actionResultErrorStack.set(res.error.stack);
}

let body: Record<string, any>;
if(res.error instanceof ActionInputError) {
body = {
type: res.error.type,
issues: res.error.issues,
fields: res.error.fields
};
} else {
body = {
...res.error,
message: res.error.message
};
}

return {
type: 'error',
status: res.error.status,
contentType: 'application/json',
body: JSON.stringify({
...res.error,
message: res.error.message,
}),
body: JSON.stringify(body),
};
}
if (res.data === undefined) {
Expand Down Expand Up @@ -252,6 +264,7 @@ export function deserializeActionResult(res: SerializedActionResult): SafeResult
let json;
try {
json = JSON.parse(res.body);

} catch {
return {
data: undefined,
Expand Down

0 comments on commit 837ee3a

Please sign in to comment.