sveltekit-superforms
Version:
Making SvelteKit forms a pleasure to use!
148 lines (147 loc) • 5.5 kB
JavaScript
import { traversePath } from './traversal.js';
import { fail as kitFail } from '@sveltejs/kit';
import {} from './adapters/adapters.js';
import { parseRequest } from './formData.js';
import { splitPath } from './stringPath.js';
import { mapErrors, mergeDefaults, replaceInvalidDefaults } from './errors.js';
/**
* Validates a schema for data validation and usage in superForm.
* @param data Data corresponding to a schema, or RequestEvent/FormData/URL. If falsy, the schema's default values will be used.
* @param schema The schema to validate against.
*/
export async function superValidate(data, adapter, options) {
if (data && 'superFormValidationLibrary' in data) {
options = adapter;
adapter = data;
data = undefined;
}
const validator = adapter;
const defaults = options?.defaults ?? validator.defaults;
const jsonSchema = validator.jsonSchema;
const parsed = await parseRequest(data, jsonSchema, options);
const addErrors = options?.errors ?? (options?.strict ? true : !!parsed.data);
// Merge with defaults in non-strict mode.
const parsedData = options?.strict ? (parsed.data ?? {}) : mergeDefaults(parsed.data, defaults);
let status;
if (!!parsed.data || addErrors) {
status = await /* @__PURE__ */ validator.validate(parsedData);
}
else {
status = { success: false, issues: [] };
}
const valid = status.success;
const errors = valid || !addErrors ? {} : mapErrors(status.issues, validator.shape);
// Final data should always have defaults, to ensure type safety
//const dataWithDefaults = { ...defaults, ...(valid ? status.data : parsedData) };
const dataWithDefaults = valid
? status.data
: replaceInvalidDefaults(options?.strict ? mergeDefaults(parsedData, defaults) : parsedData, defaults, jsonSchema, status.issues, options?.preprocessed);
let outputData;
if (jsonSchema.additionalProperties === false) {
// Strip keys not belonging to schema
outputData = {};
for (const key of Object.keys(jsonSchema.properties ?? {})) {
if (key in dataWithDefaults)
outputData[key] = dataWithDefaults[key];
}
}
else {
outputData = dataWithDefaults;
}
const output = {
id: parsed.id ?? options?.id ?? validator.id,
valid,
posted: parsed.posted,
errors: errors,
data: outputData
};
if (!parsed.posted) {
output.constraints = validator.constraints;
if (Object.keys(validator.shape).length) {
output.shape = validator.shape;
}
}
return output;
}
/////////////////////////////////////////////////////////////////////
/**
* Sends a message with a form, with an optional HTTP status code that will set
* form.valid to false if status >= 400. A status lower than 400 cannot be sent.
*/
export function message(form, message, options) {
if (options?.status && options.status >= 400) {
form.valid = false;
}
form.message = message;
const remove = options?.removeFiles !== false;
const output = remove ? withFiles({ form }) : { form };
return form.valid ? output : kitFail(options?.status ?? 400, output);
}
export const setMessage = message;
export function setError(form, path, error, options) {
// Unify signatures
if (error == undefined || (typeof error !== 'string' && !Array.isArray(error))) {
options = error;
error = path;
path = '';
}
if (options === undefined)
options = {};
const errArr = Array.isArray(error) ? error : [error];
if (!form.errors)
form.errors = {};
if (path === null || path === '') {
if (!form.errors._errors)
form.errors._errors = [];
form.errors._errors = options.overwrite ? errArr : form.errors._errors.concat(errArr);
}
else {
const realPath = splitPath(path);
const leaf = traversePath(form.errors, realPath, ({ parent, key, value }) => {
if (value === undefined)
parent[key] = {};
return parent[key];
});
if (leaf) {
leaf.parent[leaf.key] =
Array.isArray(leaf.value) && !options.overwrite ? leaf.value.concat(errArr) : errArr;
}
}
form.valid = false;
const output = options.removeFiles === false ? { form } : withFiles({ form });
return kitFail(options.status ?? 400, output);
}
export function withFiles(obj) {
if (typeof obj !== 'object')
return obj;
for (const key in obj) {
const value = obj[key];
if (value instanceof File)
delete obj[key];
else if (value && typeof value === 'object')
withFiles(value);
}
return obj;
}
export const removeFiles = withFiles;
export function fail(status, data) {
function checkForm(data) {
return !!data && typeof data === 'object' && 'valid' in data && 'data' in data && 'id' in data;
}
function checkObj(data) {
if (data && typeof data === 'object') {
for (const key in data) {
const v = data[key];
if (checkForm(v)) {
v.valid = false;
removeFiles(v);
}
else if (v && typeof v === 'object') {
checkObj(v);
}
}
}
return data;
}
return kitFail(status, checkObj(data));
}