Conversation
|
Note
|
| Cohort / File(s) | Summary |
|---|---|
Error Framework Implementation src/errors/AppError.ts |
New error system with ErrorType categorization, ErrorDefinition interface, generic AppError class with static factory method, HTTP status mapping, and Zod-based type-safe details inference. |
Framework Documentation src/errors/README.md |
Comprehensive guide covering error design patterns, protocol mapping (HTTP/gRPC), migration from legacy error patterns, and usage examples with Zod schemas. |
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~50 minutes
- Type inference and generic constraints: Verify correct behavior of InferDetails type inference and generic AppError class variance
- HTTP status mapping: Confirm ErrorType-to-status mapping is complete and accurate for all error categories
- Zod schema integration: Validate type-safe details handling with optional vs. required schema branches
- Documentation accuracy: Ensure README examples and patterns align with actual implementation behavior and design intent
Pre-merge checks and finishing touches
✅ Passed checks (3 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title clearly summarizes the main change: introducing a type-safe error handling system called AppError, which matches the primary focus of the changeset. |
| Docstring Coverage | ✅ Passed | No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. |
| Description check | ✅ Passed | The pull request description covers the main changes, includes a clear checklist with all items completed, and raises thoughtful review questions about architectural decisions. |
✨ Finishing touches
- 📝 Generate docstrings
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Post copyable unit tests in a comment
- Commit unit tests in branch
add-app-error-poc
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/errors/AppError.ts (1)
152-158: Consider setting a meaningful class name for debuggability.The anonymous class returned by
fromwill have the name of the definition's code in stack traces only if consumers extend it with a named class. For cases whereAppError.from(def)is used directly without extending, the error name in stack traces will be empty or generic.This is minor since the documented pattern encourages creating named subclasses, but you could optionally set the class name:
static from<const T extends ErrorDefinition>(definition: T) { - return class extends AppError<T> { + const ErrorClass = class extends AppError<T> { constructor(options: AppErrorOptions<InferDetails<T>>) { super(definition, options) } } + Object.defineProperty(ErrorClass, 'name', { value: definition.code }) + return ErrorClass }
📜 Review details
Configuration used: Repository: lokalise/coderabbit/.coderabbit.yaml
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/errors/AppError.ts(1 hunks)src/errors/README.md(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/errors/AppError.ts (1)
src/errors/EnhancedError.ts (1)
EnhancedError(60-88)
🪛 GitHub Actions: ci
src/errors/AppError.ts
[error] 2-171: Code coverage for this file is 0% (lines 2-171 are uncovered).
🔇 Additional comments (11)
src/errors/AppError.ts (7)
1-4: LGTM!The import of Zod as a type-only import is appropriate since it's only used for type inference. The
ValueOfutility type is a clean pattern for extracting union types from const objects.
6-34: Well-designed protocol-agnostic error categorization.The error types cover common scenarios and the
as constassertion ensures literal type inference. The pattern of exporting both the const object and a derived union type is idiomatic TypeScript.
44-53: LGTM!The HTTP status mapping is correct, and using
Record<ErrorType, number>ensures compile-time exhaustiveness checking if new error types are added.
64-73: LGTM!The interface is well-structured. Using
z.ZodTypeAnyfor the optional details schema provides flexibility while enabling type inference throughdefineError.
84-84: LGTM!The
consttype parameter ensures that string literals like'PROJECT_NOT_FOUND'remain as literal types rather than widening tostring. This is the key enabler for type discrimination.
94-96: LGTM!The conditional type correctly infers details from the Zod schema when present, falling back to
undefinedotherwise.
106-111: LGTM!The conditional type elegantly handles both cases: making
detailsoptional when no schema is defined, and required when a schema exists. This provides excellent developer experience.src/errors/README.md (4)
1-52: Clear and compelling motivation.The problem statement effectively demonstrates the type safety gap in the old approach, and the listed benefits accurately reflect the implementation.
152-180: LGTM!The protocol mapping examples clearly demonstrate the flexibility of the error type system. The gRPC mapping example provides a good template for users to implement their own protocol mappings.
185-227: LGTM!The examples clearly demonstrate the conditional requirement pattern for error details. The distinction between errors with and without schemas is well illustrated.
229-312: Comprehensive migration guide and best practices.The before/after comparisons make migration straightforward, and the best practices section provides actionable guidance for using the new error system effectively.
| /** Protocol-agnostic error type */ | ||
| readonly type: T['type'] | ||
| /** Whether error details are safe to expose externally */ | ||
| readonly isPublic: T['isPublic'] |
There was a problem hiding this comment.
won't that be boolean in 100% of times?
There was a problem hiding this comment.
Yeah, but currently it will be typed as literal true/false. It could be useful to filter out non-public errors in some error union.
| * @param definition - Error definition with const assertion for literal types | ||
| * @returns Error class with definition bound | ||
| */ | ||
| static from<const T extends ErrorDefinition>(definition: T) { |
There was a problem hiding this comment.
How .from() would be different from a simple *Error extends AppError and you just fill everything internally, while type: ErrorType should theoretically allow to distinguish different errors?
There was a problem hiding this comment.
Using .from() covers having literal types.
Defining error type/code with abstract&override is prone to missing readonly modifier, making error non type-safe. We would have to create some biome custom rule to look for such cases which seems a bit more complex.
| detailsSchema: z.object({ name: z.string() }), | ||
| }) | ||
|
|
||
| export class ProjectNotFoundError extends AppError.from(projectNotFoundDef) {} |
There was a problem hiding this comment.
I don't have strong objective concerns, but this reads "weird".
Probably a more natural way for me to describe an error would be:
export class ProjectNotFoundError extends AppError {
override readonly type = ErrorType.NOT_FOUND
...
}
export class AppError {
abstract readonly type: ErrorType
}
There was a problem hiding this comment.
It was my initial implementation, but unfortunately it doesn't allow us to use the error definition for OpenAPI response typing and is prone to unintentionally missing readonly, which makes the error non-typesafe.
With defineError we can use the error definition for HTTP response typing and the error class creation for BE purposes.
| * | ||
| * @template T - Error definition with literal code type | ||
| */ | ||
| export class AppError<T extends ErrorDefinition> extends EnhancedError { |
There was a problem hiding this comment.
Could we have some generic default here? I mean, to avoid having to create a type for each error 😓
There was a problem hiding this comment.
I'm afraid that it would lead to overusing the generic solution, decreasing the type-safety.
There was a problem hiding this comment.
We can continue using ErrorDefinition as it is; the only difference is whether we need to add it manually or not. I don’t have a strong preference. If it becomes annoying, we can always add it later.
|
It would be beneficial to maintain backward compatibility with I think we can achieve this as follows:
This would allow us to deprecate the old error classes while keeping them fully functional. At the same time, we can prepare the error-handling mechanism to support only the new error type, without requiring an immediate migration. |
| */ | ||
| export interface ErrorDefinition { | ||
| /** Unique error code - becomes a literal type for type discrimination */ | ||
| code: string |
There was a problem hiding this comment.
please give an example of what a code might be
There was a problem hiding this comment.
PROJECT_NAME_DUPLICATED, PROJECT_NOT_FOUND
| /** Whether this error is safe to expose to external consumers */ | ||
| isPublic: boolean | ||
| /** Optional Zod schema for type inference and OpenAPI schema generation */ | ||
| detailsSchema?: z.ZodTypeAny |
There was a problem hiding this comment.
what is the behaviour if schema is not provided? no openapi schema generation or some default schema?
There was a problem hiding this comment.
If no schema is provided, the error details will be strictly typed as undefined, meaning no openapi schema. If we need some sort of freeform details, we can use z.json().
Changes
Introduces AppError, a new error handling system that addresses key limitations in the current InternalError/PublicNonRecoverableError approach.
Questions for Review
@lokalise/errorpackage? Error can be created in the browser, or we might usedefineErrorin some sharedcontractpackage that is then pulled in FE.Checklist
major,minor,patchorskip-releaseSummary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.