/**
* Manages custom validation functions for RDF data validation
*/
export default class CustomValidators {
constructor() {
this.validators = new Map();
this.registerBuiltins();
}
registerBuiltins() {
// Basic type validators
this.register('uri', {
validate: (value) => {
try {
new URL(value);
return { valid: true };
} catch {
return {
valid: false,
message: 'Invalid URI format'
};
}
}
});
this.register('language', {
validate: (value) => {
const langPattern = /^[a-zA-Z]{2,3}(-[a-zA-Z]{2,4})?$/;
return {
valid: langPattern.test(value),
message: langPattern.test(value) ? null : 'Invalid language tag'
};
}
});
// Semantic validators
this.register('concept', {
validate: (value, options = {}) => {
if (!value.startsWith(options.namespace || 'http://')) {
return {
valid: false,
message: 'Concept URI must use correct namespace'
};
}
return { valid: true };
}
});
// Temporal validators
this.register('timerange', {
validate: (value, options = {}) => {
const { start, end } = value;
const startDate = new Date(start);
const endDate = new Date(end);
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
return {
valid: false,
message: 'Invalid date format'
};
}
if (startDate > endDate) {
return {
valid: false,
message: 'Start date must be before end date'
};
}
return { valid: true };
}
});
}
/**
* Register a new validator
* @param {string} name - Validator name
* @param {Object} validator - Validator definition
*/
register(name, validator) {
if (typeof validator.validate !== 'function') {
throw new Error('Validator must have a validate function');
}
this.validators.set(name, {
...validator,
async: validator.validate.constructor.name === 'AsyncFunction'
});
}
/**
* Register multiple validators
* @param {Object} validators - Map of validator names to definitions
*/
registerBatch(validators) {
for (const [name, validator] of Object.entries(validators)) {
this.register(name, validator);
}
}
/**
* Get a registered validator
* @param {string} name - Validator name
*/
get(name) {
const validator = this.validators.get(name);
if (!validator) {
throw new Error(`Validator not found: ${name}`);
}
return validator;
}
/**
* Execute a validator
* @param {string} name - Validator name
* @param {*} value - Value to validate
* @param {Object} options - Validator options
*/
async execute(name, value, options = {}) {
const validator = this.get(name);
try {
const result = await validator.validate(value, options);
return {
valid: result.valid,
message: result.message,
validator: name,
value
};
} catch (error) {
return {
valid: false,
message: error.message,
validator: name,
value,
error
};
}
}
/**
* Create a composite validator from multiple validators
* @param {Array} validators - Array of validator names or definitions
*/
compose(validators) {
return {
validate: async (value, options = {}) => {
const results = [];
for (const validator of validators) {
const name = typeof validator === 'string' ?
validator : validator.name;
const validatorOptions = typeof validator === 'string' ?
options : { ...options, ...validator.options };
const result = await this.execute(name, value, validatorOptions);
results.push(result);
if (!result.valid) break;
}
const valid = results.every(r => r.valid);
return {
valid,
results,
message: valid ? null : results.find(r => !r.valid)?.message
};
}
};
}
/**
* Create a conditional validator
* @param {Function} condition - Condition function
* @param {string|Object} validator - Validator to apply if condition is true
*/
conditional(condition, validator) {
return {
validate: async (value, options = {}) => {
if (!await condition(value, options)) {
return { valid: true };
}
const name = typeof validator === 'string' ?
validator : validator.name;
const validatorOptions = typeof validator === 'string' ?
options : { ...options, ...validator.options };
return this.execute(name, value, validatorOptions);
}
};
}
/**
* Create a recursive validator for nested structures
* @param {string|Object} validator - Base validator
* @param {Object} options - Recursion options
*/
recursive(validator, options = {}) {
return {
validate: async (value, validatorOptions = {}) => {
const results = [];
const maxDepth = options.maxDepth || 10;
const validateNode = async (node, depth = 0) => {
if (depth > maxDepth) {
throw new Error('Maximum recursion depth exceeded');
}
// Validate current node
const result = await this.execute(
typeof validator === 'string' ? validator : validator.name,
node,
typeof validator === 'string' ?
validatorOptions :
{ ...validatorOptions, ...validator.options }
);
results.push(result);
// Recurse into children if any
if (node && typeof node === 'object') {
for (const child of Object.values(node)) {
if (child && typeof child === 'object') {
await validateNode(child, depth + 1);
}
}
}
};
await validateNode(value);
const valid = results.every(r => r.valid);
return {
valid,
results,
message: valid ? null : results.find(r => !r.valid)?.message
};
}
};
}
}