File size: 6,851 Bytes
bf9e111
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Package errors provides standardized domain error types for the CLI Proxy API.
// These errors are used throughout the domain layer and are mapped to appropriate
// HTTP responses at the transport layer.
package errors

import (
	"errors"
	"fmt"
)

// ErrorCode represents a standardized error code for API responses
type ErrorCode string

const (
	// NotFound indicates a requested resource was not found
	NotFound ErrorCode = "NOT_FOUND"
	// Unauthorized indicates authentication is required or failed
	Unauthorized ErrorCode = "UNAUTHORIZED"
	// Forbidden indicates the user lacks permission
	Forbidden ErrorCode = "FORBIDDEN"
	// InvalidInput indicates the request input is invalid
	InvalidInput ErrorCode = "INVALID_INPUT"
	// Conflict indicates a resource conflict (e.g., duplicate)
	Conflict ErrorCode = "CONFLICT"
	// InternalError indicates an unexpected internal error
	InternalError ErrorCode = "INTERNAL_ERROR"
	// ServiceUnavailable indicates a dependent service is unavailable
	ServiceUnavailable ErrorCode = "SERVICE_UNAVAILABLE"
	// Timeout indicates the operation timed out
	Timeout ErrorCode = "TIMEOUT"
	// ValidationFailed indicates validation of data failed
	ValidationFailed ErrorCode = "VALIDATION_FAILED"
	// AlreadyExists indicates a resource already exists
	AlreadyExists ErrorCode = "ALREADY_EXISTS"
)

// DomainError is the base error type for all domain errors.
// It provides structured error information that can be consistently
// mapped to HTTP responses.
type DomainError struct {
	Code       ErrorCode
	Message    string
	Cause      error
	Details    map[string]interface{}
}

// Error implements the error interface
func (e *DomainError) Error() string {
	if e.Cause != nil {
		return fmt.Sprintf("[%s] %s: %v", e.Code, e.Message, e.Cause)
	}
	return fmt.Sprintf("[%s] %s", e.Code, e.Message)
}

// Unwrap returns the underlying cause of the error
func (e *DomainError) Unwrap() error {
	return e.Cause
}

// WithDetail adds a detail field to the error
func (e *DomainError) WithDetail(key string, value interface{}) *DomainError {
	if e.Details == nil {
		e.Details = make(map[string]interface{})
	}
	e.Details[key] = value
	return e
}

// IsDomainError checks if an error is a DomainError
func IsDomainError(err error) (*DomainError, bool) {
	var domainErr *DomainError
	if errors.As(err, &domainErr) {
		return domainErr, true
	}
	return nil, false
}

// New creates a new DomainError with the given code and message
func New(code ErrorCode, message string) *DomainError {
	return &DomainError{
		Code:    code,
		Message: message,
	}
}

// Wrap wraps an existing error with a domain error
func Wrap(code ErrorCode, message string, cause error) *DomainError {
	return &DomainError{
		Code:    code,
		Message: message,
		Cause:   cause,
	}
}

// Predefined error constructors for common cases

// NewNotFoundError creates a NOT_FOUND error
func NewNotFoundError(resource string, identifier string) *DomainError {
	return &DomainError{
		Code:    NotFound,
		Message: fmt.Sprintf("%s not found: %s", resource, identifier),
	}
}

// NewUnauthorizedError creates an UNAUTHORIZED error
func NewUnauthorizedError(message string) *DomainError {
	return &DomainError{
		Code:    Unauthorized,
		Message: message,
	}
}

// NewForbiddenError creates a FORBIDDEN error
func NewForbiddenError(message string) *DomainError {
	return &DomainError{
		Code:    Forbidden,
		Message: message,
	}
}

// NewInvalidInputError creates an INVALID_INPUT error
func NewInvalidInputError(message string) *DomainError {
	return &DomainError{
		Code:    InvalidInput,
		Message: message,
	}
}

// NewValidationError creates a VALIDATION_FAILED error with field details
func NewValidationError(message string, field string, reason string) *DomainError {
	err := &DomainError{
		Code:    ValidationFailed,
		Message: message,
	}
	if field != "" {
		err.WithDetail("field", field)
	}
	if reason != "" {
		err.WithDetail("reason", reason)
	}
	return err
}

// NewConflictError creates a CONFLICT error
func NewConflictError(message string) *DomainError {
	return &DomainError{
		Code:    Conflict,
		Message: message,
	}
}

// NewAlreadyExistsError creates an ALREADY_EXISTS error
func NewAlreadyExistsError(resource string, identifier string) *DomainError {
	return &DomainError{
		Code:    AlreadyExists,
		Message: fmt.Sprintf("%s already exists: %s", resource, identifier),
	}
}

// NewInternalError creates an INTERNAL_ERROR
func NewInternalError(message string, cause error) *DomainError {
	return &DomainError{
		Code:    InternalError,
		Message: message,
		Cause:   cause,
	}
}

// NewServiceUnavailableError creates a SERVICE_UNAVAILABLE error
func NewServiceUnavailableError(service string) *DomainError {
	return &DomainError{
		Code:    ServiceUnavailable,
		Message: fmt.Sprintf("Service unavailable: %s", service),
	}
}

// NewTimeoutError creates a TIMEOUT error
func NewTimeoutError(operation string) *DomainError {
	return &DomainError{
		Code:    Timeout,
		Message: fmt.Sprintf("Operation timed out: %s", operation),
	}
}

// HTTPStatusCode returns the appropriate HTTP status code for the error
func (e *DomainError) HTTPStatusCode() int {
	switch e.Code {
	case NotFound:
		return 404
	case Unauthorized:
		return 401
	case Forbidden:
		return 403
	case InvalidInput, ValidationFailed:
		return 400
	case Conflict, AlreadyExists:
		return 409
	case ServiceUnavailable:
		return 503
	case Timeout:
		return 504
	default:
		return 500
	}
}

// ToResponse converts the error to a response map suitable for JSON serialization
func (e *DomainError) ToResponse() map[string]interface{} {
	response := map[string]interface{}{
		"error":   string(e.Code),
		"message": e.Message,
	}
	if len(e.Details) > 0 {
		response["details"] = e.Details
	}
	return response
}

// Common error instances for reuse
var (
	// ErrConfigNotFound is returned when configuration is not found
	ErrConfigNotFound = New(NotFound, "configuration not found")
	
	// ErrAuthFileNotFound is returned when an auth file is not found
	ErrAuthFileNotFound = New(NotFound, "auth file not found")
	
	// ErrInvalidConfig is returned when configuration is invalid
	ErrInvalidConfig = New(ValidationFailed, "invalid configuration")
	
	// ErrAuthManagerUnavailable is returned when the auth manager is not available
	ErrAuthManagerUnavailable = New(ServiceUnavailable, "auth manager unavailable")
	
	// ErrTokenStoreUnavailable is returned when the token store is not available
	ErrTokenStoreUnavailable = New(ServiceUnavailable, "token store unavailable")
	
	// ErrLogDirectoryNotConfigured is returned when log directory is not set
	ErrLogDirectoryNotConfigured = New(InternalError, "log directory not configured")
	
	// ErrLoggingDisabled is returned when logging to file is disabled
	ErrLoggingDisabled = New(ServiceUnavailable, "logging to file disabled")
)