github.com/blend/go-sdk@v1.20240719.1/ex/multi.go (about)

     1  /*
     2  
     3  Copyright (c) 2024 - Present. Blend Labs, Inc. All rights reserved
     4  Use of this source code is governed by a MIT license that can be found in the LICENSE file.
     5  
     6  */
     7  
     8  package ex
     9  
    10  import (
    11  	"fmt"
    12  	"strings"
    13  )
    14  
    15  // Append appends errors together, creating a multi-error.
    16  func Append(err error, errs ...error) error {
    17  	if len(errs) == 0 {
    18  		return err
    19  	}
    20  	var all []error
    21  	if err != nil {
    22  		if me, ok := err.(Multi); ok {
    23  			all = me
    24  		} else {
    25  			all = append(all, NewWithStackDepth(err, DefaultNewStartDepth+1))
    26  		}
    27  	}
    28  	for _, extra := range errs {
    29  		if extra != nil {
    30  			if _, ok := extra.(Multi); !ok {
    31  				extra = NewWithStackDepth(extra, DefaultNewStartDepth+1)
    32  			}
    33  			all = append(all, extra)
    34  		}
    35  	}
    36  	if len(all) == 0 {
    37  		return nil
    38  	}
    39  	if len(all) == 1 {
    40  		return all[0]
    41  	}
    42  	return Multi(all)
    43  }
    44  
    45  // Multi represents an array of errors.
    46  type Multi []error
    47  
    48  // Unwrap returns all the errors in the multi error (basically itself)
    49  func (m Multi) Unwrap() []error {
    50  	return m
    51  }
    52  
    53  // Error implements error.
    54  func (m Multi) Error() string {
    55  	formatted, _ := m.errorString(10, 5, 0)
    56  	return formatted
    57  }
    58  
    59  // FullError returns the full error message.
    60  func (m Multi) FullError() string {
    61  	formatted, _ := m.errorString(-1, -1, 0)
    62  	return formatted
    63  }
    64  
    65  // errorString returns the error string with a length limit and a depth limit,
    66  // along with the total number of errors in the Multi error tree.
    67  // -1 means no limit.
    68  func (m Multi) errorString(listLengthLimit, depthLimit, depth int) (string, int) {
    69  	if len(m) == 0 {
    70  		return "", 0
    71  	}
    72  	prefix := "\t"
    73  	if depthLimit >= 0 && depth+1 > depthLimit {
    74  		total := countErrors(m)
    75  		return fmt.Sprintf("%s\n%s... depth limit reached ...", m.header(total), prefix), total
    76  	}
    77  
    78  	total := 0
    79  	var points []string
    80  	for i, err := range m {
    81  		if i == listLengthLimit {
    82  			expanded := total
    83  			for _, err := range m[i:] {
    84  				total += countErrors(err)
    85  			}
    86  			points = append(points, fmt.Sprintf("... and %d more", total-expanded))
    87  			break
    88  		}
    89  		if me, ok := err.(Multi); ok {
    90  			formatted, count := me.errorString(listLengthLimit, depthLimit, depth+1)
    91  			points = append(points, fmt.Sprintf("* %s", indent(prefix+prefix, formatted)))
    92  			total += count
    93  			continue
    94  		}
    95  		points = append(points, fmt.Sprintf("* %s", indent(prefix+prefix, err.Error())))
    96  		total++
    97  	}
    98  
    99  	return fmt.Sprintf("%s\n%s%s", m.header(total), prefix, strings.Join(points, "\n"+prefix)), total
   100  }
   101  
   102  func (Multi) header(total int) string {
   103  	if total == 1 {
   104  		return "1 error occurred:"
   105  	}
   106  	return fmt.Sprintf("%d errors occurred:", total)
   107  }
   108  
   109  func countErrors(err error) int {
   110  	if me, ok := err.(Multi); ok {
   111  		count := 0
   112  		for _, err := range me {
   113  			count += countErrors(err)
   114  		}
   115  		return count
   116  	}
   117  	return 1
   118  }
   119  
   120  // Indent functions from https://git.blendlabs.com/blend/go/blob/ad2d96d6f0d62d9a2a2037945663f44d438f6300/sdk/stringutil/indent.go
   121  // to avoid import cycle.
   122  
   123  // indent applies an indent prefix to a given corpus except the first line.
   124  func indent(prefix, corpus string) string {
   125  	return strings.Join(indentLines(prefix, strings.Split(corpus, "\n")), "\n")
   126  }
   127  
   128  // indentLines adds a prefix to a given list of strings except the first string.
   129  func indentLines(prefix string, corpus []string) []string {
   130  	for index := 1; index < len(corpus); index++ {
   131  		corpus[index] = prefix + corpus[index]
   132  	}
   133  	return corpus
   134  }