@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
392 lines (391 loc) • 11.5 kB
JavaScript
/**
* Request Validation Middleware
* Provides schema-based request validation for server adapters
*/
import { ValidationError as ServerValidationError } from "../errors.js";
/**
* Re-export ValidationError from errors for convenience
*/
export { ValidationError } from "../errors.js";
/**
* Create request validation middleware
*
* @example
* ```typescript
* const validationMiddleware = createRequestValidationMiddleware({
* bodySchema: {
* required: ["input"],
* properties: {
* input: { type: "string", minimum: 1 },
* temperature: { type: "number", minimum: 0, maximum: 1 },
* },
* },
* });
*
* server.registerMiddleware(validationMiddleware);
* ```
*/
export function createRequestValidationMiddleware(config) {
const { bodySchema, querySchema, paramsSchema, headersSchema, customValidator, skipPaths = [], errorFormatter = defaultErrorFormatter, } = config;
return {
name: "request-validation",
order: 15, // Run after auth
excludePaths: skipPaths,
handler: async (ctx, next) => {
const errors = [];
// Validate body
if (bodySchema && ctx.body) {
const bodyErrors = validateObject(ctx.body, bodySchema, "body");
errors.push(...bodyErrors);
}
// Validate query
if (querySchema) {
const queryErrors = validateObject(ctx.query, querySchema, "query");
errors.push(...queryErrors);
}
// Validate params
if (paramsSchema) {
const paramsErrors = validateObject(ctx.params, paramsSchema, "params");
errors.push(...paramsErrors);
}
// Validate headers
if (headersSchema) {
const headerErrors = validateObject(ctx.headers, headersSchema, "headers");
errors.push(...headerErrors);
}
// Run custom validator
if (customValidator) {
try {
await customValidator(ctx);
}
catch (error) {
if (error instanceof ServerValidationError) {
errors.push(...error.errors);
}
else {
errors.push({
field: "custom",
message: error instanceof Error ? error.message : String(error),
});
}
}
}
// If there are errors, throw
if (errors.length > 0) {
const formattedError = errorFormatter(errors.map((e) => new ServerValidationError([e], ctx.requestId)));
const error = new ServerValidationError(errors, ctx.requestId);
// Attach formatted response to error
error.response = formattedError;
throw error;
}
return next();
},
};
}
/**
* Validate an object against a schema
*/
function validateObject(obj, schema, prefix) {
const errors = [];
// Check required fields
if (schema.required) {
for (const field of schema.required) {
if (!(field in obj) || obj[field] === undefined || obj[field] === null) {
errors.push({
field: `${prefix}.${field}`,
message: `${field} is required`,
});
}
}
}
// Check additional properties
if (schema.additionalProperties === false && schema.properties) {
const allowedKeys = new Set(Object.keys(schema.properties));
for (const key of Object.keys(obj)) {
if (!allowedKeys.has(key)) {
errors.push({
field: `${prefix}.${key}`,
message: `Unknown property: ${key}`,
});
}
}
}
// Validate properties
if (schema.properties) {
for (const [key, propSchema] of Object.entries(schema.properties)) {
const value = obj[key];
// Skip undefined values (handled by required check)
if (value === undefined) {
continue;
}
const propErrors = validateProperty(value, propSchema, `${prefix}.${key}`);
errors.push(...propErrors);
}
}
return errors;
}
/**
* Validate a single property
*/
function validateProperty(value, schema, fieldPath) {
const errors = [];
// Type validation
const actualType = getType(value);
if (actualType !== schema.type) {
errors.push({
field: fieldPath,
message: `Expected ${schema.type}, got ${actualType}`,
});
return errors; // Stop further validation if type is wrong
}
// Minimum/maximum validation
if (schema.type === "number" && typeof value === "number") {
if (schema.minimum !== undefined && value < schema.minimum) {
errors.push({
field: fieldPath,
message: `Value must be at least ${schema.minimum}`,
});
}
if (schema.maximum !== undefined && value > schema.maximum) {
errors.push({
field: fieldPath,
message: `Value must be at most ${schema.maximum}`,
});
}
}
if (schema.type === "string" && typeof value === "string") {
// Support both minimum/maximum and minLength/maxLength
const minLen = schema.minLength ?? schema.minimum;
const maxLen = schema.maxLength ?? schema.maximum;
if (minLen !== undefined && value.length < minLen) {
errors.push({
field: fieldPath,
message: `String must be at least ${minLen} characters`,
});
}
if (maxLen !== undefined && value.length > maxLen) {
errors.push({
field: fieldPath,
message: `String must be at most ${maxLen} characters`,
});
}
if (schema.pattern && !new RegExp(schema.pattern).test(value)) {
errors.push({
field: fieldPath,
message: `String does not match pattern: ${schema.pattern}`,
});
}
}
if (schema.type === "array" && Array.isArray(value)) {
// Support both minimum/maximum and minItems/maxItems
const minItems = schema.minItems ?? schema.minimum;
const maxItems = schema.maxItems ?? schema.maximum;
if (minItems !== undefined && value.length < minItems) {
errors.push({
field: fieldPath,
message: `Array must have at least ${minItems} items`,
});
}
if (maxItems !== undefined && value.length > maxItems) {
errors.push({
field: fieldPath,
message: `Array must have at most ${maxItems} items`,
});
}
}
// Enum validation
if (schema.enum && !schema.enum.includes(value)) {
errors.push({
field: fieldPath,
message: `Value must be one of: ${schema.enum.join(", ")}`,
});
}
// Custom validation
if (schema.validate) {
const result = schema.validate(value);
if (result !== true) {
errors.push({
field: fieldPath,
message: typeof result === "string" ? result : "Custom validation failed",
});
}
}
return errors;
}
/**
* Get the type of a value
*/
function getType(value) {
if (value === null) {
return "null";
}
if (Array.isArray(value)) {
return "array";
}
return typeof value;
}
/**
* Default error formatter
*/
function defaultErrorFormatter(errors) {
return {
error: {
code: "VALIDATION_ERROR",
message: "Request validation failed",
details: errors.flatMap((e) => e.errors),
},
};
}
/**
* Create a field validator helper
*/
export function createFieldValidator(fieldName, rules) {
return (value) => {
const errors = validateProperty(value, rules, fieldName);
if (errors.length > 0) {
throw new ServerValidationError(errors);
}
};
}
// ============================================
// Convenience Functions
// ============================================
/**
* Create body-only validation middleware
*
* @example
* ```typescript
* const middleware = createBodyValidationMiddleware({
* required: ["input"],
* properties: {
* input: { type: "string" },
* },
* });
* ```
*/
export function createBodyValidationMiddleware(schema) {
return createRequestValidationMiddleware({ bodySchema: schema });
}
/**
* Create query-only validation middleware
*
* @example
* ```typescript
* const middleware = createQueryValidationMiddleware({
* properties: {
* page: { type: "number", minimum: 1 },
* limit: { type: "number", minimum: 1, maximum: 100 },
* },
* });
* ```
*/
export function createQueryValidationMiddleware(schema) {
return createRequestValidationMiddleware({ querySchema: schema });
}
/**
* Create a combined validation middleware with full config support
* Alias for createRequestValidationMiddleware for compatibility
*/
export const createValidationMiddleware = createRequestValidationMiddleware;
// ============================================
// Common Schemas
// ============================================
/**
* Common validation schemas for reuse
*/
export const CommonSchemas = {
/**
* UUID schema
*/
uuid: {
type: "string",
format: "uuid",
},
/**
* Email schema
*/
email: {
type: "string",
format: "email",
},
/**
* Pagination schema
*/
pagination: {
type: "object",
properties: {
page: {
type: "number",
minimum: 1,
},
limit: {
type: "number",
minimum: 1,
maximum: 100,
},
offset: {
type: "number",
minimum: 0,
},
},
},
/**
* Sorting schema
*/
sorting: {
type: "object",
properties: {
sortBy: {
type: "string",
},
sortOrder: {
type: "string",
enum: ["asc", "desc"],
},
},
},
/**
* ID path parameter schema
*/
idParam: {
type: "object",
required: ["id"],
properties: {
id: {
type: "string",
},
},
},
/**
* Date range schema
*/
dateRange: {
type: "object",
properties: {
startDate: {
type: "string",
format: "date",
},
endDate: {
type: "string",
format: "date",
},
},
},
/**
* Search query schema
*/
search: {
type: "object",
properties: {
q: {
type: "string",
minimum: 1,
maximum: 255,
},
fields: {
type: "array",
},
},
},
};