mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-04 04:29:06 +02:00
trying embedded caddy reverse proxy
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Configuration errors
|
||||
|
||||
func NewConfigInvalid(message string) *AppError {
|
||||
return New(CodeConfigInvalid, message)
|
||||
}
|
||||
|
||||
func NewConfigNotFound(path string) *AppError {
|
||||
return New(CodeConfigNotFound, fmt.Sprintf("configuration file not found: %s", path))
|
||||
}
|
||||
|
||||
func WrapConfigParseFailed(err error, path string) *AppError {
|
||||
return Wrap(CodeConfigParseFailed, fmt.Sprintf("failed to parse configuration file: %s", path), err)
|
||||
}
|
||||
|
||||
// Server errors
|
||||
|
||||
func NewServerStartFailed(err error, reason string) *AppError {
|
||||
return Wrap(CodeServerStartFailed, fmt.Sprintf("server start failed: %s", reason), err)
|
||||
}
|
||||
|
||||
func NewServerStopFailed(err error) *AppError {
|
||||
return Wrap(CodeServerStopFailed, "server shutdown failed", err)
|
||||
}
|
||||
|
||||
func NewServerAlreadyRunning() *AppError {
|
||||
return New(CodeServerAlreadyRunning, "server is already running")
|
||||
}
|
||||
|
||||
func NewServerNotRunning() *AppError {
|
||||
return New(CodeServerNotRunning, "server is not running")
|
||||
}
|
||||
|
||||
// Proxy errors
|
||||
|
||||
func NewProxyBackendUnavailable(backend string, err error) *AppError {
|
||||
return Wrap(CodeProxyBackendUnavailable, fmt.Sprintf("backend unavailable: %s", backend), err)
|
||||
}
|
||||
|
||||
func NewProxyTimeout(backend string) *AppError {
|
||||
return New(CodeProxyTimeout, fmt.Sprintf("request to backend timed out: %s", backend))
|
||||
}
|
||||
|
||||
func NewProxyInvalidTarget(target string, err error) *AppError {
|
||||
return Wrap(CodeProxyInvalidTarget, fmt.Sprintf("invalid proxy target: %s", target), err)
|
||||
}
|
||||
|
||||
// Network errors
|
||||
|
||||
func NewNetworkTimeout(operation string) *AppError {
|
||||
return New(CodeNetworkTimeout, fmt.Sprintf("network timeout: %s", operation))
|
||||
}
|
||||
|
||||
func NewNetworkUnreachable(host string) *AppError {
|
||||
return New(CodeNetworkUnreachable, fmt.Sprintf("network unreachable: %s", host))
|
||||
}
|
||||
|
||||
func NewNetworkRefused(host string) *AppError {
|
||||
return New(CodeNetworkRefused, fmt.Sprintf("connection refused: %s", host))
|
||||
}
|
||||
|
||||
// Internal errors
|
||||
|
||||
func NewInternalError(message string) *AppError {
|
||||
return New(CodeInternalError, message)
|
||||
}
|
||||
|
||||
func WrapInternalError(err error, message string) *AppError {
|
||||
return Wrap(CodeInternalError, message, err)
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Error codes for categorizing errors
|
||||
type Code string
|
||||
|
||||
const (
|
||||
// Configuration errors
|
||||
CodeConfigInvalid Code = "CONFIG_INVALID"
|
||||
CodeConfigNotFound Code = "CONFIG_NOT_FOUND"
|
||||
CodeConfigParseFailed Code = "CONFIG_PARSE_FAILED"
|
||||
|
||||
// Server errors
|
||||
CodeServerStartFailed Code = "SERVER_START_FAILED"
|
||||
CodeServerStopFailed Code = "SERVER_STOP_FAILED"
|
||||
CodeServerAlreadyRunning Code = "SERVER_ALREADY_RUNNING"
|
||||
CodeServerNotRunning Code = "SERVER_NOT_RUNNING"
|
||||
|
||||
// Proxy errors
|
||||
CodeProxyBackendUnavailable Code = "PROXY_BACKEND_UNAVAILABLE"
|
||||
CodeProxyTimeout Code = "PROXY_TIMEOUT"
|
||||
CodeProxyInvalidTarget Code = "PROXY_INVALID_TARGET"
|
||||
|
||||
// Network errors
|
||||
CodeNetworkTimeout Code = "NETWORK_TIMEOUT"
|
||||
CodeNetworkUnreachable Code = "NETWORK_UNREACHABLE"
|
||||
CodeNetworkRefused Code = "NETWORK_REFUSED"
|
||||
|
||||
// Internal errors
|
||||
CodeInternalError Code = "INTERNAL_ERROR"
|
||||
CodeUnknownError Code = "UNKNOWN_ERROR"
|
||||
)
|
||||
|
||||
// AppError represents a structured application error
|
||||
type AppError struct {
|
||||
Code Code // Error code for categorization
|
||||
Message string // Human-readable error message
|
||||
Cause error // Underlying error (if any)
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *AppError) 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 error (for errors.Is and errors.As)
|
||||
func (e *AppError) Unwrap() error {
|
||||
return e.Cause
|
||||
}
|
||||
|
||||
// Is checks if the error matches the target
|
||||
func (e *AppError) Is(target error) bool {
|
||||
t, ok := target.(*AppError)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return e.Code == t.Code
|
||||
}
|
||||
|
||||
// New creates a new AppError
|
||||
func New(code Code, message string) *AppError {
|
||||
return &AppError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap wraps an existing error with additional context
|
||||
func Wrap(code Code, message string, cause error) *AppError {
|
||||
return &AppError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Cause: cause,
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf wraps an error with a formatted message
|
||||
func Wrapf(code Code, cause error, format string, args ...interface{}) *AppError {
|
||||
return &AppError{
|
||||
Code: code,
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
Cause: cause,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCode extracts the error code from an error
|
||||
func GetCode(err error) Code {
|
||||
var appErr *AppError
|
||||
if errors.As(err, &appErr) {
|
||||
return appErr.Code
|
||||
}
|
||||
return CodeUnknownError
|
||||
}
|
||||
|
||||
// HasCode checks if an error has a specific code
|
||||
func HasCode(err error, code Code) bool {
|
||||
return GetCode(err) == code
|
||||
}
|
||||
|
||||
// IsConfigError checks if an error is configuration-related
|
||||
func IsConfigError(err error) bool {
|
||||
code := GetCode(err)
|
||||
return code == CodeConfigInvalid ||
|
||||
code == CodeConfigNotFound ||
|
||||
code == CodeConfigParseFailed
|
||||
}
|
||||
|
||||
// IsServerError checks if an error is server-related
|
||||
func IsServerError(err error) bool {
|
||||
code := GetCode(err)
|
||||
return code == CodeServerStartFailed ||
|
||||
code == CodeServerStopFailed ||
|
||||
code == CodeServerAlreadyRunning ||
|
||||
code == CodeServerNotRunning
|
||||
}
|
||||
|
||||
// IsProxyError checks if an error is proxy-related
|
||||
func IsProxyError(err error) bool {
|
||||
code := GetCode(err)
|
||||
return code == CodeProxyBackendUnavailable ||
|
||||
code == CodeProxyTimeout ||
|
||||
code == CodeProxyInvalidTarget
|
||||
}
|
||||
|
||||
// IsNetworkError checks if an error is network-related
|
||||
func IsNetworkError(err error) bool {
|
||||
code := GetCode(err)
|
||||
return code == CodeNetworkTimeout ||
|
||||
code == CodeNetworkUnreachable ||
|
||||
code == CodeNetworkRefused
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAppError_Error(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err *AppError
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "error without cause",
|
||||
err: New(CodeConfigInvalid, "invalid configuration"),
|
||||
expected: "[CONFIG_INVALID] invalid configuration",
|
||||
},
|
||||
{
|
||||
name: "error with cause",
|
||||
err: Wrap(CodeServerStartFailed, "failed to bind port", errors.New("address already in use")),
|
||||
expected: "[SERVER_START_FAILED] failed to bind port: address already in use",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := tt.err.Error(); got != tt.expected {
|
||||
t.Errorf("Error() = %v, want %v", got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
expected Code
|
||||
}{
|
||||
{
|
||||
name: "app error",
|
||||
err: New(CodeConfigInvalid, "test"),
|
||||
expected: CodeConfigInvalid,
|
||||
},
|
||||
{
|
||||
name: "wrapped app error",
|
||||
err: Wrap(CodeServerStartFailed, "test", errors.New("cause")),
|
||||
expected: CodeServerStartFailed,
|
||||
},
|
||||
{
|
||||
name: "standard error",
|
||||
err: errors.New("standard error"),
|
||||
expected: CodeUnknownError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := GetCode(tt.err); got != tt.expected {
|
||||
t.Errorf("GetCode() = %v, want %v", got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasCode(t *testing.T) {
|
||||
err := New(CodeConfigInvalid, "invalid config")
|
||||
|
||||
if !HasCode(err, CodeConfigInvalid) {
|
||||
t.Error("HasCode() should return true for matching code")
|
||||
}
|
||||
|
||||
if HasCode(err, CodeServerStartFailed) {
|
||||
t.Error("HasCode() should return false for non-matching code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsConfigError(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
err error
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "config invalid error",
|
||||
err: New(CodeConfigInvalid, "test"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "config not found error",
|
||||
err: New(CodeConfigNotFound, "test"),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "server error",
|
||||
err: New(CodeServerStartFailed, "test"),
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := IsConfigError(tt.err); got != tt.expected {
|
||||
t.Errorf("IsConfigError() = %v, want %v", got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorUnwrap(t *testing.T) {
|
||||
cause := errors.New("root cause")
|
||||
err := Wrap(CodeInternalError, "wrapped error", cause)
|
||||
|
||||
unwrapped := errors.Unwrap(err)
|
||||
if unwrapped != cause {
|
||||
t.Errorf("Unwrap() = %v, want %v", unwrapped, cause)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorIs(t *testing.T) {
|
||||
err1 := New(CodeConfigInvalid, "test1")
|
||||
err2 := New(CodeConfigInvalid, "test2")
|
||||
err3 := New(CodeServerStartFailed, "test3")
|
||||
|
||||
if !errors.Is(err1, err2) {
|
||||
t.Error("errors.Is() should return true for same error code")
|
||||
}
|
||||
|
||||
if errors.Is(err1, err3) {
|
||||
t.Error("errors.Is() should return false for different error codes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommonConstructors(t *testing.T) {
|
||||
t.Run("NewConfigNotFound", func(t *testing.T) {
|
||||
err := NewConfigNotFound("/path/to/config")
|
||||
if GetCode(err) != CodeConfigNotFound {
|
||||
t.Error("NewConfigNotFound should create CONFIG_NOT_FOUND error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NewServerAlreadyRunning", func(t *testing.T) {
|
||||
err := NewServerAlreadyRunning()
|
||||
if GetCode(err) != CodeServerAlreadyRunning {
|
||||
t.Error("NewServerAlreadyRunning should create SERVER_ALREADY_RUNNING error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NewProxyBackendUnavailable", func(t *testing.T) {
|
||||
cause := errors.New("connection refused")
|
||||
err := NewProxyBackendUnavailable("http://backend", cause)
|
||||
if GetCode(err) != CodeProxyBackendUnavailable {
|
||||
t.Error("NewProxyBackendUnavailable should create PROXY_BACKEND_UNAVAILABLE error")
|
||||
}
|
||||
if !errors.Is(err.Unwrap(), cause) {
|
||||
t.Error("NewProxyBackendUnavailable should wrap the cause")
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user