mirror of
https://github.com/withastro/astro.git
synced 2025-01-27 22:19:04 -05:00
Actions: support empty args and empty response (#11041)
* feat: support empty args and empty response * chore: changeset * Update .changeset/many-guests-yell.md Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev> --------- Co-authored-by: Florian Lefebvre <contact@florian-lefebvre.dev>
This commit is contained in:
parent
d0d1710439
commit
6cc3fb97ec
7 changed files with 39 additions and 4 deletions
5
.changeset/many-guests-yell.md
Normal file
5
.changeset/many-guests-yell.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"astro": patch
|
||||
---
|
||||
|
||||
Fixes 500 errors when sending empty params or returning an empty response from an action.
|
|
@ -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'] = {
|
||||
|
|
|
@ -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();
|
||||
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -50,4 +50,9 @@ export const server = {
|
|||
return locals.user;
|
||||
}
|
||||
}),
|
||||
fireAndForget: defineAction({
|
||||
handler: async () => {
|
||||
return;
|
||||
}
|
||||
}),
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue