Documentation
¶
Overview ¶
Package trogonerror provides a comprehensive, standardized error handling system for distributed applications based on the TrogonError specification.
TrogonError offers structured error handling with standardized error codes, rich metadata, visibility controls, internationalization support, and cause chaining for better debugging and error correlation.
Production Usage with Error Templates (Recommended) ¶
For production applications, use error templates to define reusable error patterns. Templates ensure consistency across your application and reduce code duplication:
var (
ErrUserNotFound = trogonerror.NewErrorTemplate("shopify.users", "NOT_FOUND",
trogonerror.TemplateWithCode(trogonerror.CodeNotFound))
ErrValidationFailed = trogonerror.NewErrorTemplate("shopify.validation", "INVALID_INPUT",
trogonerror.TemplateWithCode(trogonerror.CodeInvalidArgument))
ErrDatabaseError = trogonerror.NewErrorTemplate("shopify.database", "CONNECTION_FAILED",
trogonerror.TemplateWithCode(trogonerror.CodeInternal))
)
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrUserNotFound.NewError(
trogonerror.WithMetadataValuef(trogonerror.VisibilityPublic, "userId", "gid://shopify/Customer/%s", id))
}
return nil, ErrDatabaseError.NewError(trogonerror.WithWrap(err))
}
return user, nil
}
Basic Error Creation ¶
Create errors using functional options:
err := trogonerror.NewError("shopify.users", "NOT_FOUND",
trogonerror.WithCode(trogonerror.CodeNotFound),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "userId", "gid://shopify/Customer/1234567890"))
Error Codes and HTTP Status Mapping ¶
TrogonError supports 16 standardized error codes that map to HTTP status codes:
Code HTTP Status Description ---- ----------- ----------- Cancelled 499 Request cancelled Unknown 500 Unknown error InvalidArgument 400 Invalid request parameters DeadlineExceeded 504 Request timeout NotFound 404 Resource not found AlreadyExists 409 Resource already exists PermissionDenied 403 Access denied Unauthenticated 401 Authentication required ResourceExhausted 429 Rate limit exceeded FailedPrecondition 400 Precondition failed Aborted 409 Operation aborted OutOfRange 400 Value out of range Unimplemented 501 Not implemented Internal 500 Internal server error Unavailable 503 Service unavailable DataLoss 500 Data corruption
Access code information:
code := trogonerror.CodeNotFound fmt.Println(code.String()) // "NOT_FOUND" fmt.Println(code.HttpStatusCode()) // 404 fmt.Println(code.Message()) // "resource not found"
Visibility Control System ¶
TrogonError implements a three-tier visibility system to control information disclosure:
VisibilityInternal: Only visible within the same service/process
VisibilityPrivate: Visible across internal services (not to external users)
VisibilityPublic: Safe to expose to external users
err := trogonerror.NewError("shopify.auth", "ACCESS_DENIED",
trogonerror.WithCode(trogonerror.CodePermissionDenied),
trogonerror.WithVisibility(trogonerror.VisibilityPublic),
trogonerror.WithMetadataValue(trogonerror.VisibilityInternal, "userId", "gid://shopify/Customer/1234567890"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "resource", "/admin/customers"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "action", "DELETE"))
Rich Metadata with Formatting ¶
Add structured metadata with visibility controls and printf-style formatting:
err := trogonerror.NewError("shopify.orders", "ORDER_PROCESSING_FAILED",
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "orderId", "gid://shopify/Order/5432109876"),
trogonerror.WithMetadataValuef(trogonerror.VisibilityPublic, "customerId", "gid://shopify/Customer/%s", userID),
trogonerror.WithMetadataValuef(trogonerror.VisibilityPublic, "amount", "$%.2f", float64(amount)/100))
Error Chaining and Wrapping ¶
Chain errors to preserve context while wrapping standard Go errors:
dbErr := trogonerror.NewError("shopify.database", "CONNECTION_FAILED",
trogonerror.WithCode(trogonerror.CodeInternal))
serviceErr := trogonerror.NewError("shopify.users", "USER_FETCH_FAILED",
trogonerror.WithCode(trogonerror.CodeInternal),
trogonerror.WithCause(dbErr))
// Wrap standard Go errors
originalErr := fmt.Errorf("connection timeout")
wrappedErr := trogonerror.NewError("shopify.database", "CONNECTION_TIMEOUT",
trogonerror.WithCode(trogonerror.CodeUnavailable),
trogonerror.WithWrap(originalErr),
trogonerror.WithErrorMessage(originalErr))
Debugging Support ¶
Capture stack traces and debug information for internal debugging:
err := trogonerror.NewError("shopify.database", "QUERY_TIMEOUT",
trogonerror.WithCode(trogonerror.CodeInternal),
trogonerror.WithStackTrace(),
trogonerror.WithDebugDetail("Query execution exceeded 30s timeout"),
trogonerror.WithMetadataValue(trogonerror.VisibilityInternal, "query", "SELECT * FROM users WHERE id = $1"))
// Access debug information
if debugInfo := err.DebugInfo(); debugInfo != nil {
fmt.Println("Detail:", debugInfo.Detail())
fmt.Println("Stack entries:", len(debugInfo.StackEntries()))
}
Retry Guidance ¶
Provide retry guidance with duration offset or absolute time:
// Retry after a duration
err := trogonerror.NewError("shopify.api", "RATE_LIMIT_EXCEEDED",
trogonerror.WithCode(trogonerror.CodeResourceExhausted),
trogonerror.WithRetryInfoDuration(60*time.Second),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "limit", "1000"))
// Retry at specific time
retryTime := time.Now().Add(5 * time.Minute)
err := trogonerror.NewError("shopify.maintenance", "SERVICE_UNAVAILABLE",
trogonerror.WithCode(trogonerror.CodeUnavailable),
trogonerror.WithRetryTime(retryTime))
Help Links and User Guidance ¶
Provide actionable help links with formatting support:
err := trogonerror.NewError("shopify.users", "INVALID_EMAIL",
trogonerror.WithCode(trogonerror.CodeInvalidArgument),
trogonerror.WithHelpLink("Fix Email", "https://admin.shopify.com/customers/1234567890/edit#email"),
trogonerror.WithHelpLinkf("Customer Console", "https://admin.shopify.com/customers/%s/help", userID))
Internationalization ¶
Support localized error messages:
err := trogonerror.NewError("shopify.users", "NOT_FOUND",
trogonerror.WithCode(trogonerror.CodeNotFound),
trogonerror.WithLocalizedMessage("es-ES", "Usuario no encontrado"))
fmt.Println(err.Message()) // "resource not found" (default)
fmt.Println(err.LocalizedMessage().Message()) // "Usuario no encontrado"
Error Mutation with WithChanges ¶
Create modified copies of errors efficiently:
original := trogonerror.NewError("shopify.orders", "ORDER_FAILED",
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "orderId", "gid://shopify/Order/12345"))
modified := original.WithChanges(
trogonerror.WithChangeID("error-123"),
trogonerror.WithChangeSourceID("payment-service"),
trogonerror.WithChangeMetadataValuef(trogonerror.VisibilityPublic, "customerId", "gid://shopify/Customer/%s", userID))
Standard Go Error Compatibility ¶
TrogonError implements the standard Go error interface and works with errors.Is, errors.As, and error wrapping:
// Type assertion
if tErr, ok := err.(*trogonerror.TrogonError); ok {
fmt.Printf("Domain: %s, Reason: %s\n", tErr.Domain(), tErr.Reason())
}
// errors.Is comparison (by domain + reason)
if errors.Is(err, ErrUserNotFound.NewError()) {
fmt.Println("User not found")
}
// errors.As type assertion
var tErr *trogonerror.TrogonError
if errors.As(err, &tErr) {
fmt.Printf("Code: %s, HTTP Status: %d\n", tErr.Code().String(), tErr.Code().HttpStatusCode())
}
Template-Based Error Checking ¶
Use template.Is() for efficient error type checking:
if ErrUserNotFound.Is(err) {
fmt.Println("This is a user not found error")
}
Complete Example ¶
Here's a complete example showing production usage:
var ErrPaymentFailed = trogonerror.NewErrorTemplate("shopify.payments", "PAYMENT_DECLINED",
trogonerror.TemplateWithCode(trogonerror.CodeInternal))
func ProcessPayment(orderID, userID string, amount int) error {
// Simulate payment processing
if amount > 100000 { // Over $1000
return ErrPaymentFailed.NewError(
trogonerror.WithMessage("Payment amount exceeds limit"),
trogonerror.WithMetadataValuef(trogonerror.VisibilityPublic, "orderId", "gid://shopify/Order/%s", orderID),
trogonerror.WithMetadataValuef(trogonerror.VisibilityPublic, "customerId", "gid://shopify/Customer/%s", userID),
trogonerror.WithMetadataValuef(trogonerror.VisibilityPublic, "amount", "$%.2f", float64(amount)/100),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "currency", "USD"),
trogonerror.WithHelpLinkf("Contact Support", "https://admin.shopify.com/support/new?order_id=%s", orderID),
trogonerror.WithRetryInfoDuration(30*time.Minute))
}
return nil
}
Index ¶
- Constants
- type ChangeOption
- func WithChangeHelpLink(description, url string) ChangeOption
- func WithChangeHelpLinkf(description, urlFormat string, args ...any) ChangeOption
- func WithChangeID(id string) ChangeOption
- func WithChangeLocalizedMessage(locale, message string) ChangeOption
- func WithChangeMetadata(metadata map[string]MetadataValue) ChangeOption
- func WithChangeMetadataValue(visibility Visibility, key, value string) ChangeOption
- func WithChangeMetadataValuef(visibility Visibility, key, valueFormat string, args ...any) ChangeOption
- func WithChangeRetryInfoDuration(retryOffset time.Duration) ChangeOption
- func WithChangeRetryTime(retryTime time.Time) ChangeOption
- func WithChangeSourceID(sourceID string) ChangeOption
- func WithChangeTime(timestamp time.Time) ChangeOption
- type Code
- type DebugInfo
- type ErrorOption
- func WithCause(causes ...*TrogonError) ErrorOption
- func WithCode(code Code) ErrorOption
- func WithDebugDetail(detail string) ErrorOption
- func WithDebugInfo(debugInfo DebugInfo) ErrorOption
- func WithErrorMessage(err error) ErrorOption
- func WithHelp(help Help) ErrorOption
- func WithHelpLink(description, url string) ErrorOption
- func WithHelpLinkf(description, urlFormat string, args ...any) ErrorOption
- func WithID(id string) ErrorOption
- func WithLocalizedMessage(locale, message string) ErrorOption
- func WithMessage(message string) ErrorOption
- func WithMetadata(metadata map[string]MetadataValue) ErrorOption
- func WithMetadataValue(visibility Visibility, key, value string) ErrorOption
- func WithMetadataValuef(visibility Visibility, key, valueFormat string, args ...any) ErrorOption
- func WithRetryInfoDuration(retryOffset time.Duration) ErrorOption
- func WithRetryTime(retryTime time.Time) ErrorOption
- func WithSourceID(sourceID string) ErrorOption
- func WithStackTrace() ErrorOption
- func WithStackTraceDepth(maxDepth int) ErrorOption
- func WithSubject(subject string) ErrorOption
- func WithTime(timestamp time.Time) ErrorOption
- func WithVisibility(visibility Visibility) ErrorOption
- func WithWrap(err error) ErrorOption
- type ErrorTemplate
- type Help
- type HelpLink
- type LocalizedMessage
- type Metadata
- type MetadataValue
- type RetryInfo
- type TemplateOption
- type TrogonError
- func (e TrogonError) Causes() []*TrogonError
- func (e TrogonError) Code() Code
- func (e TrogonError) DebugInfo() *DebugInfo
- func (e TrogonError) Domain() string
- func (e TrogonError) Error() string
- func (e TrogonError) Help() *Help
- func (e TrogonError) ID() string
- func (e TrogonError) Is(target error) bool
- func (e TrogonError) LocalizedMessage() *LocalizedMessage
- func (e TrogonError) Message() string
- func (e TrogonError) Metadata() Metadata
- func (e TrogonError) Reason() string
- func (e TrogonError) RetryInfo() *RetryInfo
- func (e TrogonError) SourceID() string
- func (e TrogonError) SpecVersion() int
- func (e TrogonError) Subject() string
- func (e TrogonError) Time() *time.Time
- func (e TrogonError) Unwrap() error
- func (e TrogonError) Visibility() Visibility
- func (e *TrogonError) WithChanges(changes ...ChangeOption) *TrogonError
- type Visibility
Examples ¶
- As (IdiomaticErrorHandling)
- Code (Methods)
- Code (Utilities)
- ErrorTemplate
- ErrorTemplate (Production)
- ErrorTemplate (Reusable)
- NewError
- NewError (Basic)
- NewError (RichMetadata)
- TrogonError (VisibilityControl)
- WithCause
- WithCause (ErrorChaining)
- WithHelpLink (Documentation)
- WithLocalizedMessage (Internationalization)
- WithMetadataValuef (FormattedValues)
- WithRetryInfoDuration (RetryLogic)
- WithRetryTime (AbsoluteRetry)
- WithStackTrace (Debugging)
- WithWrap (StandardErrors)
Constants ¶
const SpecVersion = 1
SpecVersion represents the version of the error specification
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ChangeOption ¶
type ChangeOption func(*TrogonError)
ChangeOption represents a change to apply to a TrogonError
func WithChangeHelpLink ¶
func WithChangeHelpLink(description, url string) ChangeOption
WithChangeHelpLink adds a help link with a static URL (appends to existing help). Use WithChangeHelpLinkf for URLs that need parameter interpolation.
func WithChangeHelpLinkf ¶
func WithChangeHelpLinkf(description, urlFormat string, args ...any) ChangeOption
WithChangeHelpLinkf adds a help link with printf-style formatting for the URL (appends to existing help). Example: WithChangeHelpLinkf("Order Details", "https://console.myapp.com/orders/%s", orderID)
func WithChangeLocalizedMessage ¶
func WithChangeLocalizedMessage(locale, message string) ChangeOption
WithChangeLocalizedMessage sets localized message (replaces existing)
func WithChangeMetadata ¶
func WithChangeMetadata(metadata map[string]MetadataValue) ChangeOption
WithChangeMetadata sets metadata with explicit visibility control
func WithChangeMetadataValue ¶
func WithChangeMetadataValue(visibility Visibility, key, value string) ChangeOption
WithChangeMetadataValue sets a single metadata entry with specific visibility
func WithChangeMetadataValuef ¶
func WithChangeMetadataValuef(visibility Visibility, key, valueFormat string, args ...any) ChangeOption
WithChangeMetadataValuef sets a single metadata entry with printf-style formatting for the value Example: WithChangeMetadataValuef(trogonerror.VisibilityPublic, "orderId", "gid://shopify/Order/%s", orderID)
func WithChangeRetryInfoDuration ¶
func WithChangeRetryInfoDuration(retryOffset time.Duration) ChangeOption
WithChangeRetryInfoDuration sets retry duration (replaces existing retry info)
func WithChangeRetryTime ¶
func WithChangeRetryTime(retryTime time.Time) ChangeOption
WithChangeRetryTime sets absolute retry time (replaces existing retry info)
func WithChangeSourceID ¶
func WithChangeSourceID(sourceID string) ChangeOption
WithChangeSourceID sets the source ID
func WithChangeTime ¶
func WithChangeTime(timestamp time.Time) ChangeOption
WithChangeTime sets the timestamp
type Code ¶
type Code int
Code represents standardized error codes that map to HTTP status codes
Example (Methods) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
code := trogonerror.CodeNotFound
fmt.Println(code.String())
fmt.Println(code.HttpStatusCode())
}
Output: NOT_FOUND 404
Example (Utilities) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
// Working with error codes
code := trogonerror.CodeNotFound
fmt.Printf("Code string: %s\n", code.String())
fmt.Printf("HTTP status: %d\n", code.HttpStatusCode())
fmt.Printf("Default message: %s\n", code.Message())
err := trogonerror.NewError("shopify.core", "INVALID_REQUEST", trogonerror.WithCode(trogonerror.CodeInvalidArgument))
if err.Code() == trogonerror.CodeInvalidArgument {
fmt.Println("This is an invalid argument error")
}
}
Output: Code string: NOT_FOUND HTTP status: 404 Default message: resource not found This is an invalid argument error
func (Code) HttpStatusCode ¶
type DebugInfo ¶
type DebugInfo struct {
// contains filtered or unexported fields
}
DebugInfo contains technical details for internal debugging
func (DebugInfo) StackEntries ¶
StackEntries converts the runtime.Frame objects to formatted strings
func (DebugInfo) StackFrames ¶
StackFrames returns the raw runtime.Frame objects for advanced use cases
type ErrorOption ¶
type ErrorOption func(*TrogonError)
ErrorOption represents options for error construction
func WithCause ¶
func WithCause(causes ...*TrogonError) ErrorOption
WithCause adds one or more causes to the error
Example ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
dbErr := trogonerror.NewError("shopify.database", "CONNECTION_FAILED",
trogonerror.WithCode(trogonerror.CodeInternal),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "host", "postgres-primary.shopify.com"))
serviceErr := trogonerror.NewError("shopify.users", "USER_FETCH_FAILED",
trogonerror.WithCode(trogonerror.CodeInternal),
trogonerror.WithMessage("Failed to fetch user data"),
trogonerror.WithCause(dbErr))
fmt.Println(serviceErr.Error())
fmt.Println(len(serviceErr.Causes()))
fmt.Println(serviceErr.Causes()[0].Domain())
}
Output: Failed to fetch user data visibility: INTERNAL domain: shopify.users reason: USER_FETCH_FAILED code: INTERNAL 1 shopify.database
Example (ErrorChaining) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
dbErr := trogonerror.NewError("shopify.database", "CONNECTION_FAILED",
trogonerror.WithCode(trogonerror.CodeUnavailable),
trogonerror.WithMessage("Database connection timeout"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "host", "postgres-primary.shopify.com"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "port", "5432"))
serviceErr := trogonerror.NewError("shopify.users", "USER_FETCH_FAILED",
trogonerror.WithCode(trogonerror.CodeInternal),
trogonerror.WithMessage("Failed to fetch user data"),
trogonerror.WithCause(dbErr))
fmt.Printf("Service error: %s\n", serviceErr.Reason())
fmt.Printf("Has causes: %v\n", len(serviceErr.Causes()) > 0)
if len(serviceErr.Causes()) > 0 {
fmt.Printf("Root cause: %s\n", serviceErr.Causes()[0].Reason())
fmt.Printf("Root cause domain: %s\n", serviceErr.Causes()[0].Domain())
}
}
Output: Service error: USER_FETCH_FAILED Has causes: true Root cause: CONNECTION_FAILED Root cause domain: shopify.database
func WithDebugDetail ¶
func WithDebugDetail(detail string) ErrorOption
WithDebugDetail sets debug detail message without capturing stack trace
func WithDebugInfo ¶
func WithDebugInfo(debugInfo DebugInfo) ErrorOption
WithDebugInfo sets debug information (for internal use only)
func WithErrorMessage ¶
func WithErrorMessage(err error) ErrorOption
WithErrorMessage sets the error message to the error's Error() string
func WithHelpLink ¶
func WithHelpLink(description, url string) ErrorOption
WithHelpLink adds a help link with a static URL. Use WithHelpLinkf for URLs that need parameter interpolation.
Example (Documentation) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
// Error with help links for user guidance
err := trogonerror.NewError("shopify.users", "INVALID_EMAIL",
trogonerror.WithCode(trogonerror.CodeInvalidArgument),
trogonerror.WithMessage("Email address format is invalid"),
trogonerror.WithSubject("/email"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "fieldName", "email"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "providedValue", "invalid-email"),
trogonerror.WithHelpLink("Fix Email", "https://admin.shopify.com/customers/1234567890/edit#email"),
trogonerror.WithHelpLink("Contact Support", "https://admin.shopify.com/support/new?customer_id=1234567890"))
if help := err.Help(); help != nil {
fmt.Printf("Help links available: %d\n", len(help.Links()))
for _, link := range help.Links() {
fmt.Printf("- %s: %s\n", link.Description(), link.URL())
}
}
}
Output: Help links available: 2 - Fix Email: https://admin.shopify.com/customers/1234567890/edit#email - Contact Support: https://admin.shopify.com/support/new?customer_id=1234567890
func WithHelpLinkf ¶
func WithHelpLinkf(description, urlFormat string, args ...any) ErrorOption
WithHelpLinkf adds a help link with printf-style formatting for the URL. Example: WithHelpLinkf("User Console", "https://console.myapp.com/users/%s", userID)
func WithLocalizedMessage ¶
func WithLocalizedMessage(locale, message string) ErrorOption
WithLocalizedMessage sets localized message
Example (Internationalization) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
err := trogonerror.NewError("shopify.users", "NOT_FOUND",
trogonerror.WithCode(trogonerror.CodeNotFound),
trogonerror.WithLocalizedMessage("es-ES", "Usuario no encontrado"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "userId", "gid://shopify/Customer/7890123456"))
fmt.Printf("Default message: %s\n", err.Message())
if localizedMsg := err.LocalizedMessage(); localizedMsg != nil {
fmt.Printf("Localized (%s): %s\n", localizedMsg.Locale(), localizedMsg.Message())
}
}
Output: Default message: resource not found Localized (es-ES): Usuario no encontrado
func WithMetadata ¶
func WithMetadata(metadata map[string]MetadataValue) ErrorOption
WithMetadata sets metadata with explicit visibility control
func WithMetadataValue ¶
func WithMetadataValue(visibility Visibility, key, value string) ErrorOption
WithMetadataValue sets a single metadata entry with specific visibility
func WithMetadataValuef ¶
func WithMetadataValuef(visibility Visibility, key, valueFormat string, args ...any) ErrorOption
WithMetadataValuef sets a single metadata entry with printf-style formatting for the value Example: WithMetadataValuef(trogonerror.VisibilityPublic, "orderId", "gid://shopify/Order/%s", orderID)
Example (FormattedValues) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
// Using printf-style formatting for metadata values
userID := "1234567890"
orderID := "5432109876"
amount := 29999 // cents
err := trogonerror.NewError("shopify.orders", "ORDER_PROCESSING_FAILED",
trogonerror.WithCode(trogonerror.CodeInternal),
trogonerror.WithMetadataValuef(trogonerror.VisibilityPublic, "customerId", "gid://shopify/Customer/%s", userID),
trogonerror.WithMetadataValuef(trogonerror.VisibilityPublic, "orderId", "gid://shopify/Order/%s", orderID),
trogonerror.WithMetadataValuef(trogonerror.VisibilityPublic, "amount", "$%.2f", float64(amount)/100),
trogonerror.WithMetadataValuef(trogonerror.VisibilityInternal, "requestId", "req_%s_%s", userID, orderID))
fmt.Printf("Customer ID: %s\n", err.Metadata()["customerId"].Value())
fmt.Printf("Order ID: %s\n", err.Metadata()["orderId"].Value())
fmt.Printf("Amount: %s\n", err.Metadata()["amount"].Value())
fmt.Printf("Request ID: %s\n", err.Metadata()["requestId"].Value())
}
Output: Customer ID: gid://shopify/Customer/1234567890 Order ID: gid://shopify/Order/5432109876 Amount: $299.99 Request ID: req_1234567890_5432109876
func WithRetryInfoDuration ¶
func WithRetryInfoDuration(retryOffset time.Duration) ErrorOption
WithRetryInfoDuration sets retry information with a duration offset Following ADR: servers MUST set either retry_offset OR retry_time, never both
Example (RetryLogic) ¶
package main
import (
"fmt"
"time"
"github.com/TrogonStack/trogonerror"
)
func main() {
// Error with retry information for rate limiting
err := trogonerror.NewError("shopify.api", "RATE_LIMIT_EXCEEDED",
trogonerror.WithCode(trogonerror.CodeResourceExhausted),
trogonerror.WithMessage("API rate limit exceeded"),
trogonerror.WithRetryInfoDuration(60*time.Second),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "limit", "100"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "timeWindow", "1m"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "remaining", "0"))
if retryInfo := err.RetryInfo(); retryInfo != nil {
if retryOffset := retryInfo.RetryOffset(); retryOffset != nil {
fmt.Printf("Retry after: %s\n", retryOffset.String())
}
}
fmt.Printf("Rate limit: %s\n", err.Metadata()["limit"].Value())
fmt.Printf("Window: %s\n", err.Metadata()["timeWindow"].Value())
}
Output: Retry after: 1m0s Rate limit: 100 Window: 1m
func WithRetryTime ¶
func WithRetryTime(retryTime time.Time) ErrorOption
WithRetryTime sets retry information with an absolute time Following ADR: servers MUST set either retry_offset OR retry_time, never both
Example (AbsoluteRetry) ¶
package main
import (
"fmt"
"time"
"github.com/TrogonStack/trogonerror"
)
func main() {
retryTime := time.Date(2024, 1, 15, 14, 35, 0, 0, time.UTC)
err := trogonerror.NewError("shopify.maintenance", "SERVICE_UNAVAILABLE",
trogonerror.WithCode(trogonerror.CodeUnavailable),
trogonerror.WithMessage("Service temporarily unavailable for maintenance"),
trogonerror.WithRetryTime(retryTime),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "maintenanceWindow", "30min"))
if retryInfo := err.RetryInfo(); retryInfo != nil {
if retryTime := retryInfo.RetryTime(); retryTime != nil {
fmt.Printf("Retry at: %s\n", retryTime.Format("2006-01-02 15:04:05 UTC"))
}
}
fmt.Printf("Maintenance window: %s\n", err.Metadata()["maintenanceWindow"].Value())
}
Output: Retry at: 2024-01-15 14:35:00 UTC Maintenance window: 30min
func WithStackTrace ¶
func WithStackTrace() ErrorOption
WithStackTrace annotates the error with a stack trace at the point WithStackTrace was called This captures the current call stack for debugging purposes (internal use only)
Example (Debugging) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
// Error with stack trace for debugging
err := trogonerror.NewError("shopify.database", "QUERY_TIMEOUT",
trogonerror.WithCode(trogonerror.CodeInternal),
trogonerror.WithStackTrace(),
trogonerror.WithDebugDetail("Database query failed with timeout"),
trogonerror.WithMetadataValue(trogonerror.VisibilityInternal, "query", "SELECT * FROM customers WHERE id = $1"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "requestId", "req_2024_01_15_db_query_abc123"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "duration", "1.5s"))
fmt.Printf("Has debug info: %v\n", err.DebugInfo() != nil)
if err.DebugInfo() != nil {
fmt.Printf("Debug detail: %s\n", err.DebugInfo().Detail())
fmt.Printf("Stack entries count: %d\n", len(err.DebugInfo().StackEntries()))
}
}
Output: Has debug info: true Debug detail: Database query failed with timeout Stack entries count: 9
func WithStackTraceDepth ¶
func WithStackTraceDepth(maxDepth int) ErrorOption
WithStackTraceDepth annotates the error with a stack trace up to the specified depth
func WithVisibility ¶
func WithVisibility(visibility Visibility) ErrorOption
WithVisibility sets the error visibility
func WithWrap ¶
func WithWrap(err error) ErrorOption
WithWrap wraps an existing error
Example (StandardErrors) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
originalErr := fmt.Errorf("connection timeout after 30s")
wrappedErr := trogonerror.NewError("shopify.database", "CONNECTION_TIMEOUT",
trogonerror.WithCode(trogonerror.CodeUnavailable),
trogonerror.WithWrap(originalErr),
trogonerror.WithErrorMessage(originalErr),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "timeout", "30s"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "host", "postgres-primary.shopify.com"))
fmt.Printf("Wrapped error domain: %s\n", wrappedErr.Domain())
fmt.Printf("Wrapped error reason: %s\n", wrappedErr.Reason())
fmt.Printf("Original message preserved: %v\n", wrappedErr.Message() == originalErr.Error())
}
Output: Wrapped error domain: shopify.database Wrapped error reason: CONNECTION_TIMEOUT Original message preserved: true
type ErrorTemplate ¶
type ErrorTemplate struct {
// contains filtered or unexported fields
}
ErrorTemplate represents a reusable error definition
Example ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
template := trogonerror.NewErrorTemplate("shopify.users", "NOT_FOUND",
trogonerror.TemplateWithCode(trogonerror.CodeNotFound))
err := template.NewError(
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "userId", "gid://shopify/Customer/1234567890"))
fmt.Println(err.Error())
fmt.Println(err.Domain())
fmt.Println(err.Reason())
}
Output: resource not found visibility: INTERNAL domain: shopify.users reason: NOT_FOUND code: NOT_FOUND metadata: - userId: gid://shopify/Customer/1234567890 visibility=PUBLIC shopify.users NOT_FOUND
Example (Production) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
var (
ErrUserNotFound = trogonerror.NewErrorTemplate("shopify.users", "NOT_FOUND",
trogonerror.TemplateWithCode(trogonerror.CodeNotFound),
trogonerror.TemplateWithCode(trogonerror.CodeNotFound))
ErrInvalidInput = trogonerror.NewErrorTemplate("shopify.validation", "INVALID_INPUT",
trogonerror.TemplateWithCode(trogonerror.CodeInvalidArgument))
)
userErr := ErrUserNotFound.NewError(
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "userId", "gid://shopify/Customer/4567890123"))
inputErr := ErrInvalidInput.NewError(
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "fieldName", "email"),
trogonerror.WithSubject("/email"))
fmt.Println("User error:", userErr.Domain(), userErr.Reason())
fmt.Println("Input error:", inputErr.Domain(), inputErr.Reason())
}
Output: User error: shopify.users NOT_FOUND Input error: shopify.validation INVALID_INPUT
Example (Reusable) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
// Create a validation error template
validationTemplate := trogonerror.NewErrorTemplate("shopify.validation", "FIELD_INVALID",
trogonerror.TemplateWithCode(trogonerror.CodeInvalidArgument),
trogonerror.TemplateWithVisibility(trogonerror.VisibilityPublic))
// Create multiple validation errors from the same template
emailErr := validationTemplate.NewError(
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "fieldName", "email"),
trogonerror.WithSubject("/email"))
phoneErr := validationTemplate.NewError(
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "fieldName", "phone"),
trogonerror.WithSubject("/phone"))
fmt.Println("Email validation:", emailErr.Error())
fmt.Println("Phone validation:", phoneErr.Error())
fmt.Println("Same domain:", emailErr.Domain() == phoneErr.Domain())
fmt.Println("Same reason:", emailErr.Reason() == phoneErr.Reason())
}
Output: Email validation: invalid argument provided visibility: PUBLIC domain: shopify.validation reason: FIELD_INVALID code: INVALID_ARGUMENT subject: /email metadata: - fieldName: email visibility=PUBLIC Phone validation: invalid argument provided visibility: PUBLIC domain: shopify.validation reason: FIELD_INVALID code: INVALID_ARGUMENT subject: /phone metadata: - fieldName: phone visibility=PUBLIC Same domain: true Same reason: true
func NewErrorTemplate ¶
func NewErrorTemplate(domain, reason string, options ...TemplateOption) *ErrorTemplate
NewErrorTemplate creates a reusable error template for consistent error creation.
func (*ErrorTemplate) Is ¶
func (et *ErrorTemplate) Is(target error) bool
Is checks if the given error matches this template's domain and reason This allows checking if an error was created from this template without requiring the template to implement the error interface
func (*ErrorTemplate) NewError ¶
func (et *ErrorTemplate) NewError(options ...ErrorOption) *TrogonError
NewError creates a new error instance from the template
type Help ¶
type Help struct {
// contains filtered or unexported fields
}
Help provides links to relevant documentation
type HelpLink ¶
type HelpLink struct {
// contains filtered or unexported fields
}
HelpLink provides documentation link
func (HelpLink) Description ¶
type LocalizedMessage ¶
type LocalizedMessage struct {
// contains filtered or unexported fields
}
LocalizedMessage provides translated error message
func (LocalizedMessage) Locale ¶
func (l LocalizedMessage) Locale() string
func (LocalizedMessage) Message ¶
func (l LocalizedMessage) Message() string
type Metadata ¶
type Metadata = map[string]MetadataValue
Metadata represents a map of metadata with visibility control
type MetadataValue ¶
type MetadataValue struct {
// contains filtered or unexported fields
}
MetadataValue contains both the value and its visibility level
func (MetadataValue) Value ¶
func (m MetadataValue) Value() string
func (MetadataValue) Visibility ¶
func (m MetadataValue) Visibility() Visibility
type RetryInfo ¶
type RetryInfo struct {
// contains filtered or unexported fields
}
RetryInfo describes when a client can retry a failed request Following ADR requirements: servers MUST set either retry_offset OR retry_time, never both
func (RetryInfo) RetryOffset ¶
type TemplateOption ¶
type TemplateOption func(*ErrorTemplate)
TemplateOption represents options that can be applied to ErrorTemplate
func TemplateWithHelp ¶
func TemplateWithHelp(help Help) TemplateOption
func TemplateWithHelpLink ¶
func TemplateWithHelpLink(description, url string) TemplateOption
func TemplateWithMessage ¶
func TemplateWithMessage(message string) TemplateOption
func TemplateWithVisibility ¶
func TemplateWithVisibility(visibility Visibility) TemplateOption
type TrogonError ¶
type TrogonError struct {
// contains filtered or unexported fields
}
TrogonError represents the standardized error format following the ADR
Example (VisibilityControl) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
// Demonstrate visibility controls
err := trogonerror.NewError("shopify.auth", "ACCESS_DENIED",
trogonerror.WithCode(trogonerror.CodePermissionDenied),
trogonerror.WithVisibility(trogonerror.VisibilityPublic),
trogonerror.WithMetadataValue(trogonerror.VisibilityInternal, "userId", "gid://shopify/Customer/1234567890"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "resource", "/admin/customers"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "action", "DELETE"))
fmt.Printf("Error visibility: %s\n", err.Visibility().String())
// Show metadata with different visibility levels in alphabetical order
metadataKeys := []string{"action", "resource", "userId"}
for _, key := range metadataKeys {
if value, exists := err.Metadata()[key]; exists {
fmt.Printf("Metadata %s (%s): %s\n", key, value.Visibility().String(), value.Value())
}
}
}
Output: Error visibility: PUBLIC Metadata action (PUBLIC): DELETE Metadata resource (PRIVATE): /admin/customers Metadata userId (INTERNAL): gid://shopify/Customer/1234567890
func As ¶ added in v0.4.0
func As(err error, target trogonError) (*TrogonError, bool)
As checks if the error matches the target and returns the TrogonError if it does. This combines error matching and error extraction in a single, more idiomatic operation. The target can be either a TrogonError or an ErrorTemplate. Returns the TrogonError and true if the error matches, nil and false otherwise.
Example usage:
if trogonErr, ok := trogonerror.As(err, users.ErrUserNotFound); ok {
return trogonErr.WithChanges(
trogonerror.WithChangeMetadataValue(trogonerror.VisibilityPublic, "user_id", req.UserID),
)
}
Example (IdiomaticErrorHandling) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
// Define error templates (typically done at package level)
var ErrInsufficientStock = trogonerror.NewErrorTemplate("inventory", "INSUFFICIENT_STOCK",
trogonerror.TemplateWithCode(trogonerror.CodeResourceExhausted))
// Simulate an error from inventory service
inventoryErr := ErrInsufficientStock.NewError(
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "product_id", "prod_12345"))
// Old verbose pattern:
// if inventory.ErrInsufficientStock.Is(err) {
// var trogonErr *trogonerror.TrogonError
// if errors.As(err, &trogonErr) {
// return nil, trogonErr.WithChanges(...)
// }
// }
// New idiomatic pattern using As:
if trogonErr, ok := trogonerror.As(inventoryErr, ErrInsufficientStock); ok {
modifiedErr := trogonErr.WithChanges(
trogonerror.WithChangeMetadataValue(trogonerror.VisibilityPublic, "order_id", "order_789"),
trogonerror.WithChangeMetadataValue(trogonerror.VisibilityPublic, "requested_quantity", "10"),
)
fmt.Printf("Modified error domain: %s\n", modifiedErr.Domain())
fmt.Printf("Order ID: %s\n", modifiedErr.Metadata()["order_id"].Value())
fmt.Printf("Requested quantity: %s\n", modifiedErr.Metadata()["requested_quantity"].Value())
}
}
Output: Modified error domain: inventory Order ID: order_789 Requested quantity: 10
func NewError ¶
func NewError(domain, reason string, options ...ErrorOption) *TrogonError
NewError creates a new TrogonError following the ADR specification. Domain should be a simple identifier like "myapp.users" (not reversed-DNS). Reason should be an UPPERCASE identifier like "NOT_FOUND".
Example ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
err := trogonerror.NewError("shopify.users", "NOT_FOUND",
trogonerror.WithCode(trogonerror.CodeNotFound),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "userId", "gid://shopify/Customer/1234567890"))
fmt.Println(err.Error())
fmt.Println(err.Domain())
fmt.Println(err.Reason())
}
Output: resource not found visibility: INTERNAL domain: shopify.users reason: NOT_FOUND code: NOT_FOUND metadata: - userId: gid://shopify/Customer/1234567890 visibility=PUBLIC shopify.users NOT_FOUND
Example (Basic) ¶
package main
import (
"fmt"
"github.com/TrogonStack/trogonerror"
)
func main() {
err := trogonerror.NewError("shopify.users", "NOT_FOUND",
trogonerror.WithCode(trogonerror.CodeNotFound),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "userId", "gid://shopify/Customer/1234567890"))
fmt.Println("Error message:", err.Error())
fmt.Println("Domain:", err.Domain())
fmt.Println("Reason:", err.Reason())
fmt.Println("Code:", err.Code().String())
}
Output: Error message: resource not found visibility: INTERNAL domain: shopify.users reason: NOT_FOUND code: NOT_FOUND metadata: - userId: gid://shopify/Customer/1234567890 visibility=PUBLIC Domain: shopify.users Reason: NOT_FOUND Code: NOT_FOUND
Example (RichMetadata) ¶
package main
import (
"fmt"
"time"
"github.com/TrogonStack/trogonerror"
)
func main() {
err := trogonerror.NewError("shopify.payments", "PAYMENT_DECLINED",
trogonerror.WithCode(trogonerror.CodeInternal),
trogonerror.WithMessage("Payment processing failed due to upstream service error"),
trogonerror.WithVisibility(trogonerror.VisibilityPrivate),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "paymentId", "pay_2024_01_15_abc123def456"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPrivate, "amount", "299.99"),
trogonerror.WithMetadataValue(trogonerror.VisibilityPublic, "currency", "USD"),
trogonerror.WithMetadataValue(trogonerror.VisibilityInternal, "merchantId", "gid://shopify/Shop/1234567890"),
trogonerror.WithSubject("/payment/amount"),
trogonerror.WithTime(time.Date(2024, 1, 15, 14, 30, 45, 0, time.UTC)),
trogonerror.WithSourceID("payment-gateway-prod-01"))
fmt.Printf("Payment ID: %s\n", err.Metadata()["paymentId"].Value())
fmt.Printf("Currency: %s\n", err.Metadata()["currency"].Value())
fmt.Printf("Subject: %s\n", err.Subject())
}
Output: Payment ID: pay_2024_01_15_abc123def456 Currency: USD Subject: /payment/amount
func (TrogonError) Causes ¶
func (e TrogonError) Causes() []*TrogonError
func (TrogonError) Code ¶
func (e TrogonError) Code() Code
func (TrogonError) DebugInfo ¶
func (e TrogonError) DebugInfo() *DebugInfo
func (TrogonError) Domain ¶
func (e TrogonError) Domain() string
func (TrogonError) Error ¶
func (e TrogonError) Error() string
func (TrogonError) Help ¶
func (e TrogonError) Help() *Help
func (TrogonError) ID ¶
func (e TrogonError) ID() string
func (TrogonError) Is ¶
func (e TrogonError) Is(target error) bool
func (TrogonError) LocalizedMessage ¶
func (e TrogonError) LocalizedMessage() *LocalizedMessage
func (TrogonError) Message ¶
func (e TrogonError) Message() string
func (TrogonError) Metadata ¶
func (e TrogonError) Metadata() Metadata
func (TrogonError) Reason ¶
func (e TrogonError) Reason() string
func (TrogonError) RetryInfo ¶
func (e TrogonError) RetryInfo() *RetryInfo
func (TrogonError) SourceID ¶
func (e TrogonError) SourceID() string
func (TrogonError) SpecVersion ¶
func (e TrogonError) SpecVersion() int
func (TrogonError) Subject ¶
func (e TrogonError) Subject() string
func (TrogonError) Time ¶
func (e TrogonError) Time() *time.Time
func (TrogonError) Unwrap ¶
func (e TrogonError) Unwrap() error
func (TrogonError) Visibility ¶
func (e TrogonError) Visibility() Visibility
func (*TrogonError) WithChanges ¶
func (e *TrogonError) WithChanges(changes ...ChangeOption) *TrogonError
WithChanges applies multiple changes in a single copy operation for efficiency
type Visibility ¶
type Visibility int
Visibility controls information disclosure across trust boundaries
const ( VisibilityInternal Visibility = 0 VisibilityPrivate Visibility = 1 VisibilityPublic Visibility = 2 )
func (Visibility) String ¶
func (v Visibility) String() string