Skip to content

Commit

Permalink
Actions: support empty args and empty response (#11041)
Browse files Browse the repository at this point in the history
* feat: support empty args and empty response

* chore: changeset

* Update .changeset/many-guests-yell.md

Co-authored-by: Florian Lefebvre <[email protected]>

---------

Co-authored-by: Florian Lefebvre <[email protected]>
  • Loading branch information
bholmesdev and florian-lefebvre authored May 15, 2024
1 parent d0d1710 commit 6cc3fb9
Show file tree
Hide file tree
Showing 7 changed files with 39 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/many-guests-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"astro": patch
---

Fixes 500 errors when sending empty params or returning an empty response from an action.
2 changes: 2 additions & 0 deletions packages/astro/src/actions/runtime/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export const onRequest = defineMiddleware(async (context, next) => {

const actionPathKeys = actionPath.replace('/_actions/', '').split('.');
const action = await getAction(actionPathKeys);
if (!action) return nextWithLocalsStub(next, locals);

const result = await ApiContextStorage.run(context, () => callSafely(() => action(formData)));

const actionsInternal: Locals['_actionsInternal'] = {
Expand Down
9 changes: 8 additions & 1 deletion packages/astro/src/actions/runtime/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ export const POST: APIRoute = async (context) => {
const { request, url } = context;
const actionPathKeys = url.pathname.replace('/_actions/', '').split('.');
const action = await getAction(actionPathKeys);
if (!action) {
return new Response(null, { status: 404 });
}
const contentType = request.headers.get('Content-Type');
const contentLength = request.headers.get('Content-Length');
let args: unknown;
if (contentType && hasContentType(contentType, formContentTypes)) {
if (contentLength === '0') {
args = undefined;
} else if (contentType && hasContentType(contentType, formContentTypes)) {
args = await request.clone().formData();
} else if (contentType && hasContentType(contentType, ['application/json'])) {
args = await request.clone().json();
Expand All @@ -35,6 +41,7 @@ export const POST: APIRoute = async (context) => {
);
}
return new Response(JSON.stringify(result.data), {
status: result.data ? 200 : 204,
headers: {
'Content-Type': 'application/json',
},
Expand Down
6 changes: 3 additions & 3 deletions packages/astro/src/actions/runtime/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@ export type MaybePromise<T> = T | Promise<T>;

export async function getAction(
pathKeys: string[]
): Promise<(param: unknown) => MaybePromise<unknown>> {
): Promise<((param: unknown) => MaybePromise<unknown>) | undefined> {
let { server: actionLookup } = await import(import.meta.env.ACTIONS_PATH);
for (const key of pathKeys) {
if (!(key in actionLookup)) {
throw new Error('Action not found');
return undefined;
}
actionLookup = actionLookup[key];
}
if (typeof actionLookup !== 'function') {
throw new Error('Action not found');
return undefined;
}
return actionLookup;
}
4 changes: 4 additions & 0 deletions packages/astro/templates/actions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ async function actionHandler(clientParam, path) {
});
}
headers.set('Content-Type', 'application/json');
headers.set('Content-Length', body?.length.toString() ?? '0');
}
const res = await fetch(path, {
method: 'POST',
Expand All @@ -54,6 +55,9 @@ async function actionHandler(clientParam, path) {
if (!res.ok) {
throw await ActionError.fromResponse(res);
}
// Check if response body is empty before parsing.
if (res.status === 204) return;

const json = await res.json();
return json;
}
Expand Down
12 changes: 12 additions & 0 deletions packages/astro/test/actions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,5 +202,17 @@ describe('Astro Actions', () => {
assert.equal($('#error-message').text(), 'Not logged in');
assert.equal($('#error-code').text(), 'UNAUTHORIZED');
});

it('Sets status to 204 when no content', async () => {
const req = new Request('http://example.com/_actions/fireAndForget', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': '0',
},
});
const res = await app.render(req);
assert.equal(res.status, 204);
});
});
});
5 changes: 5 additions & 0 deletions packages/astro/test/fixtures/actions/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,9 @@ export const server = {
return locals.user;
}
}),
fireAndForget: defineAction({
handler: async () => {
return;
}
}),
};

0 comments on commit 6cc3fb9

Please sign in to comment.