Skip to content

Commit 900d259

Browse files
committed
refactor(core): add Zod validation to metadata loading (Priority 3)
Following TypeScript Standards Rule #2: No Type Assertions Without Validation Changes: - Created validation schema (packages/core/src/storage/validation.ts) * RepositoryMetadataSchema with nested objects * validateRepositoryMetadata() helper - Replaced type assertion in metadata.ts * Before: JSON.parse(content) as RepositoryMetadata * After: validateRepositoryMetadata(JSON.parse(content)) Impact: - Protects against corrupted metadata.json files - Returns null on validation failure (existing error handling) - All 1739 tests passing ✅ Scope: State file validation (metadata only, LOW RISK boundary) Note: Indexer state files are more complex and would require significant refactoring. This completes validation for the primary metadata file.
1 parent e3e2d52 commit 900d259

File tree

4 files changed

+62
-3
lines changed

4 files changed

+62
-3
lines changed

packages/core/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@
3030
"test:watch": "vitest"
3131
},
3232
"devDependencies": {
33-
"tree-sitter-wasms": "^0.1.13",
3433
"@types/mdast": "^4.0.4",
3534
"@types/node": "^24.10.1",
35+
"tree-sitter-wasms": "^0.1.13",
3636
"typescript": "^5.3.3"
3737
},
3838
"dependencies": {
@@ -45,6 +45,7 @@
4545
"remark-stringify": "^11.0.0",
4646
"ts-morph": "^27.0.2",
4747
"unified": "^11.0.5",
48-
"web-tree-sitter": "^0.25.10"
48+
"web-tree-sitter": "^0.25.10",
49+
"zod": "^4.1.13"
4950
}
5051
}

packages/core/src/storage/metadata.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { execSync } from 'node:child_process';
77
import * as fs from 'node:fs/promises';
88
import * as path from 'node:path';
99
import { getGitRemote, normalizeGitRemote } from './path';
10+
import { validateRepositoryMetadata } from './validation.js';
1011

1112
export interface RepositoryMetadata {
1213
version: string;
@@ -73,7 +74,9 @@ export async function loadMetadata(storagePath: string): Promise<RepositoryMetad
7374
const metadataPath = path.join(storagePath, 'metadata.json');
7475
try {
7576
const content = await fs.readFile(metadataPath, 'utf-8');
76-
return JSON.parse(content) as RepositoryMetadata;
77+
const data = JSON.parse(content);
78+
const validated = validateRepositoryMetadata(data);
79+
return validated as RepositoryMetadata | null;
7780
} catch {
7881
return null;
7982
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* State File Validation Utilities
3+
*
4+
* Following TypeScript Standards Rule #2: No Type Assertions Without Validation
5+
* State files (JSON) must be validated at runtime to handle corruption/format changes
6+
*/
7+
8+
import { z } from 'zod';
9+
10+
/**
11+
* Repository metadata schema
12+
*/
13+
export const RepositoryMetadataSchema = z.object({
14+
version: z.string(),
15+
repository: z.object({
16+
path: z.string(),
17+
remote: z.string().optional(),
18+
branch: z.string().optional(),
19+
lastCommit: z.string().optional(),
20+
}),
21+
indexed: z
22+
.object({
23+
timestamp: z.string(),
24+
files: z.number().int().nonnegative(),
25+
components: z.number().int().nonnegative(),
26+
size: z.number().int().nonnegative(),
27+
})
28+
.optional(),
29+
config: z
30+
.object({
31+
languages: z.array(z.string()).optional(),
32+
excludePatterns: z.array(z.string()).optional(),
33+
})
34+
.optional(),
35+
migrated: z
36+
.object({
37+
timestamp: z.string(),
38+
from: z.string(),
39+
})
40+
.optional(),
41+
});
42+
43+
export type RepositoryMetadataData = z.infer<typeof RepositoryMetadataSchema>;
44+
45+
/**
46+
* Validate repository metadata from JSON
47+
* Returns null if validation fails (for backward compatibility with current error handling)
48+
*/
49+
export function validateRepositoryMetadata(data: unknown): RepositoryMetadataData | null {
50+
const result = RepositoryMetadataSchema.safeParse(data);
51+
return result.success ? result.data : null;
52+
}

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)