1// Copyright 2018 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
5package xerrors
6
7import (
8	"fmt"
9	"strings"
10	"unicode"
11	"unicode/utf8"
12
13	"golang.org/x/xerrors/internal"
14)
15
16const percentBangString = "%!"
17
18// Errorf formats according to a format specifier and returns the string as a
19// value that satisfies error.
20//
21// The returned error includes the file and line number of the caller when
22// formatted with additional detail enabled. If the last argument is an error
23// the returned error's Format method will return it if the format string ends
24// with ": %s", ": %v", or ": %w". If the last argument is an error and the
25// format string ends with ": %w", the returned error implements an Unwrap
26// method returning it.
27//
28// If the format specifier includes a %w verb with an error operand in a
29// position other than at the end, the returned error will still implement an
30// Unwrap method returning the operand, but the error's Format method will not
31// return the wrapped error.
32//
33// It is invalid to include more than one %w verb or to supply it with an
34// operand that does not implement the error interface. The %w verb is otherwise
35// a synonym for %v.
36func Errorf(format string, a ...interface{}) error {
37	format = formatPlusW(format)
38	// Support a ": %[wsv]" suffix, which works well with xerrors.Formatter.
39	wrap := strings.HasSuffix(format, ": %w")
40	idx, format2, ok := parsePercentW(format)
41	percentWElsewhere := !wrap && idx >= 0
42	if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) {
43		err := errorAt(a, len(a)-1)
44		if err == nil {
45			return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)}
46		}
47		// TODO: this is not entirely correct. The error value could be
48		// printed elsewhere in format if it mixes numbered with unnumbered
49		// substitutions. With relatively small changes to doPrintf we can
50		// have it optionally ignore extra arguments and pass the argument
51		// list in its entirety.
52		msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...)
53		frame := Frame{}
54		if internal.EnableTrace {
55			frame = Caller(1)
56		}
57		if wrap {
58			return &wrapError{msg, err, frame}
59		}
60		return &noWrapError{msg, err, frame}
61	}
62	// Support %w anywhere.
63	// TODO: don't repeat the wrapped error's message when %w occurs in the middle.
64	msg := fmt.Sprintf(format2, a...)
65	if idx < 0 {
66		return &noWrapError{msg, nil, Caller(1)}
67	}
68	err := errorAt(a, idx)
69	if !ok || err == nil {
70		// Too many %ws or argument of %w is not an error. Approximate the Go
71		// 1.13 fmt.Errorf message.
72		return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)}
73	}
74	frame := Frame{}
75	if internal.EnableTrace {
76		frame = Caller(1)
77	}
78	return &wrapError{msg, err, frame}
79}
80
81func errorAt(args []interface{}, i int) error {
82	if i < 0 || i >= len(args) {
83		return nil
84	}
85	err, ok := args[i].(error)
86	if !ok {
87		return nil
88	}
89	return err
90}
91
92// formatPlusW is used to avoid the vet check that will barf at %w.
93func formatPlusW(s string) string {
94	return s
95}
96
97// Return the index of the only %w in format, or -1 if none.
98// Also return a rewritten format string with %w replaced by %v, and
99// false if there is more than one %w.
100// TODO: handle "%[N]w".
101func parsePercentW(format string) (idx int, newFormat string, ok bool) {
102	// Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go.
103	idx = -1
104	ok = true
105	n := 0
106	sz := 0
107	var isW bool
108	for i := 0; i < len(format); i += sz {
109		if format[i] != '%' {
110			sz = 1
111			continue
112		}
113		// "%%" is not a format directive.
114		if i+1 < len(format) && format[i+1] == '%' {
115			sz = 2
116			continue
117		}
118		sz, isW = parsePrintfVerb(format[i:])
119		if isW {
120			if idx >= 0 {
121				ok = false
122			} else {
123				idx = n
124			}
125			// "Replace" the last character, the 'w', with a 'v'.
126			p := i + sz - 1
127			format = format[:p] + "v" + format[p+1:]
128		}
129		n++
130	}
131	return idx, format, ok
132}
133
134// Parse the printf verb starting with a % at s[0].
135// Return how many bytes it occupies and whether the verb is 'w'.
136func parsePrintfVerb(s string) (int, bool) {
137	// Assume only that the directive is a sequence of non-letters followed by a single letter.
138	sz := 0
139	var r rune
140	for i := 1; i < len(s); i += sz {
141		r, sz = utf8.DecodeRuneInString(s[i:])
142		if unicode.IsLetter(r) {
143			return i + sz, r == 'w'
144		}
145	}
146	return len(s), false
147}
148
149type noWrapError struct {
150	msg   string
151	err   error
152	frame Frame
153}
154
155func (e *noWrapError) Error() string {
156	return fmt.Sprint(e)
157}
158
159func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
160
161func (e *noWrapError) FormatError(p Printer) (next error) {
162	p.Print(e.msg)
163	e.frame.Format(p)
164	return e.err
165}
166
167type wrapError struct {
168	msg   string
169	err   error
170	frame Frame
171}
172
173func (e *wrapError) Error() string {
174	return fmt.Sprint(e)
175}
176
177func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) }
178
179func (e *wrapError) FormatError(p Printer) (next error) {
180	p.Print(e.msg)
181	e.frame.Format(p)
182	return e.err
183}
184
185func (e *wrapError) Unwrap() error {
186	return e.err
187}
188