feat: bootstrap reusable GoOmni framework

This commit is contained in:
2026-09-20 22:06:24 +08:00
commit 8b19aa79ee
60 changed files with 4923 additions and 0 deletions

74
errors/errors.go Normal file
View File

@@ -0,0 +1,74 @@
package apierrors
import (
stdErrors "errors"
"net/http"
)
// APIError is the public error shape used by handlers and middleware.
// Cause is retained for logging and unwrapping, but is never serialized.
type APIError struct {
Status int
Code string
Message string
Cause error
}
func (e *APIError) Error() string {
if e == nil {
return ""
}
if e.Cause != nil {
return e.Code + ": " + e.Message + ": " + e.Cause.Error()
}
return e.Code + ": " + e.Message
}
func (e *APIError) Unwrap() error {
if e == nil {
return nil
}
return e.Cause
}
// WithCause attaches an internal cause while keeping the public response stable.
func (e *APIError) WithCause(cause error) *APIError {
e.Cause = cause
return e
}
func New(status int, code, message string) *APIError {
return &APIError{Status: status, Code: code, Message: message}
}
func BadRequest(code, message string) *APIError {
return New(http.StatusBadRequest, code, message)
}
func Unauthorized(code, message string) *APIError {
return New(http.StatusUnauthorized, code, message)
}
func Forbidden(code, message string) *APIError {
return New(http.StatusForbidden, code, message)
}
func NotFound(code, message string) *APIError {
return New(http.StatusNotFound, code, message)
}
func Internal(cause error) *APIError {
return New(http.StatusInternalServerError, "INTERNAL_ERROR", "internal server error").WithCause(cause)
}
// From converts any error into the public API error shape.
func From(err error) *APIError {
if err == nil {
return nil
}
var apiErr *APIError
if stdErrors.As(err, &apiErr) && apiErr != nil {
return apiErr
}
return Internal(err)
}