mirror of
https://github.com/holos-run/holos.git
synced 2026-03-19 16:54:58 +00:00
The holos cli does not use an interface to handle different Platform api versions. This makes it difficult to evolve the API in a backwards compatible way. This patch adds a top level switch statement to the `holos render platform` command. The switch discriminates on the Platform API version. v1alpha3 and earlier are classified as legacy versions and will use the existing strict types. v1alpha4 and later versions will use an interface to render the platform, allowing for multiple types to implement the platform rendering interface.
132 lines
2.9 KiB
Go
132 lines
2.9 KiB
Go
// Package errors provides error wrapping with location information
|
|
package errors
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"path/filepath"
|
|
"runtime"
|
|
)
|
|
|
|
// ErrUnsupported is errors.ErrUnsupported
|
|
var ErrUnsupported = errors.ErrUnsupported
|
|
|
|
func NotImplemented() error {
|
|
return wrap(New("not implemented"), 2)
|
|
}
|
|
|
|
// Format calls fmt.Format(format, a...) and wraps the error with the caller
|
|
// source location.
|
|
func Format(format string, a ...any) error {
|
|
return wrap(fmt.Errorf(format, a...), 2)
|
|
}
|
|
|
|
// As calls errors.As
|
|
func As(err error, target any) bool {
|
|
return errors.As(err, target)
|
|
}
|
|
|
|
// Is calls errors.Is
|
|
func Is(err error, target error) bool {
|
|
return errors.Is(err, target)
|
|
}
|
|
|
|
// Join calls errors.Join
|
|
func Join(errs ...error) error {
|
|
return errors.Join(errs...)
|
|
}
|
|
|
|
// New calls errors.New
|
|
func New(text string) error {
|
|
return errors.New(text)
|
|
}
|
|
|
|
// Unwrap calls errors.Unwrap
|
|
func Unwrap(err error) error {
|
|
return errors.Unwrap(err)
|
|
}
|
|
|
|
// Source represents the Source file and line where an error was encountered
|
|
type Source struct {
|
|
File string `json:"file"`
|
|
Line int `json:"line"`
|
|
}
|
|
|
|
func (s *Source) Loc() string {
|
|
gp := filepath.Base(filepath.Dir(filepath.Dir(s.File)))
|
|
p := filepath.Base(filepath.Dir(s.File))
|
|
return fmt.Sprintf("%s/%s/%s:%d", gp, p, filepath.Base(s.File), s.Line)
|
|
}
|
|
|
|
// ErrorAt wraps an error with the Source location the error was encountered at
|
|
// for tracing from a top level error handler.
|
|
type ErrorAt struct {
|
|
Err error
|
|
Source Source
|
|
}
|
|
|
|
// Unwrap implements error wrapping.
|
|
func (e *ErrorAt) Unwrap() error {
|
|
return e.Err
|
|
}
|
|
|
|
// Error returns the error string with Source location.
|
|
func (e *ErrorAt) Error() string {
|
|
return e.Source.Loc() + ": " + e.Err.Error()
|
|
}
|
|
|
|
func wrap(err error, skip int) error {
|
|
// Nothing to do
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// Already a holos error no need to do anything.
|
|
var errAt *ErrorAt
|
|
if errors.As(err, &errAt) {
|
|
return err
|
|
}
|
|
|
|
// Try to wrap err with caller info
|
|
if _, file, line, ok := runtime.Caller(skip); ok {
|
|
return &ErrorAt{
|
|
Err: err,
|
|
Source: Source{
|
|
File: file,
|
|
Line: line,
|
|
},
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Wrap wraps err in a ErrorAt or returns err if err is nil, already a
|
|
// ErrorAt, or caller info is not available.
|
|
//
|
|
// XXX: Refactor to Err(error, ...slog.Attr). Often want to add attributes for
|
|
// the top level logger.
|
|
func Wrap(err error) error {
|
|
return wrap(err, 2)
|
|
}
|
|
|
|
// Log logs err with Source location if Err is a ErrorAt
|
|
func Log(log *slog.Logger, ctx context.Context, level slog.Level, err error, msg string, args ...any) {
|
|
var errAt *ErrorAt
|
|
if ok := errors.As(err, &errAt); ok {
|
|
args = append(args,
|
|
slog.String("err", errAt.Unwrap().Error()),
|
|
slog.String("loc", errAt.Source.Loc()),
|
|
)
|
|
} else {
|
|
if _, file, line, ok := runtime.Caller(1); ok {
|
|
source := Source{file, line}
|
|
args = append(args, slog.String("loc", source.Loc()))
|
|
}
|
|
args = append(args, slog.String("err", err.Error()))
|
|
}
|
|
log.Log(ctx, level, msg, args...)
|
|
}
|