Error Handling
Handle authorization errors and ensure exhaustive type checking with PolicyError and assertNever.
permit provides utilities for handling errors and ensuring exhaustive type checking in your authorization logic.
PolicyError
The PolicyError class is a custom error type for policy-related errors. Use it to distinguish authorization errors from other application errors.
import { PolicyError } from "@zap-studio/permit/errors";Properties
| Property | Type | Description |
|---|---|---|
name | string | Always "PolicyError" |
message | string | Error description |
stack | string | Stack trace (inherited) |
Creating Policy Errors
import { PolicyError } from "@zap-studio/permit/errors";
// Throw when an authorization check fails
throw new PolicyError("User is not authorized to delete this resource");
// Throw for invalid policy configuration
throw new PolicyError("Unknown resource type: 'invalid'");
// Throw for missing context
throw new PolicyError("User context is required for this action");Catching Policy Errors
import { PolicyError } from "@zap-studio/permit/errors";
async function deletePost(postId: string, context: AppContext) {
try {
const post = await getPost(postId);
if (!(await policy.can(context, "delete", "post", post))) {
throw new PolicyError("Not authorized to delete this post");
}
await db.posts.delete(postId);
return { success: true };
} catch (error) {
if (error instanceof PolicyError) {
// Handle authorization errors
return { success: false, error: error.message, code: "FORBIDDEN" };
}
// Re-throw unexpected errors
throw error;
}
}assertNever
The assertNever() helper ensures exhaustive type checking in TypeScript. It causes a compile-time error if a switch statement or if-else chain doesn't handle all possible cases.
import { assertNever } from "@zap-studio/permit/helpers";How It Works
assertNever() accepts a value of type never. If TypeScript can prove that a value could reach assertNever(), it means you've missed a case.
Example: Exhaustive Action Handling
import { assertNever } from "@zap-studio/permit/helpers";
type Action = "read" | "write" | "delete";
function getPermissionLevel(action: Action): number {
switch (action) {
case "read":
return 1;
case "write":
return 2;
case "delete":
return 3;
default:
// TypeScript error if we forget a case
return assertNever(action);
}
}If you add a new action without updating the switch:
type Action = "read" | "write" | "delete" | "archive"; // Added "archive"
function getPermissionLevel(action: Action): number {
switch (action) {
case "read":
return 1;
case "write":
return 2;
case "delete":
return 3;
default:
// TypeScript ERROR: Argument of type 'string' is not assignable to parameter of type 'never'.
return assertNever(action);
}
}Runtime Behavior
If assertNever() is reached at runtime (e.g., due to type assertions or JavaScript calling the function), it throws an error:
import { assertNever } from "@zap-studio/permit/helpers";
// This would throw: Error: Unexpected value: unknown
const badValue = "unknown" as never;
assertNever(badValue);Best Practices
- Use
PolicyErrorfor authorization failures — Makes it easy to distinguish from other errors - Catch errors at boundaries — Handle
PolicyErrorin middleware or API handlers - Include context in error messages — "Not authorized to delete post-123" is better than "Forbidden"
- Use
assertNeverfor exhaustive checks — Especially when handling actions or resource types - Log denied attempts — Track authorization failures for security monitoring
Last updated on