cuelang.org/go@v0.10.1/internal/mod/mvs/errors.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package mvs
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  )
    11  
    12  // BuildListError decorates an error that occurred gathering requirements
    13  // while constructing a build list. BuildListError prints the chain
    14  // of requirements to the module where the error occurred.
    15  type BuildListError[V comparable] struct {
    16  	Err   error
    17  	stack []buildListErrorElem[V]
    18  	vs    Versions[V]
    19  }
    20  
    21  type buildListErrorElem[V comparable] struct {
    22  	m V
    23  
    24  	// nextReason is the reason this module depends on the next module in the
    25  	// stack. Typically either "requires", or "updating to".
    26  	nextReason string
    27  }
    28  
    29  // NewBuildListError returns a new BuildListError wrapping an error that
    30  // occurred at a module found along the given path of requirements and/or
    31  // upgrades, which must be non-empty.
    32  //
    33  // The isVersionChange function reports whether a path step is due to an
    34  // explicit upgrade or downgrade (as opposed to an existing requirement in a
    35  // go.mod file). A nil isVersionChange function indicates that none of the path
    36  // steps are due to explicit version changes.
    37  func NewBuildListError[V comparable](err error, path []V, vs Versions[V], isVersionChange func(from, to V) bool) *BuildListError[V] {
    38  	stack := make([]buildListErrorElem[V], 0, len(path))
    39  	for len(path) > 1 {
    40  		reason := "requires"
    41  		if isVersionChange != nil && isVersionChange(path[0], path[1]) {
    42  			reason = "updating to"
    43  		}
    44  		stack = append(stack, buildListErrorElem[V]{
    45  			m:          path[0],
    46  			nextReason: reason,
    47  		})
    48  		path = path[1:]
    49  	}
    50  	stack = append(stack, buildListErrorElem[V]{m: path[0]})
    51  
    52  	return &BuildListError[V]{
    53  		Err:   err,
    54  		stack: stack,
    55  		vs:    vs,
    56  	}
    57  }
    58  
    59  // Module returns the module where the error occurred. If the module stack
    60  // is empty, this returns a zero value.
    61  func (e *BuildListError[V]) Module() V {
    62  	if len(e.stack) == 0 {
    63  		return *new(V)
    64  	}
    65  	return e.stack[len(e.stack)-1].m
    66  }
    67  
    68  func (e *BuildListError[V]) Error() string {
    69  	b := &strings.Builder{}
    70  	stack := e.stack
    71  
    72  	// Don't print modules at the beginning of the chain without a
    73  	// version. These always seem to be the main module or a
    74  	// synthetic module ("target@").
    75  	for len(stack) > 0 && e.vs.Version(stack[0].m) == "" {
    76  		stack = stack[1:]
    77  	}
    78  
    79  	if len(stack) == 0 {
    80  		b.WriteString(e.Err.Error())
    81  	} else {
    82  		for _, elem := range stack[:len(stack)-1] {
    83  			fmt.Fprintf(b, "%v %s\n\t", elem.m, elem.nextReason)
    84  		}
    85  		m := stack[len(stack)-1].m
    86  		fmt.Fprintf(b, "%v: %v", m, e.Err)
    87  		// TODO the original mvs code was careful to ensure that the final module path
    88  		// and version were included as part of the error message, but it did that
    89  		// by checking for mod/module-specific error types, but we don't want this
    90  		// package to depend on module. We could potentially do it by making those
    91  		// errors implement interface types defined in this package.
    92  	}
    93  	return b.String()
    94  }