model-explorer / js /utils /validators.js
mr4's picture
Upload 71 files
9bd422a verified
/**
* Validators - Input and data validation utilities
* Provides functions for validating ONNX files, SafeTensors files, model data, and user inputs.
* Requirements: 3.3, 9.3, 36.1, 36.3, 36.5
*/
const Validators = (function () {
// ─── ONNX File Validation ─────────────────────────────────────────────────
/**
* ONNX protobuf magic bytes (first 2 bytes of a valid serialised protobuf
* message with field 1 = irVersion). We accept any non-empty ArrayBuffer
* whose first byte is 0x08 (field 1, wire type 0 = varint) as a minimal
* heuristic, since ONNX models always start with the irVersion field.
*
* A stricter check would require a full protobuf parse; this is intentionally
* lightweight for a browser-side static app.
*/
const ONNX_MAGIC_BYTE = 0x08;
/**
* Validate that a File object is a valid ONNX file.
* Checks:
* 1. The file has a .onnx extension (case-insensitive).
* 2. The file is not empty.
*
* @param {File} file - Browser File object
* @returns {{ valid: boolean, error?: string }}
*/
function isValidOnnxFile(file) {
if (!file) {
return { valid: false, error: 'No file provided.' };
}
// Extension check
const name = file.name || '';
if (!name.toLowerCase().endsWith('.onnx')) {
return {
valid: false,
error: `Invalid file type "${name}". Only .onnx files are accepted.`,
};
}
// Empty file check
if (file.size === 0) {
return { valid: false, error: 'The file is empty.' };
}
return { valid: true };
}
/**
* Validate the raw bytes of an ONNX file (ArrayBuffer).
* Performs a lightweight magic-byte check.
*
* @param {ArrayBuffer} buffer
* @returns {{ valid: boolean, error?: string }}
*/
function isValidOnnxBuffer(buffer) {
if (!(buffer instanceof ArrayBuffer) || buffer.byteLength === 0) {
return { valid: false, error: 'Invalid or empty buffer.' };
}
const view = new Uint8Array(buffer, 0, Math.min(4, buffer.byteLength));
if (view[0] !== ONNX_MAGIC_BYTE) {
return {
valid: false,
error: 'File does not appear to be a valid ONNX model (unexpected header bytes).',
};
}
return { valid: true };
}
// ─── SafeTensors File Validation ────────────────────────────────────────────
/**
* Validate that a File object is a valid SafeTensors file.
* Checks:
* 1. The file has a .safetensors extension (case-insensitive).
* 2. The file is not empty.
*
* @param {File} file - Browser File object
* @returns {{ valid: boolean, error?: string }}
*/
function isValidSafeTensorsFile(file) {
if (!file) {
return { valid: false, error: 'No file provided.' };
}
// Extension check
const name = file.name || '';
if (!name.toLowerCase().endsWith('.safetensors')) {
return {
valid: false,
error: `Invalid file type "${name}". Only .safetensors files are accepted.`,
};
}
// Empty file check
if (file.size === 0) {
return { valid: false, error: 'The file is empty.' };
}
return { valid: true };
}
// ─── Model Data Validation ────────────────────────────────────────────────
/**
* Validate a parsed model data object returned by ONNXParser.
* Ensures the required top-level fields are present and non-null.
*
* @param {Object} data - ParsedModel object
* @returns {{ valid: boolean, error?: string }}
*/
function isValidModelData(data) {
if (!data || typeof data !== 'object') {
return { valid: false, error: 'Model data is null or not an object.' };
}
const required = ['metadata', 'graph', 'inputs', 'outputs'];
for (const field of required) {
if (!(field in data) || data[field] == null) {
return { valid: false, error: `Model data is missing required field: "${field}".` };
}
}
if (!Array.isArray(data.inputs)) {
return { valid: false, error: 'Model data "inputs" must be an array.' };
}
if (!Array.isArray(data.outputs)) {
return { valid: false, error: 'Model data "outputs" must be an array.' };
}
return { valid: true };
}
// ─── Input / Search Validation ────────────────────────────────────────────
/**
* Validate a generic user input string.
* Returns false for null/undefined; trims and checks length.
*
* @param {*} input
* @param {{ minLength?: number, maxLength?: number, required?: boolean }} [opts]
* @returns {{ valid: boolean, error?: string }}
*/
function isValidInput(input, opts = {}) {
const { minLength = 0, maxLength = 1000, required = false } = opts;
if (input == null || input === '') {
if (required) {
return { valid: false, error: 'This field is required.' };
}
return { valid: true }; // empty is OK when not required
}
const str = String(input).trim();
if (required && str.length === 0) {
return { valid: false, error: 'This field cannot be blank.' };
}
if (str.length < minLength) {
return { valid: false, error: `Input must be at least ${minLength} characters.` };
}
if (str.length > maxLength) {
return { valid: false, error: `Input must not exceed ${maxLength} characters.` };
}
return { valid: true };
}
/**
* Validate a search query string.
* Allows empty strings (clears the filter); rejects excessively long ones.
*
* @param {string} query
* @returns {{ valid: boolean, error?: string }}
*/
function isValidSearchQuery(query) {
return isValidInput(query, { maxLength: 200 });
}
/**
* Validate a model path string (must be non-empty and end with .onnx or .safetensors).
* @param {string} path
* @returns {{ valid: boolean, error?: string }}
*/
function isValidModelPath(path) {
if (!path || typeof path !== 'string' || path.trim() === '') {
return { valid: false, error: 'Model path must be a non-empty string.' };
}
const lower = path.toLowerCase();
if (!lower.endsWith('.onnx') && !lower.endsWith('.safetensors')) {
return { valid: false, error: 'Model path must point to an .onnx or .safetensors file.' };
}
return { valid: true };
}
// ─── Public API ───────────────────────────────────────────────────────────
return {
isValidOnnxFile,
isValidOnnxBuffer,
isValidSafeTensorsFile,
isValidModelData,
isValidInput,
isValidSearchQuery,
isValidModelPath,
};
})();
// Export for global access in vanilla JS context
window.Validators = Validators;