-
Notifications
You must be signed in to change notification settings - Fork 7
Description
Summary
Add convenience helper functions for checking error types, following Go SDK best practices for error handling ergonomics.
Python SDK Parity (100% Verified)
| Python SDK | Go SDK | Fields Match |
|---|---|---|
| ClaudeSDKError | SDKError + BaseError | YES |
| CLIConnectionError | ConnectionError | YES |
| CLINotFoundError | CLINotFoundError | YES (Path) |
| ProcessError | ProcessError | YES (ExitCode, Stderr) |
| CLIJSONDecodeError | JSONDecodeError | YES (Line, Position, OriginalError) |
| MessageParseError | MessageParseError | YES (Data) |
Note: Python SDK has NO Is*/As* helpers - these are Go-specific ergonomic additions following the `os.IsNotExist`, `os.IsPermission` pattern from the Go standard library.
Current State
The SDK defines 5 user-facing error types in `internal/shared/errors.go`:
| Error Type | Location | Type Code | Additional Fields |
|---|---|---|---|
| ConnectionError | L34-49 | "connection_error" | - |
| CLINotFoundError | L51-72 | "cli_not_found_error" | Path |
| ProcessError | L74-104 | "process_error" | ExitCode, Stderr |
| JSONDecodeError | L106-140 | "json_decode_error" | Line, Position, OriginalError |
| MessageParseError | L142-159 | "message_parse_error" | Data |
Current Usage Pattern (verbose):
```go
var connError *claudecode.ConnectionError
if errors.As(err, &connError) {
// Handle connection error
}
var cliError *claudecode.CLINotFoundError
if errors.As(err, &cliError) {
fmt.Printf("Path searched: %s\n", cliError.Path)
}
```
Proposed Addition
Is* Functions (Boolean Type Checks)
Add to `errors.go` after line 41:
```go
// IsConnectionError reports whether err is or wraps a ConnectionError.
func IsConnectionError(err error) bool {
var target *ConnectionError
return errors.As(err, &target)
}
// IsCLINotFoundError reports whether err is or wraps a CLINotFoundError.
func IsCLINotFoundError(err error) bool {
var target *CLINotFoundError
return errors.As(err, &target)
}
// IsProcessError reports whether err is or wraps a ProcessError.
func IsProcessError(err error) bool {
var target *ProcessError
return errors.As(err, &target)
}
// IsJSONDecodeError reports whether err is or wraps a JSONDecodeError.
func IsJSONDecodeError(err error) bool {
var target *JSONDecodeError
return errors.As(err, &target)
}
// IsMessageParseError reports whether err is or wraps a MessageParseError.
func IsMessageParseError(err error) bool {
var target *MessageParseError
return errors.As(err, &target)
}
```
As* Functions (Typed Extraction with Field Access)
```go
// AsConnectionError returns err as a *ConnectionError if it is one, nil otherwise.
func AsConnectionError(err error) *ConnectionError {
var target *ConnectionError
if errors.As(err, &target) {
return target
}
return nil
}
// AsCLINotFoundError returns err as a *CLINotFoundError if it is one, nil otherwise.
func AsCLINotFoundError(err error) *CLINotFoundError {
var target *CLINotFoundError
if errors.As(err, &target) {
return target
}
return nil
}
// AsProcessError returns err as a *ProcessError if it is one, nil otherwise.
func AsProcessError(err error) *ProcessError {
var target *ProcessError
if errors.As(err, &target) {
return target
}
return nil
}
// AsJSONDecodeError returns err as a *JSONDecodeError if it is one, nil otherwise.
func AsJSONDecodeError(err error) *JSONDecodeError {
var target *JSONDecodeError
if errors.As(err, &target) {
return target
}
return nil
}
// AsMessageParseError returns err as a *MessageParseError if it is one, nil otherwise.
func AsMessageParseError(err error) *MessageParseError {
var target *MessageParseError
if errors.As(err, &target) {
return target
}
return nil
}
```
New Usage Pattern (clean):
```go
// Simple boolean check
if claudecode.IsConnectionError(err) {
log.Println("Connection failed, will retry...")
return retryWithBackoff()
}
if claudecode.IsCLINotFoundError(err) {
fmt.Println("Claude CLI not found. Install with: npm install -g @anthropic-ai/claude-code")
return
}
// Field access with As* helpers
if procErr := claudecode.AsProcessError(err); procErr != nil {
fmt.Printf("Process failed with exit code %d: %s\n", procErr.ExitCode, procErr.Stderr)
}
if cliErr := claudecode.AsCLINotFoundError(err); cliErr != nil {
fmt.Printf("CLI not found at path: %s\n", cliErr.Path)
}
```
Implementation Details
Files to Modify
| File | Action | Details |
|---|---|---|
| `errors.go` | ADD | Add "errors" import, 5 Is* functions, 5 As* functions after L41 |
| `errors_helpers_test.go` | CREATE | New test file (~150 lines) |
| `examples/08_client_advanced/main.go` | UPDATE | Use new helpers, remove "errors" import |
Go Best Practices
Reference: Working with Errors in Go 1.13
- Use `errors.As()` internally - Correctly handles wrapped errors (unlike older `os.IsNotExist` pattern)
- Follow `os.IsNotExist` naming - Is* for boolean checks, consistent with stdlib
- Add As for field extraction* - Go-native pattern for typed error access
- Support wrapped errors - Helpers work with `fmt.Errorf("...: %w", err)`
Project Patterns (from internal/shared/errors.go)
- All errors implement `SDKError` interface with `Type()` method
- All errors embed `BaseError` for common functionality
- `Unwrap()` implemented for error chaining
- Comments follow Go doc conventions: `// FunctionName does X`
Benefits
- Reduced boilerplate - No need to declare typed variable for simple checks
- Discoverable API - IDE autocomplete shows available error checks
- Consistent with Go patterns - Similar to `os.IsNotExist`, `os.IsPermission`
- Preserves flexibility - Users can still use `errors.As` directly
- Field access - As* functions provide typed access to error fields
Acceptance Criteria
Implementation
- Add `IsConnectionError(err error) bool`
- Add `IsCLINotFoundError(err error) bool`
- Add `IsProcessError(err error) bool`
- Add `IsJSONDecodeError(err error) bool`
- Add `IsMessageParseError(err error) bool`
- Add `AsConnectionError(err error) *ConnectionError`
- Add `AsCLINotFoundError(err error) *CLINotFoundError`
- Add `AsProcessError(err error) *ProcessError`
- Add `AsJSONDecodeError(err error) *JSONDecodeError`
- Add `AsMessageParseError(err error) *MessageParseError`
Testing (errors_helpers_test.go)
- Table-driven `TestIsErrorHelpers` covering all 5 types
- Test wrapped errors (`fmt.Errorf` with `%w`)
- Test nil error returns false/nil
- Test generic error returns false/nil
- `TestAsErrorHelpers` with field access validation
- Verify Path, ExitCode, Stderr, Line, Position, Data field access
Documentation
- Update `examples/08_client_advanced/main.go` to use new helpers
- Add godoc comments following project conventions
Quality Gates
- `go fmt ./...`
- `go vet ./...`
- `go build ./...`
- `go test ./...` passes
- `go test -cover ./...` shows coverage for new code
Priority
Low - Quality of life improvement, not blocking functionality.
Reference
Standard library patterns:
- `os.IsNotExist(err)`
- `os.IsPermission(err)`
- Working with Errors in Go 1.13