diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-01-17 14:16:29 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-01-17 14:41:00 +0900 |
| commit | efc90c3221f42562a9758b0bbdabf30cc95b8f7b (patch) | |
| tree | b9a85a44c85fcb11af5dd1440137558b11837e14 /internal/pkg/pkg.go | |
| parent | 610ee13ab395768ff5f2dfaff899fb3ba2a5a514 (diff) | |
internal/pkg: deduplicate dependency errors
This significantly simplifies error reporting for caller.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pkg/pkg.go')
| -rw-r--r-- | internal/pkg/pkg.go | 82 |
1 files changed, 74 insertions, 8 deletions
diff --git a/internal/pkg/pkg.go b/internal/pkg/pkg.go index e0b50cfc..65bf5e17 100644 --- a/internal/pkg/pkg.go +++ b/internal/pkg/pkg.go @@ -394,7 +394,7 @@ type pendingArtifactDep struct { // Address of result error slice populated during [Cache.Cure], dereferenced // after acquiring errsMu if curing fails. No additional action is taken, // [Cache] and its caller are responsible for further error handling. - errs *[]error + errs *DependencyCureError // Address of mutex synchronising access to errs. errsMu *sync.Mutex @@ -1108,6 +1108,76 @@ func (c *Cache) Cure(a Artifact) ( return c.cure(a) } +// CureError wraps a non-nil error returned attempting to cure an [Artifact]. +type CureError struct { + Ident unique.Handle[ID] + Err error +} + +// Unwrap returns the underlying error. +func (e *CureError) Unwrap() error { return e.Err } + +// Error returns the error message from the underlying Err. +func (e *CureError) Error() string { return e.Err.Error() } + +// A DependencyCureError wraps errors returned while curing dependencies. +type DependencyCureError []*CureError + +// sort sorts underlying errors by their identifier. +func (e *DependencyCureError) sort() { + var identBuf [2]ID + slices.SortFunc(*e, func(a, b *CureError) int { + identBuf[0], identBuf[1] = a.Ident.Value(), b.Ident.Value() + return slices.Compare(identBuf[0][:], identBuf[1][:]) + }) +} + +// unwrap recursively expands and deduplicates underlying errors. +func (e *DependencyCureError) unwrap() DependencyCureError { + errs := make(DependencyCureError, 0, len(*e)) + for _, err := range *e { + if _e, ok := err.Err.(*DependencyCureError); ok { + errs = append(errs, _e.unwrap()...) + continue + } + errs = append(errs, err) + } + me := make(map[unique.Handle[ID]]*CureError, len(errs)) + for _, err := range errs { + me[err.Ident] = err + } + return slices.AppendSeq( + make(DependencyCureError, 0, len(me)), + maps.Values(me), + ) +} + +// Unwrap returns a deduplicated slice of underlying errors. +func (e *DependencyCureError) Unwrap() []error { + errs := e.unwrap() + errs.sort() + _errs := make([]error, len(errs)) + for i, err := range errs { + _errs[i] = err + } + return _errs +} + +// Error returns a user-facing multiline error message. +func (e *DependencyCureError) Error() string { + errs := e.unwrap() + errs.sort() + if len(errs) == 0 { + return "invalid dependency cure outcome" + } + var buf strings.Builder + buf.WriteString("errors curing dependencies:") + for _, err := range errs { + buf.WriteString("\n\t" + Encode(err.Ident.Value()) + ": " + err.Error()) + } + return buf.String() +} + // cure implements Cure without checking the full dependency graph. func (c *Cache) cure(a Artifact) ( pathname *check.Absolute, @@ -1290,7 +1360,7 @@ func (c *Cache) cure(a Artifact) ( var wg sync.WaitGroup wg.Add(len(deps)) res := make([]*check.Absolute, len(deps)) - errs := make([]error, 0, len(deps)) + errs := make(DependencyCureError, 0, len(deps)) var errsMu sync.Mutex for i, d := range deps { pending := pendingArtifactDep{d, &res[i], &errs, &errsMu, &wg} @@ -1306,11 +1376,7 @@ func (c *Cache) cure(a Artifact) ( wg.Wait() if len(errs) > 0 { - err = errors.Join(errs...) - if err == nil { - // unreachable - err = syscall.ENOTRECOVERABLE - } + err = &errs return } for i, p := range res { @@ -1411,7 +1477,7 @@ func (pending *pendingArtifactDep) cure(c *Cache) { } pending.errsMu.Lock() - *pending.errs = append(*pending.errs, err) + *pending.errs = append(*pending.errs, &CureError{c.Ident(pending.a), err}) pending.errsMu.Unlock() } |
