@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
238 lines • 7.08 kB
JavaScript
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
/**
* Schema validation utility using Ajv
*/
export class SchemaValidator {
ajv;
constructor() {
this.ajv = new Ajv({
allErrors: true,
removeAdditional: false,
useDefaults: true,
coerceTypes: true,
strict: false
});
// Add format validation (date, email, uri, etc.)
addFormats(this.ajv);
// Add custom formats if needed
this.addCustomFormats();
}
/**
* Validate data against a JSON schema
*/
validate(schema, data) {
try {
const validate = this.ajv.compile(schema);
const valid = validate(data);
if (valid) {
return { valid: true, errors: [] };
}
const errors = (validate.errors || []).map(error => ({
path: error.instancePath || 'root',
message: error.message || 'Validation failed',
value: error.data
}));
return { valid: false, errors };
}
catch (error) {
return {
valid: false,
errors: [{
path: 'schema',
message: `Schema compilation error: ${error instanceof Error ? error.message : 'Unknown error'}`,
value: schema
}]
};
}
}
/**
* Validate and return cleaned data
*/
validateAndClean(schema, data) {
const result = this.validate(schema, data);
if (result.valid) {
return { valid: true, data: data };
}
return { valid: false, errors: result.errors };
}
/**
* Create a validation function for a specific schema
*/
createValidator(schema) {
const compiledValidator = this.ajv.compile(schema);
return (data) => {
const valid = compiledValidator(data);
if (valid) {
return { valid: true, errors: [] };
}
const errors = (compiledValidator.errors || []).map(error => ({
path: error.instancePath || 'root',
message: error.message || 'Validation failed',
value: error.data
}));
return { valid: false, errors };
};
}
/**
* Add custom format validators
*/
addCustomFormats() {
// Unix timestamp format
this.ajv.addFormat('unix-timestamp', {
type: 'number',
validate: (data) => {
return Number.isInteger(data) && data > 0 && data < 4000000000; // reasonable timestamp range
}
});
// Story points format (Fibonacci-like sequence)
this.ajv.addFormat('story-points', {
type: 'number',
validate: (data) => {
const validPoints = [0, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89];
return validPoints.includes(data);
}
});
// Priority levels
this.ajv.addFormat('priority', {
type: 'string',
validate: (data) => {
return ['low', 'medium', 'high', 'critical'].includes(data);
}
});
// Status values for various entities
this.ajv.addFormat('story-status', {
type: 'string',
validate: (data) => {
return ['todo', 'in_progress', 'in_review', 'done', 'backlog'].includes(data);
}
});
this.ajv.addFormat('sprint-status', {
type: 'string',
validate: (data) => {
return ['planning', 'active', 'completed', 'cancelled'].includes(data);
}
});
this.ajv.addFormat('approval-status', {
type: 'string',
validate: (data) => {
return ['pending', 'approved', 'rejected', 'expired'].includes(data);
}
});
// Documentation status
this.ajv.addFormat('doc-status', {
type: 'string',
validate: (data) => {
return ['pending', 'in_progress', 'approved', 'outdated'].includes(data);
}
});
}
}
// Global validator instance
let globalValidator = null;
export function getValidator() {
if (!globalValidator) {
globalValidator = new SchemaValidator();
}
return globalValidator;
}
/**
* Common schema definitions for reuse
*/
export const CommonSchemas = {
id: {
type: 'string',
minLength: 1,
maxLength: 255,
pattern: '^[a-zA-Z0-9_-]+$'
},
projectId: {
type: 'string',
minLength: 1,
maxLength: 255,
description: 'Project identifier'
},
timestamp: {
type: 'integer',
format: 'unix-timestamp',
description: 'Unix timestamp'
},
priority: {
type: 'string',
format: 'priority',
default: 'medium',
description: 'Priority level'
},
tags: {
type: 'array',
items: {
type: 'string',
minLength: 1,
maxLength: 50
},
default: [],
description: 'Array of tags'
},
storyPoints: {
type: 'integer',
format: 'story-points',
default: 0,
description: 'Story points using Fibonacci sequence'
},
status: {
story: {
type: 'string',
format: 'story-status',
default: 'todo',
description: 'Story status'
},
sprint: {
type: 'string',
format: 'sprint-status',
default: 'planning',
description: 'Sprint status'
},
approval: {
type: 'string',
format: 'approval-status',
default: 'pending',
description: 'Approval status'
}
},
documentation: {
url: {
type: 'string',
format: 'uri',
description: 'Document URL'
},
status: {
type: 'string',
format: 'doc-status',
default: 'pending',
description: 'Documentation status'
}
}
};
/**
* Utility function to create error responses
*/
export function createValidationError(errors) {
const errorMessages = errors.map(e => `${e.path}: ${e.message}`);
return {
code: 'VALIDATION_ERROR',
message: `Input validation failed: ${errorMessages.join('; ')}`,
details: {
validationErrors: errors,
errorCount: errors.length
},
suggestions: [
'Check the input parameters against the tool schema',
'Ensure all required fields are provided',
'Verify data types match the expected format',
'Review field constraints (min/max length, allowed values)'
],
recoverable: true,
category: 'validation'
};
}
//# sourceMappingURL=validation.js.map