mirror of
https://github.com/holos-run/holos.git
synced 2026-03-20 09:15:02 +00:00
68 lines
2.1 KiB
Go
68 lines
2.1 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"connectrpc.com/connect"
|
|
cue "cuelang.org/go/cue/errors"
|
|
"github.com/holos-run/holos/internal/errors"
|
|
"github.com/holos-run/holos/internal/holos"
|
|
"google.golang.org/genproto/googleapis/rpc/errdetails"
|
|
)
|
|
|
|
// MakeMain makes a main function for the cli or tests.
|
|
func MakeMain(options ...holos.Option) func() int {
|
|
return func() (exitCode int) {
|
|
cfg := holos.New(options...)
|
|
slog.SetDefault(cfg.Logger())
|
|
ctx := context.Background()
|
|
feature := &holos.EnvFlagger{}
|
|
if err := New(cfg, feature).ExecuteContext(ctx); err != nil {
|
|
return HandleError(ctx, err, cfg)
|
|
}
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// HandleError is the top level error handler that unwraps and logs errors.
|
|
func HandleError(ctx context.Context, err error, hc *holos.Config) (exitCode int) {
|
|
// Connect errors have codes, log them.
|
|
log := hc.NewTopLevelLogger().With("code", connect.CodeOf(err))
|
|
var cueErr cue.Error
|
|
var errAt *errors.ErrorAt
|
|
if errors.As(err, &errAt) {
|
|
loc := errAt.Source.Loc()
|
|
err2 := errAt.Unwrap()
|
|
log.ErrorContext(ctx, fmt.Sprintf("could not run: %s at %s", err2, loc), "err", err2, "loc", loc)
|
|
} else {
|
|
log.ErrorContext(ctx, fmt.Sprintf("could not run: %s", err), "err", err)
|
|
}
|
|
// cue errors are bundled up as a list and refer to multiple files / lines.
|
|
if errors.As(err, &cueErr) {
|
|
msg := cue.Details(cueErr, nil)
|
|
_, _ = fmt.Fprint(hc.Stderr(), msg)
|
|
}
|
|
// connect errors have details and codes.
|
|
// Refer to https://connectrpc.com/docs/go/errors
|
|
if connectErr := new(connect.Error); errors.As(err, &connectErr) {
|
|
for _, detail := range connectErr.Details() {
|
|
msg, valueErr := detail.Value()
|
|
if valueErr != nil {
|
|
log.WarnContext(ctx, "could not decode error detail", "err", err, "type", detail.Type(), "note", "this usually means we don't have the schema for the protobuf message type")
|
|
continue
|
|
}
|
|
if info, ok := msg.(*errdetails.ErrorInfo); ok {
|
|
logDetail := log.With("reason", info.GetReason(), "domain", info.GetDomain())
|
|
for k, v := range info.GetMetadata() {
|
|
logDetail = logDetail.With(k, v)
|
|
}
|
|
logDetail.ErrorContext(ctx, info.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1
|
|
}
|