UNPKG

@gebrai/gebrai

Version:

Model Context Protocol server for GeoGebra mathematical visualization

557 lines 22 kB
"use strict"; /** * Validation utilities for GeoGebra MCP tools */ Object.defineProperty(exports, "__esModule", { value: true }); exports.validatePointName = validatePointName; exports.validateObjectName = validateObjectName; exports.validateCoordinates = validateCoordinates; exports.validateRadius = validateRadius; exports.validatePolygonVertices = validatePolygonVertices; exports.validateLinearEquation = validateLinearEquation; exports.validateRequiredParams = validateRequiredParams; exports.validateFunctionExpression = validateFunctionExpression; exports.validateParametricExpressions = validateParametricExpressions; exports.validateImplicitExpression = validateImplicitExpression; exports.validateDomainRange = validateDomainRange; exports.validateFunctionStyling = validateFunctionStyling; exports.validateAlgebraicExpression = validateAlgebraicExpression; exports.validateEquation = validateEquation; exports.validateVariableName = validateVariableName; exports.validateSystemOfEquations = validateSystemOfEquations; exports.validateVariablesList = validateVariablesList; exports.validateSliderParameters = validateSliderParameters; exports.validateAnimationSpeed = validateAnimationSpeed; exports.validateAnimationDirection = validateAnimationDirection; exports.validateAnimationExportParameters = validateAnimationExportParameters; /** * Validate point name (GeoGebra naming conventions) */ function validatePointName(name) { if (!name || typeof name !== 'string') { return { isValid: false, error: 'Point name must be a non-empty string' }; } if (name.length === 0) { return { isValid: false, error: 'Point name cannot be empty' }; } // GeoGebra naming rules: start with letter, can contain letters, numbers, underscore const namePattern = /^[a-zA-Z][a-zA-Z0-9_]*$/; if (!namePattern.test(name)) { return { isValid: false, error: 'Point name must start with a letter and contain only letters, numbers, and underscores' }; } return { isValid: true }; } /** * Validate object name (general GeoGebra naming conventions) */ function validateObjectName(name) { return validatePointName(name); // Same rules apply } /** * Validate coordinates */ function validateCoordinates(x, y) { if (typeof x !== 'number' || typeof y !== 'number') { return { isValid: false, error: 'Coordinates must be numbers' }; } if (!isFinite(x) || !isFinite(y)) { return { isValid: false, error: 'Coordinates must be finite numbers' }; } // Reasonable coordinate range for visualization const maxCoord = 1000000; if (Math.abs(x) > maxCoord || Math.abs(y) > maxCoord) { return { isValid: false, error: `Coordinates must be within range [-${maxCoord}, ${maxCoord}]` }; } return { isValid: true }; } /** * Validate radius */ function validateRadius(radius) { if (typeof radius !== 'number') { return { isValid: false, error: 'Radius must be a number' }; } if (!isFinite(radius)) { return { isValid: false, error: 'Radius must be a finite number' }; } if (radius < 0) { return { isValid: false, error: 'Radius must be non-negative' }; } if (radius > 1000000) { return { isValid: false, error: 'Radius must be reasonable (≤ 1,000,000)' }; } return { isValid: true }; } /** * Validate polygon vertices */ function validatePolygonVertices(vertices) { if (!Array.isArray(vertices)) { return { isValid: false, error: 'Vertices must be an array' }; } if (vertices.length < 3) { return { isValid: false, error: 'Polygon must have at least 3 vertices' }; } if (vertices.length > 100) { return { isValid: false, error: 'Polygon cannot have more than 100 vertices' }; } // Check for duplicate vertices const uniqueVertices = new Set(vertices); if (uniqueVertices.size !== vertices.length) { return { isValid: false, error: 'Polygon vertices must be unique' }; } // Validate each vertex name for (let i = 0; i < vertices.length; i++) { const vertex = vertices[i]; if (!vertex) { return { isValid: false, error: `Vertex ${i + 1} is undefined or empty` }; } const validation = validatePointName(vertex); if (!validation.isValid) { return { isValid: false, error: `Vertex ${i + 1}: ${validation.error}` }; } } return { isValid: true }; } /** * Validate linear equation format */ function validateLinearEquation(equation) { if (!equation || typeof equation !== 'string') { return { isValid: false, error: 'Equation must be a non-empty string' }; } if (!equation.includes('=')) { return { isValid: false, error: 'Equation must contain an equals sign (=)' }; } // Basic validation for common linear equation formats const patterns = [ /^y\s*=\s*[+-]?\s*\d*\.?\d*\s*\*?\s*x\s*[+-]\s*\d+\.?\d*$/, // y = mx + b /^y\s*=\s*[+-]?\s*\d+\.?\d*\s*\*?\s*x$/, // y = mx /^y\s*=\s*[+-]?\s*\d+\.?\d*$/, // y = b (horizontal line) /^x\s*=\s*[+-]?\s*\d+\.?\d*$/, // x = a (vertical line) /^[+-]?\s*\d*\.?\d*\s*\*?\s*x\s*[+-]\s*\d*\.?\d*\s*\*?\s*y\s*=\s*[+-]?\s*\d+\.?\d*$/, // ax + by = c ]; const normalizedEq = equation.replace(/\s+/g, ' ').trim(); const isValidFormat = patterns.some(pattern => pattern.test(normalizedEq)); if (!isValidFormat) { return { isValid: false, error: 'Equation format not recognized. Use formats like "y = 2x + 3", "x = 5", or "2x + 3y = 6"' }; } return { isValid: true }; } /** * Validate that required parameters are provided for a given method */ function validateRequiredParams(params, required) { for (const param of required) { if (!(param in params) || params[param] === undefined || params[param] === null) { return { isValid: false, error: `Missing required parameter: ${param}` }; } } return { isValid: true }; } /** * Validate function expression for standard functions f(x) = ... */ function validateFunctionExpression(expression) { if (!expression || typeof expression !== 'string') { return { isValid: false, error: 'Function expression must be a non-empty string' }; } const trimmed = expression.trim(); if (trimmed.length === 0) { return { isValid: false, error: 'Function expression cannot be empty' }; } // Basic validation for function expressions // Allow common mathematical functions and operators const allowedPattern = /^[x\d\+\-\*\/\^\(\)\.\s,sincotaglnbsqrtepie]*$/i; if (!allowedPattern.test(trimmed)) { return { isValid: false, error: 'Function expression contains invalid characters. Use only x, numbers, +, -, *, /, ^, (), sin, cos, tan, log, ln, sqrt, abs, e, pi' }; } // Check for balanced parentheses let parenCount = 0; for (const char of trimmed) { if (char === '(') parenCount++; if (char === ')') parenCount--; if (parenCount < 0) { return { isValid: false, error: 'Unbalanced parentheses in function expression' }; } } if (parenCount !== 0) { return { isValid: false, error: 'Unbalanced parentheses in function expression' }; } return { isValid: true }; } /** * Validate parametric function expressions */ function validateParametricExpressions(xExpr, yExpr, parameter) { if (!parameter || typeof parameter !== 'string') { return { isValid: false, error: 'Parameter name must be a non-empty string' }; } // Validate parameter name (typically t, u, s, etc.) const paramPattern = /^[a-zA-Z][a-zA-Z0-9]*$/; if (!paramPattern.test(parameter)) { return { isValid: false, error: 'Parameter name must start with a letter and contain only letters and numbers' }; } // Validate x expression const xValidation = validateParametricExpression(xExpr, parameter); if (!xValidation.isValid) { return { isValid: false, error: `X expression: ${xValidation.error}` }; } // Validate y expression const yValidation = validateParametricExpression(yExpr, parameter); if (!yValidation.isValid) { return { isValid: false, error: `Y expression: ${yValidation.error}` }; } return { isValid: true }; } /** * Validate a single parametric expression */ function validateParametricExpression(expression, parameter) { if (!expression || typeof expression !== 'string') { return { isValid: false, error: 'Expression must be a non-empty string' }; } const trimmed = expression.trim(); if (trimmed.length === 0) { return { isValid: false, error: 'Expression cannot be empty' }; } // Allow the parameter variable and common mathematical functions const escapedParam = parameter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const allowedPattern = new RegExp(`^[${escapedParam}\\d\\+\\-\\*\\/\\^\\(\\)\\.\\s,sincotaglnbsqrtepie]*$`, 'i'); if (!allowedPattern.test(trimmed)) { return { isValid: false, error: `Expression contains invalid characters. Use only ${parameter}, numbers, +, -, *, /, ^, (), sin, cos, tan, log, ln, sqrt, abs, e, pi` }; } // Check for balanced parentheses let parenCount = 0; for (const char of trimmed) { if (char === '(') parenCount++; if (char === ')') parenCount--; if (parenCount < 0) { return { isValid: false, error: 'Unbalanced parentheses in expression' }; } } if (parenCount !== 0) { return { isValid: false, error: 'Unbalanced parentheses in expression' }; } return { isValid: true }; } /** * Validate implicit function expression */ function validateImplicitExpression(expression) { if (!expression || typeof expression !== 'string') { return { isValid: false, error: 'Implicit expression must be a non-empty string' }; } const trimmed = expression.trim(); if (trimmed.length === 0) { return { isValid: false, error: 'Implicit expression cannot be empty' }; } // Must contain both x and y variables if (!trimmed.includes('x') || !trimmed.includes('y')) { return { isValid: false, error: 'Implicit expression must contain both x and y variables' }; } // Allow x, y, numbers, and mathematical functions const allowedPattern = /^[xy\d\+\-\*\/\^\(\)\.\s,sincotaglnbsqrtepie=]*$/i; if (!allowedPattern.test(trimmed)) { return { isValid: false, error: 'Implicit expression contains invalid characters. Use only x, y, numbers, +, -, *, /, ^, (), sin, cos, tan, log, ln, sqrt, abs, e, pi, =' }; } // Check for balanced parentheses let parenCount = 0; for (const char of trimmed) { if (char === '(') parenCount++; if (char === ')') parenCount--; if (parenCount < 0) { return { isValid: false, error: 'Unbalanced parentheses in implicit expression' }; } } if (parenCount !== 0) { return { isValid: false, error: 'Unbalanced parentheses in implicit expression' }; } return { isValid: true }; } /** * Validate domain range parameters */ function validateDomainRange(min, max, paramName = 'parameter') { if (typeof min !== 'number' || typeof max !== 'number') { return { isValid: false, error: `${paramName} range bounds must be numbers` }; } if (!isFinite(min) || !isFinite(max)) { return { isValid: false, error: `${paramName} range bounds must be finite numbers` }; } if (min >= max) { return { isValid: false, error: `${paramName} minimum must be less than maximum` }; } // Reasonable range limits const maxRange = 1000000; if (Math.abs(min) > maxRange || Math.abs(max) > maxRange) { return { isValid: false, error: `${paramName} range must be within [-${maxRange}, ${maxRange}]` }; } const rangeDiff = max - min; if (rangeDiff < 0.001) { return { isValid: false, error: `${paramName} range must be at least 0.001 units wide` }; } if (rangeDiff > maxRange * 2) { return { isValid: false, error: `${paramName} range cannot exceed ${maxRange * 2} units wide` }; } return { isValid: true }; } /** * Validate function styling parameters */ function validateFunctionStyling(color, thickness, style) { if (color !== undefined) { if (typeof color !== 'string') { return { isValid: false, error: 'Color must be a string' }; } // Basic color validation (hex, named colors, rgb) const colorPattern = /^(#[0-9A-Fa-f]{6}|#[0-9A-Fa-f]{3}|rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)|[a-zA-Z]+)$/; if (!colorPattern.test(color.trim())) { return { isValid: false, error: 'Color must be a valid hex color (#RRGGBB), RGB value (rgb(r,g,b)), or color name' }; } } if (thickness !== undefined) { if (typeof thickness !== 'number') { return { isValid: false, error: 'Line thickness must be a number' }; } if (!isFinite(thickness)) { return { isValid: false, error: 'Line thickness must be a finite number' }; } if (thickness < 1 || thickness > 10) { return { isValid: false, error: 'Line thickness must be between 1 and 10' }; } } if (style !== undefined) { if (typeof style !== 'string') { return { isValid: false, error: 'Line style must be a string' }; } const validStyles = ['solid', 'dashed', 'dotted']; if (!validStyles.includes(style.toLowerCase())) { return { isValid: false, error: `Line style must be one of: ${validStyles.join(', ')}` }; } } return { isValid: true }; } /** * Validate algebraic expression for CAS operations */ function validateAlgebraicExpression(expression) { if (!expression || typeof expression !== 'string') { return { isValid: false, error: 'Expression must be a non-empty string' }; } const trimmed = expression.trim(); if (trimmed.length === 0) { return { isValid: false, error: 'Expression cannot be empty' }; } // Allow common mathematical symbols, variables, and functions const allowedPattern = /^[a-zA-Z\d\+\-\*\/\^\(\)\.\s,=]*$/; if (!allowedPattern.test(trimmed)) { return { isValid: false, error: 'Expression contains invalid characters. Use only variables, numbers, +, -, *, /, ^, =, ()' }; } // Check for balanced parentheses let parenCount = 0; for (const char of trimmed) { if (char === '(') parenCount++; if (char === ')') parenCount--; if (parenCount < 0) { return { isValid: false, error: 'Unbalanced parentheses in expression' }; } } if (parenCount !== 0) { return { isValid: false, error: 'Unbalanced parentheses in expression' }; } return { isValid: true }; } /** * Validate equation format for solving */ function validateEquation(equation) { if (!equation || typeof equation !== 'string') { return { isValid: false, error: 'Equation must be a non-empty string' }; } const trimmed = equation.trim(); if (trimmed.length === 0) { return { isValid: false, error: 'Equation cannot be empty' }; } // Check if equation contains exactly one equals sign const equalsSigns = (trimmed.match(/=/g) || []).length; if (equalsSigns === 0) { return { isValid: false, error: 'Equation must contain an equals sign (=)' }; } if (equalsSigns > 1) { return { isValid: false, error: 'Equation cannot contain multiple equals signs' }; } // Validate both sides of the equation const parts = trimmed.split('='); const leftSide = parts[0]?.trim() || ''; const rightSide = parts[1]?.trim() || ''; const leftValidation = validateAlgebraicExpression(leftSide); if (!leftValidation.isValid) { return { isValid: false, error: `Left side of equation: ${leftValidation.error}` }; } const rightValidation = validateAlgebraicExpression(rightSide); if (!rightValidation.isValid) { return { isValid: false, error: `Right side of equation: ${rightValidation.error}` }; } return { isValid: true }; } /** * Validate variable name for CAS operations */ function validateVariableName(variable) { if (!variable || typeof variable !== 'string') { return { isValid: false, error: 'Variable name must be a non-empty string' }; } const trimmed = variable.trim(); if (trimmed.length === 0) { return { isValid: false, error: 'Variable name cannot be empty' }; } // Variable must be a single letter or letter followed by digits/underscores const variablePattern = /^[a-zA-Z][a-zA-Z0-9_]*$/; if (!variablePattern.test(trimmed)) { return { isValid: false, error: 'Variable name must start with a letter and contain only letters, numbers, and underscores' }; } // Check for reserved function names const reservedNames = ['sin', 'cos', 'tan', 'log', 'ln', 'abs', 'sqrt', 'exp', 'pi', 'e']; if (reservedNames.includes(trimmed.toLowerCase())) { return { isValid: false, error: `"${trimmed}" is a reserved function name and cannot be used as a variable` }; } return { isValid: true }; } /** * Validate system of equations */ function validateSystemOfEquations(equations) { if (!Array.isArray(equations)) { return { isValid: false, error: 'Equations must be provided as an array' }; } if (equations.length === 0) { return { isValid: false, error: 'At least one equation must be provided' }; } if (equations.length > 10) { return { isValid: false, error: 'Cannot solve more than 10 equations simultaneously' }; } // Validate each equation for (let i = 0; i < equations.length; i++) { const equation = equations[i]; if (equation === undefined || equation === null) { return { isValid: false, error: `Equation ${i + 1} is undefined or null` }; } const validation = validateEquation(equation); if (!validation.isValid) { return { isValid: false, error: `Equation ${i + 1}: ${validation.error}` }; } } return { isValid: true }; } /** * Validate variables list for system solving */ function validateVariablesList(variables) { if (!Array.isArray(variables)) { return { isValid: false, error: 'Variables must be provided as an array' }; } if (variables.length === 0) { return { isValid: false, error: 'At least one variable must be specified' }; } if (variables.length > 10) { return { isValid: false, error: 'Cannot solve for more than 10 variables simultaneously' }; } // Validate each variable name for (let i = 0; i < variables.length; i++) { const variable = variables[i]; if (variable === undefined || variable === null) { return { isValid: false, error: `Variable ${i + 1} is undefined or null` }; } const validation = validateVariableName(variable); if (!validation.isValid) { return { isValid: false, error: `Variable ${i + 1}: ${validation.error}` }; } } // Check for duplicate variables const uniqueVars = new Set(variables); if (uniqueVars.size !== variables.length) { return { isValid: false, error: 'Variables list cannot contain duplicates' }; } return { isValid: true }; } /** * Validates slider parameters for creation */ function validateSliderParameters(name, min, max, increment, defaultValue) { // Validate name const nameValidation = validateObjectName(name); if (!nameValidation.isValid) { return nameValidation; } // Validate range if (min >= max) { return { isValid: false, error: 'Minimum value must be less than maximum value' }; } // Validate increment if (increment !== undefined && increment <= 0) { return { isValid: false, error: 'Increment must be positive' }; } // Validate default value if (defaultValue !== undefined && (defaultValue < min || defaultValue > max)) { return { isValid: false, error: 'Default value must be within the specified range' }; } return { isValid: true }; } /** * Validates animation speed parameter */ function validateAnimationSpeed(speed) { if (speed <= 0) { return { isValid: false, error: 'Animation speed must be positive' }; } if (speed > 10) { return { isValid: false, error: 'Animation speed should not exceed 10 for reasonable performance' }; } return { isValid: true }; } /** * Validates animation direction parameter */ function validateAnimationDirection(direction) { if (direction && !['forward', 'backward', 'oscillating'].includes(direction)) { return { isValid: false, error: 'Animation direction must be "forward", "backward", or "oscillating"' }; } return { isValid: true }; } /** * Validates parameters for animation export */ function validateAnimationExportParameters(frameCount, frameDelay, totalDuration) { if (frameCount <= 0 || !Number.isInteger(frameCount)) { return { isValid: false, error: 'Frame count must be a positive integer' }; } if (frameCount > 300) { return { isValid: false, error: 'Frame count should not exceed 300 for reasonable file size' }; } if (frameDelay <= 0) { return { isValid: false, error: 'Frame delay must be positive' }; } if (totalDuration !== undefined && totalDuration <= 0) { return { isValid: false, error: 'Total duration must be positive' }; } return { isValid: true }; } //# sourceMappingURL=validation.js.map