|
import * as devalue from 'devalue'; |
|
import { json } from '../../../exports/index.js'; |
|
import { get_status, normalize_error } from '../../../utils/error.js'; |
|
import { is_form_content_type, negotiate } from '../../../utils/http.js'; |
|
import { HttpError, Redirect, ActionFailure, SvelteKitError } from '../../control.js'; |
|
import { handle_error_and_jsonify } from '../utils.js'; |
|
|
|
|
|
export function is_action_json_request(event) { |
|
const accept = negotiate(event.request.headers.get('accept') ?? '*/*', [ |
|
'application/json', |
|
'text/html' |
|
]); |
|
|
|
return accept === 'application/json' && event.request.method === 'POST'; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function handle_action_json_request(event, options, server) { |
|
const actions = server?.actions; |
|
|
|
if (!actions) { |
|
const no_actions_error = new SvelteKitError( |
|
405, |
|
'Method Not Allowed', |
|
'POST method not allowed. No actions exist for this page' |
|
); |
|
return action_json( |
|
{ |
|
type: 'error', |
|
error: await handle_error_and_jsonify(event, options, no_actions_error) |
|
}, |
|
{ |
|
status: no_actions_error.status, |
|
headers: { |
|
|
|
|
|
allow: 'GET' |
|
} |
|
} |
|
); |
|
} |
|
|
|
check_named_default_separate(actions); |
|
|
|
try { |
|
const data = await call_action(event, actions); |
|
|
|
if (__SVELTEKIT_DEV__) { |
|
validate_action_return(data); |
|
} |
|
|
|
if (data instanceof ActionFailure) { |
|
return action_json({ |
|
type: 'failure', |
|
status: data.status, |
|
|
|
|
|
|
|
data: stringify_action_response(data.data, (event.route.id)) |
|
}); |
|
} else { |
|
return action_json({ |
|
type: 'success', |
|
status: data ? 200 : 204, |
|
|
|
data: stringify_action_response(data, (event.route.id)) |
|
}); |
|
} |
|
} catch (e) { |
|
const err = normalize_error(e); |
|
|
|
if (err instanceof Redirect) { |
|
return action_json_redirect(err); |
|
} |
|
|
|
return action_json( |
|
{ |
|
type: 'error', |
|
error: await handle_error_and_jsonify(event, options, check_incorrect_fail_use(err)) |
|
}, |
|
{ |
|
status: get_status(err) |
|
} |
|
); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
function check_incorrect_fail_use(error) { |
|
return error instanceof ActionFailure |
|
? new Error('Cannot "throw fail()". Use "return fail()"') |
|
: error; |
|
} |
|
|
|
|
|
|
|
|
|
export function action_json_redirect(redirect) { |
|
return action_json({ |
|
type: 'redirect', |
|
status: redirect.status, |
|
location: redirect.location |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function action_json(data, init) { |
|
return json(data, init); |
|
} |
|
|
|
|
|
|
|
|
|
export function is_action_request(event) { |
|
return event.request.method === 'POST'; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function handle_action_request(event, server) { |
|
const actions = server?.actions; |
|
|
|
if (!actions) { |
|
|
|
event.setHeaders({ |
|
|
|
|
|
allow: 'GET' |
|
}); |
|
return { |
|
type: 'error', |
|
error: new SvelteKitError( |
|
405, |
|
'Method Not Allowed', |
|
'POST method not allowed. No actions exist for this page' |
|
) |
|
}; |
|
} |
|
|
|
check_named_default_separate(actions); |
|
|
|
try { |
|
const data = await call_action(event, actions); |
|
|
|
if (__SVELTEKIT_DEV__) { |
|
validate_action_return(data); |
|
} |
|
|
|
if (data instanceof ActionFailure) { |
|
return { |
|
type: 'failure', |
|
status: data.status, |
|
data: data.data |
|
}; |
|
} else { |
|
return { |
|
type: 'success', |
|
status: 200, |
|
|
|
data |
|
}; |
|
} |
|
} catch (e) { |
|
const err = normalize_error(e); |
|
|
|
if (err instanceof Redirect) { |
|
return { |
|
type: 'redirect', |
|
status: err.status, |
|
location: err.location |
|
}; |
|
} |
|
|
|
return { |
|
type: 'error', |
|
error: check_incorrect_fail_use(err) |
|
}; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
function check_named_default_separate(actions) { |
|
if (actions.default && Object.keys(actions).length > 1) { |
|
throw new Error( |
|
'When using named actions, the default action cannot be used. See the docs for more info: https://kit.svelte.dev/docs/form-actions#named-actions' |
|
); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
async function call_action(event, actions) { |
|
const url = new URL(event.request.url); |
|
|
|
let name = 'default'; |
|
for (const param of url.searchParams) { |
|
if (param[0].startsWith('/')) { |
|
name = param[0].slice(1); |
|
if (name === 'default') { |
|
throw new Error('Cannot use reserved action name "default"'); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
const action = actions[name]; |
|
if (!action) { |
|
throw new SvelteKitError(404, 'Not Found', `No action with name '${name}' found`); |
|
} |
|
|
|
if (!is_form_content_type(event.request)) { |
|
throw new SvelteKitError( |
|
415, |
|
'Unsupported Media Type', |
|
`Form actions expect form-encoded data — received ${event.request.headers.get( |
|
'content-type' |
|
)}` |
|
); |
|
} |
|
|
|
return action(event); |
|
} |
|
|
|
|
|
function validate_action_return(data) { |
|
if (data instanceof Redirect) { |
|
throw new Error('Cannot `return redirect(...)` — use `redirect(...)` instead'); |
|
} |
|
|
|
if (data instanceof HttpError) { |
|
throw new Error('Cannot `return error(...)` — use `error(...)` or `return fail(...)` instead'); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
export function uneval_action_response(data, route_id) { |
|
return try_deserialize(data, devalue.uneval, route_id); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function stringify_action_response(data, route_id) { |
|
return try_deserialize(data, devalue.stringify, route_id); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
function try_deserialize(data, fn, route_id) { |
|
try { |
|
return fn(data); |
|
} catch (e) { |
|
|
|
const error = (e); |
|
|
|
if ('path' in error) { |
|
let message = `Data returned from action inside ${route_id} is not serializable: ${error.message}`; |
|
if (error.path !== '') message += ` (data.${error.path})`; |
|
throw new Error(message); |
|
} |
|
|
|
throw error; |
|
} |
|
} |
|
|