forked from UKSOURCE/cms.hailearning.edu.vn
feat: standardize admin form limits and guidance
This commit is contained in:
158
utils/lengthValidation.js
Normal file
158
utils/lengthValidation.js
Normal file
@@ -0,0 +1,158 @@
|
||||
const DEFAULT_LABEL = "Field";
|
||||
|
||||
const normalizePath = (path) => {
|
||||
return String(path || "")
|
||||
.replace(/\[(\d+)\]/g, ".$1")
|
||||
.replace(/\[\*\]/g, ".*")
|
||||
.replace(/\[\]/g, ".*")
|
||||
.split(".")
|
||||
.filter(Boolean);
|
||||
};
|
||||
|
||||
const toLabel = (path) => {
|
||||
if (!path) {
|
||||
return DEFAULT_LABEL;
|
||||
}
|
||||
|
||||
return String(path)
|
||||
.replace(/\[\d+\]/g, "")
|
||||
.replace(/\.\*/g, "")
|
||||
.replace(/[._-]+/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim()
|
||||
.replace(/^./, (ch) => ch.toUpperCase());
|
||||
};
|
||||
|
||||
const collectMatches = (value, segments, currentPath = [], results = []) => {
|
||||
if (segments.length === 0) {
|
||||
results.push({
|
||||
path: currentPath.join("."),
|
||||
value,
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
const [segment, ...rest] = segments;
|
||||
|
||||
if (segment === "*") {
|
||||
if (!Array.isArray(value)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
value.forEach((item, index) => {
|
||||
collectMatches(item, rest, [...currentPath, String(index)], results);
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
return results;
|
||||
}
|
||||
|
||||
if (typeof value !== "object") {
|
||||
return results;
|
||||
}
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(value, segment)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
return collectMatches(value[segment], rest, [...currentPath, segment], results);
|
||||
};
|
||||
|
||||
const countWords = (value) => {
|
||||
const normalized = String(value || "")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
if (!normalized) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return normalized.split(" ").length;
|
||||
};
|
||||
|
||||
const buildErrorMessage = (label, path, maxLength, maxWords) => {
|
||||
const target = path ? `${label} (${path})` : label;
|
||||
|
||||
if (maxLength && maxWords) {
|
||||
return `${target} must not exceed ${maxLength} characters or ${maxWords} words.`;
|
||||
}
|
||||
|
||||
if (maxLength) {
|
||||
return `${target} must not exceed ${maxLength} characters.`;
|
||||
}
|
||||
|
||||
return `${target} must not exceed ${maxWords} words.`;
|
||||
};
|
||||
|
||||
const validateLengthRules = (payload, rules = []) => {
|
||||
const errors = [];
|
||||
|
||||
for (const rule of rules) {
|
||||
const paths = Array.isArray(rule.paths)
|
||||
? rule.paths
|
||||
: [rule.path].filter(Boolean);
|
||||
|
||||
for (const path of paths) {
|
||||
const segments = normalizePath(path);
|
||||
const matches = collectMatches(payload, segments);
|
||||
|
||||
for (const match of matches) {
|
||||
if (typeof match.value !== "string") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const normalized = match.value.trim();
|
||||
if (!normalized && rule.allowEmpty !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const actualLength = normalized.length;
|
||||
const actualWords = countWords(normalized);
|
||||
const maxLength = Number(rule.maxLength);
|
||||
const maxWords = Number(rule.maxWords);
|
||||
const exceedsLength = Number.isFinite(maxLength) && maxLength > 0 && actualLength > maxLength;
|
||||
const exceedsWords = Number.isFinite(maxWords) && maxWords > 0 && actualWords > maxWords;
|
||||
|
||||
if (!exceedsLength && !exceedsWords) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const label = rule.label || toLabel(path);
|
||||
errors.push({
|
||||
path: match.path,
|
||||
label,
|
||||
message: buildErrorMessage(label, match.path, exceedsLength ? maxLength : null, exceedsWords ? maxWords : null),
|
||||
maxLength: Number.isFinite(maxLength) ? maxLength : undefined,
|
||||
maxWords: Number.isFinite(maxWords) ? maxWords : undefined,
|
||||
actualLength,
|
||||
actualWords,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors,
|
||||
};
|
||||
};
|
||||
|
||||
const summarizeLengthErrors = (validation, limit = 1) => {
|
||||
if (!validation || !Array.isArray(validation.errors) || validation.errors.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return validation.errors
|
||||
.slice(0, limit)
|
||||
.map((error) => error.message)
|
||||
.join(" ");
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
validateLengthRules,
|
||||
summarizeLengthErrors,
|
||||
toLabel,
|
||||
};
|
||||
Reference in New Issue
Block a user