Skip to main content
Glama
validation.js11.3 kB
/** * 验证工具模块 * 提供参数验证和数据验证功能 */ import { logger } from './logger.js'; /** * 验证结果类型 * @typedef {Object} ValidationResult * @property {boolean} valid - 是否验证通过 * @property {string[]} errors - 错误信息列表 */ /** * 参数验证器 * @param {Object} params - 要验证的参数对象 * @param {Object} schema - 验证模式 * @returns {ValidationResult} 验证结果 */ export function validateParams(params, schema) { const errors = []; try { // 遍历验证模式 for (const [field, rules] of Object.entries(schema)) { const value = params[field]; // 检查必填字段 if (rules.required && (value === undefined || value === null || value === '')) { errors.push(`${field} 是必填字段`); continue; } // 如果字段不存在且不是必填的,跳过验证 if (!rules.required && (value === undefined || value === null)) { continue; } // 类型验证 if (rules.type) { const typeValidation = validateType(value, rules.type, field); if (!typeValidation.valid) { errors.push(...typeValidation.errors); continue; } } // 数值范围验证 if (rules.min !== undefined && typeof value === 'number') { if (value < rules.min) { errors.push(`${field} 必须大于等于 ${rules.min}`); } } if (rules.max !== undefined && typeof value === 'number') { if (value > rules.max) { errors.push(`${field} 必须小于等于 ${rules.max}`); } } // 字符串长度验证 if (rules.min !== undefined && typeof value === 'string') { if (value.length < rules.min) { errors.push(`${field} 长度必须大于等于 ${rules.min}`); } } if (rules.max !== undefined && typeof value === 'string') { if (value.length > rules.max) { errors.push(`${field} 长度必须小于等于 ${rules.max}`); } } // 数组长度验证 if (rules.min !== undefined && Array.isArray(value)) { if (value.length < rules.min) { errors.push(`${field} 必须包含至少 ${rules.min} 个元素`); } } if (rules.max !== undefined && Array.isArray(value)) { if (value.length > rules.max) { errors.push(`${field} 最多包含 ${rules.max} 个元素`); } } // 枚举值验证 if (rules.enum && Array.isArray(rules.enum)) { if (!rules.enum.includes(value)) { errors.push(`${field} 必须是以下值之一: ${rules.enum.join(', ')}`); } } // 正则表达式验证 if (rules.pattern && rules.pattern instanceof RegExp) { if (!rules.pattern.test(value)) { errors.push(`${field} 格式不正确`); } } // 自定义验证函数 if (rules.validate && typeof rules.validate === 'function') { const customResult = rules.validate(value, params); if (customResult !== true) { errors.push(typeof customResult === 'string' ? customResult : `${field} 验证失败`); } } } // 检查未知字段 const knownFields = Object.keys(schema); const unknownFields = Object.keys(params).filter(field => !knownFields.includes(field)); if (unknownFields.length > 0) { errors.push(`发现未知字段: ${unknownFields.join(', ')}`); } return { valid: errors.length === 0, errors }; } catch (error) { logger.error('参数验证失败:', error); return { valid: false, errors: ['参数验证过程出错'] }; } } /** * 类型验证 * @param {*} value - 要验证的值 * @param {string} expectedType - 期望的类型 * @param {string} fieldName - 字段名称 * @returns {ValidationResult} 验证结果 */ function validateType(value, expectedType, fieldName) { const errors = []; switch (expectedType) { case 'string': if (typeof value !== 'string') { errors.push(`${fieldName} 必须是字符串类型`); } break; case 'number': if (typeof value !== 'number' || isNaN(value)) { errors.push(`${fieldName} 必须是数字类型`); } break; case 'boolean': if (typeof value !== 'boolean') { errors.push(`${fieldName} 必须是布尔类型`); } break; case 'array': if (!Array.isArray(value)) { errors.push(`${fieldName} 必须是数组类型`); } break; case 'object': if (typeof value !== 'object' || value === null || Array.isArray(value)) { errors.push(`${fieldName} 必须是对象类型`); } break; case 'date': if (!(value instanceof Date) && isNaN(new Date(value))) { errors.push(`${fieldName} 必须是有效的日期`); } break; case 'email': if (typeof value !== 'string' || !isValidEmail(value)) { errors.push(`${fieldName} 必须是有效的邮箱地址`); } break; case 'url': if (typeof value !== 'string' || !isValidUrl(value)) { errors.push(`${fieldName} 必须是有效的URL`); } break; case 'uuid': if (typeof value !== 'string' || !isValidUUID(value)) { errors.push(`${fieldName} 必须是有效的UUID`); } break; default: logger.warn(`未知的验证类型: ${expectedType}`); } return { valid: errors.length === 0, errors }; } /** * 验证邮箱地址 * @param {string} email - 邮箱地址 * @returns {boolean} 是否有效 */ function isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } /** * 验证URL * @param {string} url - URL地址 * @returns {boolean} 是否有效 */ function isValidUrl(url) { try { new URL(url); return true; } catch { return false; } } /** * 验证UUID * @param {string} uuid - UUID字符串 * @returns {boolean} 是否有效 */ function isValidUUID(uuid) { const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; return uuidRegex.test(uuid); } /** * 数据清洗器 * @param {Object} data - 要清洗的数据 * @param {Object} schema - 清洗模式 * @returns {Object} 清洗后的数据 */ export function sanitizeData(data, schema) { const sanitized = {}; try { for (const [field, rules] of Object.entries(schema)) { if (data[field] === undefined || data[field] === null) { if (rules.default !== undefined) { sanitized[field] = rules.default; } continue; } let value = data[field]; // 类型转换 if (rules.type) { value = convertType(value, rules.type); } // 字符串清洗 if (typeof value === 'string' && rules.trim) { value = value.trim(); } if (typeof value === 'string' && rules.lowercase) { value = value.toLowerCase(); } if (typeof value === 'string' && rules.uppercase) { value = value.toUpperCase(); } // 数组清洗 if (Array.isArray(value) && rules.unique) { value = [...new Set(value)]; } if (Array.isArray(value) && rules.sort) { value = value.sort(); } sanitized[field] = value; } return sanitized; } catch (error) { logger.error('数据清洗失败:', error); return {}; } } /** * 类型转换 * @param {*} value - 要转换的值 * @param {string} targetType - 目标类型 * @returns {*} 转换后的值 */ function convertType(value, targetType) { try { switch (targetType) { case 'string': return String(value); case 'number': const num = Number(value); return isNaN(num) ? 0 : num; case 'boolean': if (typeof value === 'string') { return value.toLowerCase() === 'true' || value === '1'; } return Boolean(value); case 'array': if (Array.isArray(value)) { return value; } if (typeof value === 'string') { try { return JSON.parse(value); } catch { return [value]; } } return [value]; case 'object': if (typeof value === 'string') { try { return JSON.parse(value); } catch { return {}; } } return typeof value === 'object' && value !== null ? value : {}; case 'date': return new Date(value); default: return value; } } catch (error) { logger.error(`类型转换失败: ${value} -> ${targetType}`, error); return value; } } /** * 批量验证器 * @param {Array<Object>} items - 要验证的项目数组 * @param {Object} schema - 验证模式 * @returns {Object} 批量验证结果 */ export function validateBatch(items, schema) { const results = []; let allValid = true; try { for (let i = 0; i < items.length; i++) { const item = items[i]; const validation = validateParams(item, schema); results.push({ index: i, data: item, valid: validation.valid, errors: validation.errors }); if (!validation.valid) { allValid = false; } } return { allValid, results, validCount: results.filter(r => r.valid).length, invalidCount: results.filter(r => !r.valid).length }; } catch (error) { logger.error('批量验证失败:', error); return { allValid: false, results: [], validCount: 0, invalidCount: items.length }; } } /** * 异步验证器 * @param {Object} params - 要验证的参数 * @param {Object} schema - 验证模式 * @returns {Promise<ValidationResult>} 验证结果 */ export async function validateParamsAsync(params, schema) { try { const syncResult = validateParams(params, schema); if (!syncResult.valid) { return syncResult; } // 执行异步验证 const asyncErrors = []; for (const [field, rules] of Object.entries(schema)) { if (rules.asyncValidate && typeof rules.asyncValidate === 'function') { try { const asyncResult = await rules.asyncValidate(params[field], params); if (asyncResult !== true) { asyncErrors.push(typeof asyncResult === 'string' ? asyncResult : `${field} 异步验证失败`); } } catch (error) { logger.error(`异步验证失败: ${field}`, error); asyncErrors.push(`${field} 异步验证出错`); } } } return { valid: asyncErrors.length === 0, errors: [...syncResult.errors, ...asyncErrors] }; } catch (error) { logger.error('异步参数验证失败:', error); return { valid: false, errors: ['异步验证过程出错'] }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/billyangbc/xiaohongshu-mcp-nodejs'

If you have feedback or need assistance with the MCP directory API, please join our Discord server