github.com/octohelm/cuemod@v0.9.4/internal/cmd/go/internals/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  	"golang.org/x/mod/module"
    12  )
    13  
    14  // BuildListError decorates an error that occurred gathering requirements
    15  // while constructing a build list. BuildListError prints the chain
    16  // of requirements to the module where the error occurred.
    17  type BuildListError struct {
    18  	Err   error
    19  	stack []buildListErrorElem
    20  }
    21  
    22  type buildListErrorElem struct {
    23  	m module.Version
    24  
    25  	// nextReason is the reason this module depends on the next module in the
    26  	// stack. Typically either "requires", or "updating to".
    27  	nextReason string
    28  }
    29  
    30  // NewBuildListError returns a new BuildListError wrapping an error that
    31  // occurred at a module found along the given path of requirements and/or
    32  // upgrades, which must be non-empty.
    33  //
    34  // The isVersionChange function reports whether a path step is due to an
    35  // explicit upgrade or downgrade (as opposed to an existing requirement in a
    36  // go.mod file). A nil isVersionChange function indicates that none of the path
    37  // steps are due to explicit version changes.
    38  func NewBuildListError(err error, path []module.Version, isVersionChange func(from, to module.Version) bool) *BuildListError {
    39  	stack := make([]buildListErrorElem, 0, len(path))
    40  	for len(path) > 1 {
    41  		reason := "requires"
    42  		if isVersionChange != nil && isVersionChange(path[0], path[1]) {
    43  			reason = "updating to"
    44  		}
    45  		stack = append(stack, buildListErrorElem{
    46  			m:          path[0],
    47  			nextReason: reason,
    48  		})
    49  		path = path[1:]
    50  	}
    51  	stack = append(stack, buildListErrorElem{m: path[0]})
    52  
    53  	return &BuildListError{
    54  		Err:   err,
    55  		stack: stack,
    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) Module() module.Version {
    62  	if len(e.stack) == 0 {
    63  		return module.Version{}
    64  	}
    65  	return e.stack[len(e.stack)-1].m
    66  }
    67  
    68  func (e *BuildListError) 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 && stack[0].m.Version == "" {
    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, "%s %s\n\t", elem.m, elem.nextReason)
    84  		}
    85  		// Ensure that the final module path and version are included as part of the
    86  		// error message.
    87  		m := stack[len(stack)-1].m
    88  		if mErr, ok := e.Err.(*module.ModuleError); ok {
    89  			actual := module.Version{Path: mErr.Path, Version: mErr.Version}
    90  			if v, ok := mErr.Err.(*module.InvalidVersionError); ok {
    91  				actual.Version = v.Version
    92  			}
    93  			if actual == m {
    94  				fmt.Fprintf(b, "%v", e.Err)
    95  			} else {
    96  				fmt.Fprintf(b, "%s (replaced by %s): %v", m, actual, mErr.Err)
    97  			}
    98  		} else {
    99  			fmt.Fprintf(b, "%v", module.VersionError(m, e.Err))
   100  		}
   101  	}
   102  	return b.String()
   103  }
   104  
   105  func (e *BuildListError) Unwrap() error { return e.Err }