In the previous chapter, Settings Cascade & Resolution, we learned how the application gathers settings from multiple files and merges them into one.
But here is the catch: Merging is blind.
If a user writes "timeout": "five minutes" (a string) but the application expects "timeout": 300 (a number), the merge will succeed, but the application will crash when it tries to do math on the text "five minutes".
We need a rigid system to check data after it is merged but before the application uses it.
Think of Schema Definition as the Border Patrol or a Nightclub Bouncer.
In this project, we use a library called Zod to define these rules.
A Schema is a blueprint that describes exactly what the data should look like.
We define the "shape" of our settings in src/settings/types.ts. We use Zod (z) to create these rules.
Here is a simplified example of what a rule looks like:
// types.ts (Simplified)
export const SettingsSchema = z.object({
// Rule: Must be a number, cannot be negative
cleanupPeriodDays: z.number().nonnegative().optional(),
// Rule: Must be exactly "bash" or "powershell"
defaultShell: z.enum(['bash', 'powershell']).optional(),
})
When the application loads, it passes the merged JSON object through this schema.
As a developer or user, you will mostly interact with this system when you make a mistake. The system is designed to give you Actionable Advice, not just cryptic error codes.
Imagine you are editing your configuration and you make a typo:
{
"cleanupPeriodDays": -5
}
The schema says .nonnegative(). Zod catches this.
What the System Outputs:
Settings validation failed:
- cleanupPeriodDays: Number must be greater than or equal to 0
The system goes a step further. If you make a common mistake, it offers a suggestion.
Input:
{
"env": { "DEBUG": true } // Error: Environment variables must be strings!
}
Output:
- env.DEBUG: Expected string, received boolean.
Tip: Environment variables must be strings. Wrap numbers and booleans in quotes.
Example: "DEBUG": "true"
How do we turn raw validation errors into friendly tips? Let's walk through the process.
The logic is split into three main parts: Defining the rules, Running the check, and Polishing the errors.
types.ts)This file is the "Law Book." It contains the Zod definitions.
// types.ts
export const SettingsSchema = lazySchema(() =>
z.object({
// Defines 'env' as a dictionary where both key and value are strings
env: EnvironmentVariablesSchema().optional(),
// Defines 'cleanupPeriodDays' as a positive integer
cleanupPeriodDays: z.number().nonnegative().int().optional(),
}).passthrough()
)
Note: .passthrough() means "if you see extra keys I don't know about, just ignore them, don't crash."
validation.ts)
This is the engine that runs the check. It uses safeParse so the app doesn't crash on invalid data.
// validation.ts
export function validateSettingsFileContent(content: string) {
const jsonData = jsonParse(content)
// Run the data against the blueprint
const result = SettingsSchema().strict().safeParse(jsonData)
if (result.success) {
return { isValid: true }
}
// If failed, make errors readable
const errors = formatZodError(result.error, 'settings')
return { isValid: false, error: errors }
}
validationTips.ts)This is the "Friendly Support Agent." It looks at the ugly technical error and tries to match it to a helpful hint.
It uses a list of TIP_MATCHERS to find specific context.
// validationTips.ts
const TIP_MATCHERS = [
{
// If the error is in 'env' and the type is wrong...
matches: (ctx) =>
ctx.path.startsWith('env.') && ctx.code === 'invalid_type',
// ...suggest wrapping the value in quotes!
tip: {
suggestion: 'Environment variables must be strings. Wrap numbers in quotes.',
},
},
// ... more rules
]
We have ensured that our configuration data structure is correct. The "Border Patrol" stops numbers from entering fields meant for strings and ensures required formats are respected.
However, some rules are too complex for a simple structure check. For example, allowing a tool to run only if it matches a specific complex command string like Bash(npm run test). For that, we need a specialized parser.
In the next chapter, we will look at how we validate these complex permission strings.
Permission Rule Syntax Validation
Generated by Code IQ