// Package errors provides domain-specific error types for structured // error handling with machine-readable codes and categories. package errors import "fmt" // DomainError is the base interface for all domain errors. type DomainError interface { error Code() []byte Category() []byte IsRetryable() bool } // Base provides common fields for domain errors. type Base struct { code []byte category []byte message []byte retryable bool cause error } func (e *Base) Error() string { if e.cause != nil { return string(append([]byte(nil), fmt.Sprintf("%s: %v", e.message, e.cause)...)) } return string(e.message) } func (e *Base) Code() []byte { return e.code } func (e *Base) Category() []byte { return e.category } func (e *Base) IsRetryable() bool { return e.retryable } func (e *Base) Unwrap() error { return e.cause } func (e *Base) WithCause(cause error) *Base { return &Base{ code: e.code, category: e.category, message: e.message, retryable: e.retryable, cause: cause, } } func (e *Base) WithMessage(msg []byte) *Base { return &Base{ code: e.code, category: e.category, message: msg, retryable: e.retryable, cause: e.cause, } } // --- Validation --- type ValidationError struct { Base Field []byte } func NewValidationError(code, field, message []byte) *ValidationError { return &ValidationError{ Base: Base{code: code, category: []byte("validation"), message: message}, Field: field, } } func (e *ValidationError) WithField(field []byte) *ValidationError { return &ValidationError{Base: e.Base, Field: field} } var ( ErrInvalidEventID = NewValidationError([]byte("INVALID_ID"), []byte("id"), []byte("event ID does not match computed hash")) ErrInvalidSignature = NewValidationError([]byte("INVALID_SIG"), []byte("sig"), []byte("signature verification failed")) ErrFutureTimestamp = NewValidationError([]byte("FUTURE_TS"), []byte("created_at"), []byte("timestamp too far in future")) ErrPastTimestamp = NewValidationError([]byte("PAST_TS"), []byte("created_at"), []byte("timestamp too far in past")) ErrUppercaseHex = NewValidationError([]byte("UPPERCASE_HEX"), []byte("id/pubkey"), []byte("hex values must be lowercase")) ErrProtectedTagMismatch = NewValidationError([]byte("PROTECTED_TAG"), []byte("tags"), []byte("protected event can only be modified by author")) ErrInvalidJSON = NewValidationError([]byte("INVALID_JSON"), nil, []byte("malformed JSON")) ErrEventTooLarge = NewValidationError([]byte("EVENT_TOO_LARGE"), []byte("content"), []byte("event exceeds size limit")) ErrInvalidKind = NewValidationError([]byte("INVALID_KIND"), []byte("kind"), []byte("event kind not allowed")) ErrMissingTag = NewValidationError([]byte("MISSING_TAG"), []byte("tags"), []byte("required tag missing")) ErrInvalidTagValue = NewValidationError([]byte("INVALID_TAG"), []byte("tags"), []byte("tag value validation failed")) ) // --- Authorization --- type AuthorizationError struct { Base Pubkey []byte AccessLevel []byte RequireAuth bool } func (e *AuthorizationError) NeedsAuth() bool { return e.RequireAuth } func NewAuthRequired(reason []byte) *AuthorizationError { return &AuthorizationError{ Base: Base{code: []byte("AUTH_REQUIRED"), category: []byte("authorization"), message: reason}, RequireAuth: true, } } func NewAccessDenied(level, reason []byte) *AuthorizationError { return &AuthorizationError{ Base: Base{code: []byte("ACCESS_DENIED"), category: []byte("authorization"), message: reason}, AccessLevel: level, } } func (e *AuthorizationError) WithPubkey(pubkey []byte) *AuthorizationError { return &AuthorizationError{ Base: e.Base, Pubkey: pubkey, AccessLevel: e.AccessLevel, RequireAuth: e.RequireAuth, } } var ( ErrAuthRequired = NewAuthRequired([]byte("authentication required")) ErrBanned = &AuthorizationError{ Base: Base{code: []byte("BANNED"), category: []byte("authorization"), message: []byte("pubkey banned")}, } ErrIPBlocked = &AuthorizationError{ Base: Base{code: []byte("IP_BLOCKED"), category: []byte("authorization"), message: []byte("IP address blocked")}, } ErrNotFollowed = &AuthorizationError{ Base: Base{code: []byte("NOT_FOLLOWED"), category: []byte("authorization"), message: []byte("write access requires being followed by admin")}, } ErrNotMember = &AuthorizationError{ Base: Base{code: []byte("NOT_MEMBER"), category: []byte("authorization"), message: []byte("membership required")}, } ErrInsufficientAccess = &AuthorizationError{ Base: Base{code: []byte("INSUFFICIENT_ACCESS"), category: []byte("authorization"), message: []byte("insufficient access level")}, } ) // --- Processing --- type ProcessingError struct { Base EventID []byte Kind uint16 } func NewProcessingError(code, message []byte, retryable bool) *ProcessingError { return &ProcessingError{ Base: Base{code: code, category: []byte("processing"), message: message, retryable: retryable}, } } func (e *ProcessingError) WithEventID(id []byte) *ProcessingError { return &ProcessingError{Base: e.Base, EventID: id, Kind: e.Kind} } func (e *ProcessingError) WithKind(kind uint16) *ProcessingError { return &ProcessingError{Base: e.Base, EventID: e.EventID, Kind: kind} } var ( ErrDuplicate = NewProcessingError([]byte("DUPLICATE"), []byte("event already exists"), false) ErrReplaceNotAllowed = NewProcessingError([]byte("REPLACE_DENIED"), []byte("cannot replace event from different author"), false) ErrDeletedEvent = NewProcessingError([]byte("DELETED"), []byte("event has been deleted"), false) ErrEphemeralNotStored = NewProcessingError([]byte("EPHEMERAL"), []byte("ephemeral events are not stored"), false) ErrRateLimited = NewProcessingError([]byte("RATE_LIMITED"), []byte("rate limit exceeded"), true) ) // --- Policy --- type PolicyError struct { Base RuleName []byte Action []byte } func NewPolicyBlocked(ruleName, reason []byte) *PolicyError { return &PolicyError{ Base: Base{code: []byte("POLICY_BLOCKED"), category: []byte("policy"), message: reason}, RuleName: ruleName, Action: []byte("block"), } } func NewPolicyRejected(ruleName, reason []byte) *PolicyError { return &PolicyError{ Base: Base{code: []byte("POLICY_REJECTED"), category: []byte("policy"), message: reason}, RuleName: ruleName, Action: []byte("reject"), } } var ( ErrKindBlocked = &PolicyError{ Base: Base{code: []byte("KIND_BLOCKED"), category: []byte("policy"), message: []byte("event kind not allowed by policy")}, Action: []byte("block"), } ErrPubkeyBlocked = &PolicyError{ Base: Base{code: []byte("PUBKEY_BLOCKED"), category: []byte("policy"), message: []byte("pubkey blocked by policy")}, Action: []byte("block"), } ErrContentBlocked = &PolicyError{ Base: Base{code: []byte("CONTENT_BLOCKED"), category: []byte("policy"), message: []byte("content blocked by policy")}, Action: []byte("block"), } ) // --- Storage --- type StorageError struct{ Base } func NewStorageError(code, message []byte, cause error, retryable bool) *StorageError { return &StorageError{Base: Base{code: code, category: []byte("storage"), message: message, cause: cause, retryable: retryable}} } var ( ErrDatabaseUnavailable = NewStorageError([]byte("DB_UNAVAILABLE"), []byte("database not available"), nil, true) ErrWriteTimeout = NewStorageError([]byte("WRITE_TIMEOUT"), []byte("write operation timed out"), nil, true) ErrReadTimeout = NewStorageError([]byte("READ_TIMEOUT"), []byte("read operation timed out"), nil, true) ErrStorageFull = NewStorageError([]byte("STORAGE_FULL"), []byte("storage capacity exceeded"), nil, false) ErrCorruptedData = NewStorageError([]byte("CORRUPTED_DATA"), []byte("data corruption detected"), nil, false) ) // --- Service --- type ServiceError struct { Base ServiceName []byte } func NewServiceError(code, service, message []byte, retryable bool) *ServiceError { return &ServiceError{ Base: Base{code: code, category: []byte("service"), message: message, retryable: retryable}, ServiceName: service, } } var ( ErrServiceUnavailable = NewServiceError([]byte("SERVICE_UNAVAILABLE"), nil, []byte("service temporarily unavailable"), true) ErrServiceTimeout = NewServiceError([]byte("SERVICE_TIMEOUT"), nil, []byte("service request timed out"), true) ErrServiceOverloaded = NewServiceError([]byte("SERVICE_OVERLOADED"), nil, []byte("service is overloaded"), true) ) // --- Helpers --- func Is(err error, target DomainError) bool { if de, ok := err.(DomainError); ok { return bytesEqual(de.Code(), target.Code()) } return false } func Code(err error) []byte { if de, ok := err.(DomainError); ok { return de.Code() } return nil } func Category(err error) []byte { if de, ok := err.(DomainError); ok { return de.Category() } return []byte("unknown") } func IsRetryable(err error) bool { if de, ok := err.(DomainError); ok { return de.IsRetryable() } return false } func IsValidation(err error) bool { _, ok := err.(*ValidationError); return ok } func IsAuthorization(err error) bool { _, ok := err.(*AuthorizationError); return ok } func IsProcessing(err error) bool { _, ok := err.(*ProcessingError); return ok } func IsPolicy(err error) bool { _, ok := err.(*PolicyError); return ok } func IsStorage(err error) bool { _, ok := err.(*StorageError); return ok } func NeedsAuth(err error) bool { if ae, ok := err.(*AuthorizationError); ok { return ae.NeedsAuth() } return false } func bytesEqual(a, b []byte) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }