Initial QSfera import
This commit is contained in:
+225
@@ -0,0 +1,225 @@
|
||||
// File: inspect.go
|
||||
// Updated to support both error and *Error with delegation for cleaner *Error handling
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
stderrs "errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Inspect provides detailed examination of an error, handling both single errors and MultiError
|
||||
func Inspect(err error) {
|
||||
if err == nil {
|
||||
fmt.Println("No error occurred")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n=== Error Inspection ===\n")
|
||||
fmt.Printf("Top-level error: %v\n", err)
|
||||
fmt.Printf("Top-level error type: %T\n", err)
|
||||
|
||||
// Handle *Error directly
|
||||
if e, ok := err.(*Error); ok {
|
||||
InspectError(e)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle MultiError
|
||||
if multi, ok := err.(*MultiError); ok {
|
||||
allErrors := multi.Errors()
|
||||
fmt.Printf("\nContains %d errors:\n", len(allErrors))
|
||||
for i, e := range allErrors {
|
||||
fmt.Printf("\n--- Error %d ---\n", i+1)
|
||||
inspectSingleError(e)
|
||||
}
|
||||
} else {
|
||||
// Inspect single error if not MultiError or *Error
|
||||
fmt.Println("\n--- Details ---")
|
||||
inspectSingleError(err)
|
||||
}
|
||||
|
||||
// Additional diagnostics
|
||||
fmt.Println("\n--- Diagnostics ---")
|
||||
if IsRetryable(err) {
|
||||
fmt.Println("- Error chain contains retryable errors")
|
||||
}
|
||||
if IsTimeout(err) {
|
||||
fmt.Println("- Error chain contains timeout errors")
|
||||
}
|
||||
if code := getErrorCode(err); code != 0 {
|
||||
fmt.Printf("- Highest priority error code: %d\n", code)
|
||||
}
|
||||
fmt.Printf("========================\n\n")
|
||||
}
|
||||
|
||||
// InspectError provides detailed inspection of a specific *Error instance
|
||||
func InspectError(err *Error) {
|
||||
if err == nil {
|
||||
fmt.Println("No error occurred")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\n=== Error Inspection (*Error) ===\n")
|
||||
fmt.Printf("Top-level error: %v\n", err)
|
||||
fmt.Printf("Top-level error type: %T\n", err)
|
||||
|
||||
fmt.Println("\n--- Details ---")
|
||||
inspectSingleError(err) // Delegate to handle unwrapping and details
|
||||
|
||||
// Additional diagnostics specific to *Error
|
||||
fmt.Println("\n--- Diagnostics ---")
|
||||
if IsRetryable(err) {
|
||||
fmt.Println("- Error is retryable")
|
||||
}
|
||||
if IsTimeout(err) {
|
||||
fmt.Println("- Error chain contains timeout errors")
|
||||
}
|
||||
if code := err.Code(); code != 0 {
|
||||
fmt.Printf("- Error code: %d\n", code)
|
||||
}
|
||||
fmt.Printf("========================\n\n")
|
||||
}
|
||||
|
||||
// inspectSingleError handles inspection of a single error (may be part of a chain)
|
||||
func inspectSingleError(err error) {
|
||||
if err == nil {
|
||||
fmt.Println(" (nil error)")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf(" Error: %v\n", err)
|
||||
fmt.Printf(" Type: %T\n", err)
|
||||
|
||||
// Handle wrapped errors, including *Error type
|
||||
var currentErr error = err
|
||||
depth := 0
|
||||
for currentErr != nil {
|
||||
prefix := strings.Repeat(" ", depth+1)
|
||||
if depth > 0 {
|
||||
fmt.Printf("%sWrapped Cause (%T): %v\n", prefix, currentErr, currentErr)
|
||||
}
|
||||
|
||||
// Check if it's our specific *Error type
|
||||
if e, ok := currentErr.(*Error); ok {
|
||||
if name := e.Name(); name != "" {
|
||||
fmt.Printf("%sName: %s\n", prefix, name)
|
||||
}
|
||||
if cat := e.Category(); cat != "" {
|
||||
fmt.Printf("%sCategory: %s\n", prefix, cat)
|
||||
}
|
||||
if code := e.Code(); code != 0 {
|
||||
fmt.Printf("%sCode: %d\n", prefix, code)
|
||||
}
|
||||
if ctx := e.Context(); len(ctx) > 0 {
|
||||
fmt.Printf("%sContext:\n", prefix)
|
||||
for k, v := range ctx {
|
||||
fmt.Printf("%s %s: %v\n", prefix, k, v)
|
||||
}
|
||||
}
|
||||
if stack := e.Stack(); len(stack) > 0 {
|
||||
fmt.Printf("%sStack (Top 3):\n", prefix)
|
||||
limit := 3
|
||||
if len(stack) < limit {
|
||||
limit = len(stack)
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
fmt.Printf("%s %s\n", prefix, stack[i])
|
||||
}
|
||||
if len(stack) > limit {
|
||||
fmt.Printf("%s ... (%d more frames)\n", prefix, len(stack)-limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap using standard errors.Unwrap and handle *Error Unwrap
|
||||
var nextErr error
|
||||
// Prioritize *Error's Unwrap if available AND it returns non-nil
|
||||
if e, ok := currentErr.(*Error); ok {
|
||||
unwrapped := e.Unwrap()
|
||||
if unwrapped != nil {
|
||||
nextErr = unwrapped
|
||||
} else {
|
||||
// If *Error.Unwrap returns nil, fall back to standard unwrap
|
||||
// This handles cases where *Error might wrap a non-standard error
|
||||
// or where its internal cause is deliberately nil.
|
||||
nextErr = stderrs.Unwrap(currentErr)
|
||||
}
|
||||
} else {
|
||||
nextErr = stderrs.Unwrap(currentErr) // Fall back to standard unwrap for non-*Error types
|
||||
}
|
||||
|
||||
// Prevent infinite loops if Unwrap returns the same error, or stop if no more unwrapping
|
||||
if nextErr == currentErr || nextErr == nil {
|
||||
break
|
||||
}
|
||||
currentErr = nextErr
|
||||
depth++
|
||||
if depth > 10 { // Safety break for very deep or potentially cyclic chains
|
||||
fmt.Printf("%s... (chain too deep or potential cycle)\n", strings.Repeat(" ", depth+1))
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getErrorCode traverses the error chain to find the highest priority code.
|
||||
// It uses errors.As to find the first *Error in the chain.
|
||||
func getErrorCode(err error) int {
|
||||
var code int = 0 // Default code
|
||||
var target *Error
|
||||
if As(err, &target) { // Use the package's As helper
|
||||
if target != nil { // Add nil check for safety
|
||||
code = target.Code()
|
||||
}
|
||||
}
|
||||
// If the top-level error is *Error and has a code, it might take precedence.
|
||||
// This depends on desired logic. Let's keep it simple for now: first code found by As.
|
||||
if code == 0 { // Only check top-level if As didn't find one with a code
|
||||
if e, ok := err.(*Error); ok {
|
||||
code = e.Code()
|
||||
}
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
// handleError demonstrates using Inspect with additional handling logic
|
||||
func handleError(err error) {
|
||||
fmt.Println("\n=== Processing Failure ===")
|
||||
Inspect(err) // Use the primary Inspect function
|
||||
|
||||
// Additional handling based on inspection
|
||||
code := getErrorCode(err) // Use the helper
|
||||
|
||||
switch {
|
||||
case IsTimeout(err):
|
||||
fmt.Println("\nAction: Check connectivity or increase timeout")
|
||||
case code == 402: // Check code obtained via helper
|
||||
fmt.Println("\nAction: Payment processing failed - notify billing")
|
||||
default:
|
||||
fmt.Println("\nAction: Generic failure handling")
|
||||
}
|
||||
}
|
||||
|
||||
// processOrder demonstrates Chain usage with Inspect
|
||||
func processOrder() error {
|
||||
validateInput := func() error { return nil }
|
||||
processPayment := func() error { return stderrs.New("credit card declined") }
|
||||
sendNotification := func() error { fmt.Println("Notification sent."); return nil }
|
||||
logOrder := func() error { fmt.Println("Order logged."); return nil }
|
||||
|
||||
chain := NewChain(ChainWithTimeout(2*time.Second)).
|
||||
Step(validateInput).Tag("validation").
|
||||
Step(processPayment).Tag("billing").Code(402).Retry(3, 100*time.Millisecond, WithRetryIf(IsRetryable)).
|
||||
Step(sendNotification).Optional().
|
||||
Step(logOrder)
|
||||
|
||||
err := chain.Run()
|
||||
if err != nil {
|
||||
handleError(err) // Call the unified error handler
|
||||
return err // Propagate the error if needed
|
||||
}
|
||||
fmt.Println("Order processed successfully!")
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user