Skip to main content
Glama
validation.ts5.87 kB
/** * Validation utilities for LSP operations * Common guards for file existence, paths, positions, and workspace state */ import * as fs from 'fs'; import * as path from 'path'; import { LspContext, ValidationResult, SymbolPositionRequest, FileRequest, ValidationErrorCode, OneBasedPosition, toZeroBased, } from './types.js'; /** * Validates that the workspace is ready for operations */ export function validateWorkspaceReady(ctx: LspContext): ValidationResult { const { workspaceState } = ctx; if (workspaceState.isLoading) { return { valid: false, error: { errorCode: ValidationErrorCode.WorkspaceNotReady, message: `Workspace is still loading (started ${workspaceState.loadingStartedAt?.toISOString()}). Please wait for initialization to complete.`, }, }; } if (!workspaceState.isReady) { return { valid: false, error: { errorCode: ValidationErrorCode.WorkspaceNotReady, message: 'Workspace is not ready. Initialization may have failed.', }, }; } return { valid: true }; } /** * Validates file existence and converts to absolute path */ export function validateAndNormalizeFilePath( filePath: string, workspaceDir?: string ): ValidationResult & { absolutePath?: string } { try { // Convert to absolute path using workspace directory if provided const absolutePath = workspaceDir ? path.resolve(workspaceDir, filePath) : path.resolve(filePath); // Check if file exists if (!fs.existsSync(absolutePath)) { return { valid: false, error: { errorCode: ValidationErrorCode.InvalidPath, message: `File not found: ${absolutePath}`, }, }; } // Check if it's actually a file (not a directory) const stats = fs.statSync(absolutePath); if (!stats.isFile()) { return { valid: false, error: { errorCode: ValidationErrorCode.InvalidPath, message: `Path is not a file: ${absolutePath}`, }, }; } return { valid: true, absolutePath }; } catch (error) { return { valid: false, error: { errorCode: ValidationErrorCode.InvalidPath, message: `Invalid file path: ${error instanceof Error ? error.message : String(error)}`, }, }; } } /** * Validates that position is within file bounds */ export async function validatePosition( filePath: string, position: OneBasedPosition ): Promise<ValidationResult> { try { const content = await fs.promises.readFile(filePath, 'utf8'); const lines = content.split('\n'); // Convert to 0-based for validation (LSP coordinates) const zeroBasedPosition = toZeroBased(position); // Check line bounds if (zeroBasedPosition.line < 0 || zeroBasedPosition.line >= lines.length) { return { valid: false, error: { errorCode: ValidationErrorCode.PositionOutOfBounds, message: `Line ${position.line} is out of bounds. File has ${lines.length} lines.`, }, }; } // Check character bounds const lineContent = lines[zeroBasedPosition.line]; if (!lineContent) { return { valid: false, error: { errorCode: ValidationErrorCode.PositionOutOfBounds, message: `Line ${position.line} does not exist in file.`, }, }; } if ( zeroBasedPosition.character < 0 || zeroBasedPosition.character > lineContent.length ) { return { valid: false, error: { errorCode: ValidationErrorCode.PositionOutOfBounds, message: `Character ${position.character} is out of bounds. Line ${position.line} has ${lineContent.length} characters.`, }, }; } return { valid: true }; } catch (error) { return { valid: false, error: { errorCode: ValidationErrorCode.InvalidPath, message: `Cannot read file for position validation: ${error instanceof Error ? error.message : String(error)}`, }, }; } } /** * Comprehensive validation for file-based requests */ export function validateFileRequest( ctx: LspContext, request: FileRequest ): ValidationResult & { absolutePath?: string } { // Check workspace readiness const workspaceCheck = validateWorkspaceReady(ctx); if (!workspaceCheck.valid) { return workspaceCheck; } // Validate and normalize file path using workspace context const pathCheck = validateAndNormalizeFilePath( request.file, ctx.workspacePath ); if (!pathCheck.valid) { return pathCheck; } return pathCheck.absolutePath ? { valid: true, absolutePath: pathCheck.absolutePath } : { valid: true }; } /** * Comprehensive validation for symbol position requests */ export async function validateSymbolPositionRequest( ctx: LspContext, request: SymbolPositionRequest ): Promise<ValidationResult & { absolutePath?: string }> { // Check workspace readiness const workspaceCheck = validateWorkspaceReady(ctx); if (!workspaceCheck.valid) { return workspaceCheck; } // Validate and normalize file path using workspace context const pathCheck = validateAndNormalizeFilePath( request.file, ctx.workspacePath ); if (!pathCheck.valid) { return pathCheck; } // Validate position bounds const positionCheck = await validatePosition( pathCheck.absolutePath!, request.position ); if (!positionCheck.valid) { return positionCheck; } return pathCheck.absolutePath ? { valid: true, absolutePath: pathCheck.absolutePath } : { valid: true }; } /** * Simple workspace validation for search operations (no file required) */ export function validateWorkspaceOperation(ctx: LspContext): ValidationResult { return validateWorkspaceReady(ctx); }

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/p1va/symbols-mcp'

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