// Package domain contains domain-specific error types for the EdgeConnect client package domain import ( "errors" "fmt" "strings" ) // ErrorCode represents different types of domain errors type ErrorCode int const ( // Resource errors ErrResourceNotFound ErrorCode = iota ErrResourceAlreadyExists ErrResourceConflict // Validation errors ErrValidationFailed ErrInvalidConfiguration ErrInvalidInput // Business logic errors ErrQuotaExceeded ErrInsufficientPermissions ErrOperationNotAllowed // Infrastructure errors ErrNetworkError ErrAuthenticationFailed ErrServiceUnavailable ErrTimeout // Internal errors ErrInternalError ErrUnknownError ) // String returns a human-readable string representation of the error code func (e ErrorCode) String() string { switch e { case ErrResourceNotFound: return "RESOURCE_NOT_FOUND" case ErrResourceAlreadyExists: return "RESOURCE_ALREADY_EXISTS" case ErrResourceConflict: return "RESOURCE_CONFLICT" case ErrValidationFailed: return "VALIDATION_FAILED" case ErrInvalidConfiguration: return "INVALID_CONFIGURATION" case ErrInvalidInput: return "INVALID_INPUT" case ErrQuotaExceeded: return "QUOTA_EXCEEDED" case ErrInsufficientPermissions: return "INSUFFICIENT_PERMISSIONS" case ErrOperationNotAllowed: return "OPERATION_NOT_ALLOWED" case ErrNetworkError: return "NETWORK_ERROR" case ErrAuthenticationFailed: return "AUTHENTICATION_FAILED" case ErrServiceUnavailable: return "SERVICE_UNAVAILABLE" case ErrTimeout: return "TIMEOUT" case ErrInternalError: return "INTERNAL_ERROR" case ErrUnknownError: return "UNKNOWN_ERROR" default: return "UNDEFINED_ERROR" } } // DomainError represents a domain-specific error with detailed context type DomainError struct { Code ErrorCode `json:"code"` Message string `json:"message"` Details string `json:"details,omitempty"` Cause error `json:"-"` Context map[string]interface{} `json:"context,omitempty"` Resource *ResourceIdentifier `json:"resource,omitempty"` Operation string `json:"operation,omitempty"` Retryable bool `json:"retryable"` } // ResourceIdentifier provides context about the resource involved in the error type ResourceIdentifier struct { Type string `json:"type"` Name string `json:"name"` Organization string `json:"organization,omitempty"` Region string `json:"region,omitempty"` Version string `json:"version,omitempty"` } // Error implements the error interface func (e *DomainError) Error() string { var parts []string if e.Operation != "" { parts = append(parts, fmt.Sprintf("operation %s failed", e.Operation)) } if e.Resource != nil { parts = append(parts, fmt.Sprintf("resource %s", e.resourceString())) } parts = append(parts, e.Message) if e.Details != "" { parts = append(parts, e.Details) } result := strings.Join(parts, ": ") if e.Cause != nil { result = fmt.Sprintf("%s (caused by: %v)", result, e.Cause) } return result } // Unwrap returns the underlying cause for error wrapping func (e *DomainError) Unwrap() error { return e.Cause } // Is checks if the error matches a specific error code func (e *DomainError) Is(target error) bool { if de, ok := target.(*DomainError); ok { return e.Code == de.Code } return false } // IsRetryable indicates whether the operation should be retried func (e *DomainError) IsRetryable() bool { return e.Retryable } // WithContext adds context information to the error func (e *DomainError) WithContext(key string, value interface{}) *DomainError { if e.Context == nil { e.Context = make(map[string]interface{}) } e.Context[key] = value return e } // WithDetails adds additional details to the error func (e *DomainError) WithDetails(details string) *DomainError { e.Details = details return e } func (e *DomainError) resourceString() string { if e.Resource == nil { return "" } parts := []string{e.Resource.Type} if e.Resource.Organization != "" && e.Resource.Name != "" { parts = append(parts, fmt.Sprintf("%s/%s", e.Resource.Organization, e.Resource.Name)) } else if e.Resource.Name != "" { parts = append(parts, e.Resource.Name) } if e.Resource.Version != "" { parts = append(parts, fmt.Sprintf("version %s", e.Resource.Version)) } if e.Resource.Region != "" { parts = append(parts, fmt.Sprintf("in region %s", e.Resource.Region)) } return strings.Join(parts, " ") } // Error creation helpers // NewDomainError creates a new domain error with the specified code and message func NewDomainError(code ErrorCode, message string) *DomainError { return &DomainError{ Code: code, Message: message, Retryable: isRetryableByDefault(code), } } // NewDomainErrorWithCause creates a new domain error with an underlying cause func NewDomainErrorWithCause(code ErrorCode, message string, cause error) *DomainError { return &DomainError{ Code: code, Message: message, Cause: cause, Retryable: isRetryableByDefault(code), } } // NewResourceError creates a domain error for resource-related operations func NewResourceError(code ErrorCode, operation string, resource *ResourceIdentifier, message string) *DomainError { return &DomainError{ Code: code, Message: message, Operation: operation, Resource: resource, Retryable: isRetryableByDefault(code), } } func isRetryableByDefault(code ErrorCode) bool { switch code { case ErrNetworkError, ErrServiceUnavailable, ErrTimeout, ErrInternalError: return true default: return false } } // Predefined errors for common scenarios var ( // Resource errors ErrAppNotFound = NewDomainError(ErrResourceNotFound, "application not found") ErrAppExists = NewDomainError(ErrResourceAlreadyExists, "application already exists") ErrInstanceNotFound = NewDomainError(ErrResourceNotFound, "app instance not found") ErrInstanceExists = NewDomainError(ErrResourceAlreadyExists, "app instance already exists") ErrCloudletNotFound = NewDomainError(ErrResourceNotFound, "cloudlet not found") // Validation errors ErrInvalidAppKey = NewDomainError(ErrValidationFailed, "invalid application key") ErrInvalidInstanceKey = NewDomainError(ErrValidationFailed, "invalid app instance key") ErrInvalidCloudletKey = NewDomainError(ErrValidationFailed, "invalid cloudlet key") ErrMissingRegion = NewDomainError(ErrValidationFailed, "region is required") // Business logic errors ErrDeploymentFailed = NewDomainError(ErrOperationNotAllowed, "deployment failed") ErrRollbackFailed = NewDomainError(ErrOperationNotAllowed, "rollback failed") ErrPlanningFailed = NewDomainError(ErrOperationNotAllowed, "deployment planning failed") ) // Helper functions for creating specific error scenarios // NewAppError creates an error related to application operations func NewAppError(code ErrorCode, operation string, appKey AppKey, region string, message string) *DomainError { resource := &ResourceIdentifier{ Type: "app", Organization: appKey.Organization, Name: appKey.Name, Version: appKey.Version, Region: region, } return NewResourceError(code, operation, resource, message) } // NewInstanceError creates an error related to app instance operations func NewInstanceError(code ErrorCode, operation string, instanceKey AppInstanceKey, region string, message string) *DomainError { resource := &ResourceIdentifier{ Type: "app-instance", Organization: instanceKey.Organization, Name: instanceKey.Name, Region: region, } return NewResourceError(code, operation, resource, message) } // NewCloudletError creates an error related to cloudlet operations func NewCloudletError(code ErrorCode, operation string, cloudletKey CloudletKey, region string, message string) *DomainError { resource := &ResourceIdentifier{ Type: "cloudlet", Organization: cloudletKey.Organization, Name: cloudletKey.Name, Region: region, } return NewResourceError(code, operation, resource, message) } // Error checking utilities // IsNotFoundError checks if an error indicates a resource was not found func IsNotFoundError(err error) bool { var de *DomainError return errors.As(err, &de) && de.Code == ErrResourceNotFound } // IsValidationError checks if an error is a validation error func IsValidationError(err error) bool { var de *DomainError return errors.As(err, &de) && (de.Code == ErrValidationFailed || de.Code == ErrInvalidInput || de.Code == ErrInvalidConfiguration) } // IsRetryableError checks if an error is retryable func IsRetryableError(err error) bool { var de *DomainError if errors.As(err, &de) { return de.IsRetryable() } return false } // IsAuthenticationError checks if an error is authentication-related func IsAuthenticationError(err error) bool { var de *DomainError return errors.As(err, &de) && de.Code == ErrAuthenticationFailed }