Skip to content

Commit 0bf6135

Browse files
committed
Improve error handling/messages in Hugo Pipes
Fixes #14257 Closes #14270
1 parent b82e496 commit 0bf6135

File tree

15 files changed

+212
-97
lines changed

15 files changed

+212
-97
lines changed

‎common/herrors/file_error.go‎

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,50 @@ func Unwrap(err error) error {
288288
return err
289289
}
290290

291+
// UnwrapFileErrors returns all FileError contained in err.
292+
func UnwrapFileErrors(err error) []FileError {
293+
if err == nil {
294+
return nil
295+
}
296+
errs := Errors(err)
297+
var fileErrors []FileError
298+
for _, e := range errs {
299+
if v, ok := e.(FileError); ok {
300+
fileErrors = append(fileErrors, v)
301+
}
302+
fileErrors = append(fileErrors, UnwrapFileErrors(errors.Unwrap(e))...)
303+
}
304+
return fileErrors
305+
}
306+
307+
// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
308+
func UnwrapFileErrorsWithErrorContext(err error) []FileError {
309+
errs := UnwrapFileErrors(err)
310+
var n int
311+
for _, e := range errs {
312+
if e.ErrorContext() != nil {
313+
errs[n] = e
314+
n++
315+
}
316+
}
317+
return errs[:n]
318+
}
319+
320+
// Errors returns the list of errors contained in err.
321+
func Errors(err error) []error {
322+
if err == nil {
323+
return nil
324+
}
325+
326+
type unwrapper interface {
327+
Unwrap() []error
328+
}
329+
if u, ok := err.(unwrapper); ok {
330+
return u.Unwrap()
331+
}
332+
return []error{err}
333+
}
334+
291335
func extractFileTypePos(err error) (string, text.Position) {
292336
err = Unwrap(err)
293337

@@ -350,30 +394,6 @@ func UnwrapFileError(err error) FileError {
350394
return nil
351395
}
352396

353-
// UnwrapFileErrors tries to unwrap all FileError.
354-
func UnwrapFileErrors(err error) []FileError {
355-
var errs []FileError
356-
for err != nil {
357-
if v, ok := err.(FileError); ok {
358-
errs = append(errs, v)
359-
}
360-
err = errors.Unwrap(err)
361-
}
362-
return errs
363-
}
364-
365-
// UnwrapFileErrorsWithErrorContext tries to unwrap all FileError in err that has an ErrorContext.
366-
func UnwrapFileErrorsWithErrorContext(err error) []FileError {
367-
var errs []FileError
368-
for err != nil {
369-
if v, ok := err.(FileError); ok && v.ErrorContext() != nil {
370-
errs = append(errs, v)
371-
}
372-
err = errors.Unwrap(err)
373-
}
374-
return errs
375-
}
376-
377397
func extractOffsetAndType(e error) (int, string) {
378398
switch v := e.(type) {
379399
case *json.UnmarshalTypeError:

‎hugolib/hugo_sites.go‎

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ package hugolib
1515

1616
import (
1717
"context"
18+
"errors"
1819
"fmt"
1920
"io"
2021
"iter"
@@ -254,6 +255,16 @@ func (f *fatalErrorHandler) FatalError(err error) {
254255
f.err = err
255256
}
256257

258+
// Stop stops the fatal error handler without setting an error.
259+
func (f *fatalErrorHandler) Stop() {
260+
f.mu.Lock()
261+
defer f.mu.Unlock()
262+
if !f.done {
263+
f.done = true
264+
close(f.donec)
265+
}
266+
}
267+
257268
func (f *fatalErrorHandler) getErr() error {
258269
f.mu.Lock()
259270
defer f.mu.Unlock()
@@ -397,38 +408,35 @@ func (h *HugoSites) onPageRender() {
397408
}
398409
}
399410

400-
func (h *HugoSites) pickOneAndLogTheRest(errors []error) error {
401-
if len(errors) == 0 {
411+
func (h *HugoSites) filterAndJoinErrors(errs []error) error {
412+
if len(errs) == 0 {
402413
return nil
403414
}
404415

405-
var i int
406-
407-
for j, err := range errors {
408-
// If this is in server mode, we want to return an error to the client
409-
// with a file context, if possible.
410-
if herrors.UnwrapFileError(err) != nil {
411-
i = j
412-
break
413-
}
414-
}
415-
416-
// Log the rest, but add a threshold to avoid flooding the log.
417-
const errLogThreshold = 5
418-
419-
for j, err := range errors {
420-
if j == i || err == nil {
416+
seen := map[string]bool{}
417+
var n int
418+
for _, err := range errs {
419+
if err == nil {
421420
continue
422421
}
423-
424-
if j >= errLogThreshold {
425-
break
422+
errMsg := herrors.Cause(err).Error() // We don't need to see many "Could not resolve "@alpinejs/persists""
423+
if !seen[errMsg] {
424+
seen[errMsg] = true
425+
errs[n] = err
426+
n++
426427
}
428+
}
429+
errs = errs[:n]
427430

428-
h.Log.Errorln(err)
431+
for i, err := range errs {
432+
errs[i] = herrors.ImproveRenderErr(err)
429433
}
430434

431-
return errors[i]
435+
const limit = 10
436+
if len(errs) > limit {
437+
errs = errs[:limit]
438+
}
439+
return errors.Join(errs...)
432440
}
433441

434442
func (h *HugoSites) isMultilingual() bool {

‎hugolib/hugo_sites_build.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ func (h *HugoSites) Build(config BuildCfg, events ...fsnotify.Event) error {
117117
}
118118
errors = append(errors, e)
119119
}
120-
to <- h.pickOneAndLogTheRest(errors)
120+
to <- h.filterAndJoinErrors(errors)
121121

122122
close(to)
123123
}(errCollector, errs)

‎hugolib/hugo_sites_build_errors_test.go‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,9 @@ minifyOutput = true
319319
b, err := TestE(t, files)
320320

321321
b.Assert(err, qt.IsNotNil)
322-
fe := herrors.UnwrapFileError(err)
322+
fes := herrors.UnwrapFileErrors(err)
323+
b.Assert(len(fes), qt.Equals, 1)
324+
fe := fes[0]
323325
b.Assert(fe, qt.IsNotNil)
324326
b.Assert(fe.Position().LineNumber, qt.Equals, 2)
325327
b.Assert(fe.Position().ColumnNumber, qt.Equals, 9)
@@ -626,7 +628,9 @@ line3: 'value3'
626628

627629
b.Assert(err, qt.Not(qt.IsNil))
628630
b.Assert(err.Error(), qt.Contains, "[2:1] non-map value is specified")
629-
fe := herrors.UnwrapFileError(err)
631+
fes := herrors.UnwrapFileErrors(err)
632+
b.Assert(len(fes), qt.Equals, 1)
633+
fe := fes[0]
630634
b.Assert(fe, qt.Not(qt.IsNil))
631635
pos := fe.Position()
632636
b.Assert(pos.Filename, qt.Contains, filepath.FromSlash("content/_index.md"))

‎hugolib/integrationtest_builder.go‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -593,11 +593,6 @@ func (s *IntegrationTestBuilder) AssertFileExists(filename string, b bool) {
593593
s.Assert(err, checker)
594594
}
595595

596-
func (s *IntegrationTestBuilder) AssertIsFileError(err error) herrors.FileError {
597-
s.Assert(err, qt.ErrorAs, new(herrors.FileError))
598-
return herrors.UnwrapFileError(err)
599-
}
600-
601596
func (s *IntegrationTestBuilder) AssertRenderCountContent(count int) {
602597
s.Helper()
603598
s.Assert(s.counters.contentRenderCounter.Load(), qt.Equals, uint64(count))

‎hugolib/site.go‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,13 +1546,18 @@ func (s *Site) resetBuildState(sourceChanged bool) {
15461546

15471547
func (s *Site) errorCollator(results <-chan error, errs chan<- error) {
15481548
var errors []error
1549+
defer func() {
1550+
errs <- s.h.filterAndJoinErrors(errors)
1551+
close(errs)
1552+
}()
1553+
const maxErrors = 10
15491554
for e := range results {
15501555
errors = append(errors, e)
1556+
if len(errors) >= maxErrors {
1557+
s.h.Stop()
1558+
break
1559+
}
15511560
}
1552-
1553-
errs <- s.h.pickOneAndLogTheRest(errors)
1554-
1555-
close(errs)
15561561
}
15571562

15581563
// GetPage looks up a page of a given type for the given ref.

‎hugolib/site_render.go‎

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222

2323
"github.com/bep/logg"
2424
"github.com/gohugoio/go-radix"
25-
"github.com/gohugoio/hugo/common/herrors"
2625
"github.com/gohugoio/hugo/hugolib/doctree"
2726
"github.com/gohugoio/hugo/tpl/tplimpl"
2827

@@ -115,7 +114,7 @@ func (s *Site) renderPages(ctx *siteRenderContext) error {
115114

116115
err := <-errs
117116
if err != nil {
118-
return fmt.Errorf("%v failed to render pages: %w", s.resolveDimensionNames(), herrors.ImproveRenderErr(err))
117+
return fmt.Errorf("%v failed to render pages: %w", s.resolveDimensionNames(), err)
119118
}
120119
return nil
121120
}
@@ -129,6 +128,15 @@ func pageRenderer(
129128
) {
130129
defer wg.Done()
131130

131+
sendErr := func(err error) bool {
132+
select {
133+
case results <- err:
134+
return true
135+
case <-s.h.Done():
136+
return false
137+
}
138+
}
139+
132140
for p := range pages {
133141

134142
if p.m.isStandalone() && !ctx.shouldRenderStandalonePage(p.Kind()) {
@@ -137,8 +145,11 @@ func pageRenderer(
137145

138146
if p.m.pageConfig.Build.PublishResources {
139147
if err := p.renderResources(); err != nil {
140-
s.SendError(p.errorf(err, "failed to render page resources"))
141-
continue
148+
if sendErr(p.errorf(err, "failed to render resources")) {
149+
continue
150+
} else {
151+
return
152+
}
142153
}
143154
}
144155

@@ -149,8 +160,11 @@ func pageRenderer(
149160

150161
templ, found, err := p.resolveTemplate()
151162
if err != nil {
152-
s.SendError(p.errorf(err, "failed to resolve template"))
153-
continue
163+
if sendErr(p.errorf(err, "failed to resolve template")) {
164+
continue
165+
} else {
166+
return
167+
}
154168
}
155169

156170
if !found {
@@ -181,12 +195,20 @@ func pageRenderer(
181195
}
182196

183197
if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, targetPath, p, d, templ); err != nil {
184-
results <- err
198+
if sendErr(err) {
199+
continue
200+
} else {
201+
return
202+
}
185203
}
186204

187205
if p.paginator != nil && p.paginator.current != nil {
188206
if err := s.renderPaginator(p, templ); err != nil {
189-
results <- err
207+
if sendErr(err) {
208+
continue
209+
} else {
210+
return
211+
}
190212
}
191213
}
192214
}

‎main.go‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,19 @@ import (
1717
"log"
1818
"os"
1919

20+
"github.com/gohugoio/hugo/common/herrors"
21+
"github.com/gohugoio/hugo/common/loggers"
22+
2023
"github.com/gohugoio/hugo/commands"
2124
)
2225

2326
func main() {
2427
log.SetFlags(0)
2528
err := commands.Execute(os.Args[1:])
2629
if err != nil {
27-
log.Fatalf("Error: %s", err)
30+
for _, e := range herrors.Errors(err) {
31+
loggers.Log().Errorf("%s", e)
32+
}
33+
os.Exit(1)
2834
}
2935
}

‎markup/tableofcontents/tableofcontents_integration_test.go‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"strings"
1818
"testing"
1919

20+
qt "github.com/frankban/quicktest"
2021
"github.com/gohugoio/hugo/hugolib"
2122
)
2223

@@ -118,8 +119,9 @@ CONTENT
118119

119120
files = strings.ReplaceAll(files, `2`, `"x"`)
120121

121-
b, _ = hugolib.TestE(t, files)
122-
b.AssertLogMatches(`error calling ToHTML: startLevel: unable to cast "x" of type string`)
122+
b, err := hugolib.TestE(t, files)
123+
b.Assert(err, qt.Not(qt.IsNil))
124+
b.Assert(err.Error(), qt.Contains, `error calling ToHTML: startLevel: unable to cast "x" of type string`)
123125
}
124126

125127
func TestHeadingsNilpointerIssue11843(t *testing.T) {

0 commit comments

Comments
 (0)