Spaces:
Build error
Build error
File size: 4,788 Bytes
8a01471 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
import Ajv from 'ajv';
import schema from './nn3d.schema.json';
import type { NN3DModel } from './types';
// Create AJV instance with formats
const ajv = new Ajv({ allErrors: true, strict: false });
// Compile the schema
const validate = ajv.compile<NN3DModel>(schema);
/**
* Validation result
*/
export interface ValidationResult {
valid: boolean;
errors: ValidationError[];
}
/**
* Validation error details
*/
export interface ValidationError {
path: string;
message: string;
keyword: string;
params: Record<string, unknown>;
}
/**
* Validate an NN3D model against the schema
*/
export function validateNN3DModel(model: unknown): ValidationResult {
const valid = validate(model);
if (valid) {
return { valid: true, errors: [] };
}
const errors: ValidationError[] = (validate.errors || []).map(err => ({
path: err.instancePath || '/',
message: err.message || 'Unknown validation error',
keyword: err.keyword,
params: err.params as Record<string, unknown>,
}));
return { valid: false, errors };
}
/**
* Parse and validate a JSON string as NN3D model
*/
export function parseNN3DModel(jsonString: string): { model: NN3DModel | null; validation: ValidationResult } {
try {
const parsed = JSON.parse(jsonString);
const validation = validateNN3DModel(parsed);
return {
model: validation.valid ? parsed as NN3DModel : null,
validation,
};
} catch (e) {
return {
model: null,
validation: {
valid: false,
errors: [{
path: '/',
message: `JSON parse error: ${e instanceof Error ? e.message : 'Unknown error'}`,
keyword: 'parse',
params: {},
}],
},
};
}
}
/**
* Validate model structure beyond schema (semantic validation)
*/
export function validateModelSemantics(model: NN3DModel): ValidationResult {
const errors: ValidationError[] = [];
// Check for duplicate node IDs
const nodeIds = new Set<string>();
for (const node of model.graph.nodes) {
if (nodeIds.has(node.id)) {
errors.push({
path: `/graph/nodes/${node.id}`,
message: `Duplicate node ID: ${node.id}`,
keyword: 'uniqueId',
params: { id: node.id },
});
}
nodeIds.add(node.id);
}
// Validate edge references
for (const edge of model.graph.edges) {
if (!nodeIds.has(edge.source)) {
errors.push({
path: `/graph/edges/${edge.id || 'unknown'}`,
message: `Edge source node not found: ${edge.source}`,
keyword: 'nodeRef',
params: { source: edge.source },
});
}
if (!nodeIds.has(edge.target)) {
errors.push({
path: `/graph/edges/${edge.id || 'unknown'}`,
message: `Edge target node not found: ${edge.target}`,
keyword: 'nodeRef',
params: { target: edge.target },
});
}
}
// Validate subgraph node references
if (model.graph.subgraphs) {
for (const subgraph of model.graph.subgraphs) {
for (const nodeId of subgraph.nodes) {
if (!nodeIds.has(nodeId)) {
errors.push({
path: `/graph/subgraphs/${subgraph.id}`,
message: `Subgraph references non-existent node: ${nodeId}`,
keyword: 'nodeRef',
params: { nodeId },
});
}
}
}
}
// Check for cycles (optional - may be valid in some networks)
const hasCycle = detectCycles(model.graph.nodes, model.graph.edges);
if (hasCycle) {
errors.push({
path: '/graph',
message: 'Graph contains cycles (may be intentional for recurrent networks)',
keyword: 'cycle',
params: {},
});
}
return {
valid: errors.length === 0,
errors,
};
}
/**
* Detect cycles in the graph using DFS
*/
function detectCycles(nodes: { id: string }[], edges: { source: string; target: string }[]): boolean {
const adjacency = new Map<string, string[]>();
for (const node of nodes) {
adjacency.set(node.id, []);
}
for (const edge of edges) {
adjacency.get(edge.source)?.push(edge.target);
}
const visited = new Set<string>();
const recursionStack = new Set<string>();
function dfs(nodeId: string): boolean {
visited.add(nodeId);
recursionStack.add(nodeId);
const neighbors = adjacency.get(nodeId) || [];
for (const neighbor of neighbors) {
if (!visited.has(neighbor)) {
if (dfs(neighbor)) return true;
} else if (recursionStack.has(neighbor)) {
return true;
}
}
recursionStack.delete(nodeId);
return false;
}
for (const node of nodes) {
if (!visited.has(node.id)) {
if (dfs(node.id)) return true;
}
}
return false;
}
export { schema as nn3dSchema };
|