git » chasquid » commit bfb9694

Wrap errors with %w when returning them

author Alberto Bertogli
2026-10-03 10:03:49 UTC
committer Alberto Bertogli
2026-10-03 10:03:49 UTC
parent 8b19760f4edfc117b35f1690a6ecf9deda2fd298

Wrap errors with %w when returning them

Many places where we return errors don't use the %w wrapping, because
they predate that functionality.

This patch changes those to use %w, so callers can inspect the
underlying errors with errors.Is and errors.As if needed in the future.

The text of the errors does not change, and no existing callers inspect
these errors, so there are no behaviour changes.

internal/config/config.go +5 -5
internal/courier/mda.go +1 -1
internal/courier/smtp.go +7 -7
internal/domaininfo/domaininfo.go +1 -1
internal/dovecot/dovecot.go +5 -5
internal/queue/queue.go +2 -2
internal/sts/sts.go +3 -3
internal/userdb/userdb.go +1 -1
test/util/minidns/minidns.go +2 -2

diff --git a/internal/config/config.go b/internal/config/config.go
index 2f3c067..4151000 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -44,13 +44,13 @@ func Load(path, overrides string) (*Config, error) {
 	// Load from the path.
 	buf, err := os.ReadFile(path)
 	if err != nil {
-		return nil, fmt.Errorf("failed to read config at %q: %v", path, err)
+		return nil, fmt.Errorf("failed to read config at %q: %w", path, err)
 	}
 
 	fromFile := &Config{}
 	err = prototext.Unmarshal(buf, fromFile)
 	if err != nil {
-		return nil, fmt.Errorf("parsing config: %v", err)
+		return nil, fmt.Errorf("parsing config: %w", err)
 	}
 	override(c, fromFile)
 
@@ -58,7 +58,7 @@ func Load(path, overrides string) (*Config, error) {
 	fromOverrides := &Config{}
 	err = prototext.Unmarshal([]byte(overrides), fromOverrides)
 	if err != nil {
-		return nil, fmt.Errorf("parsing override: %v", err)
+		return nil, fmt.Errorf("parsing override: %w", err)
 	}
 	override(c, fromOverrides)
 
@@ -67,14 +67,14 @@ func Load(path, overrides string) (*Config, error) {
 	if c.Hostname == "" {
 		c.Hostname, err = os.Hostname()
 		if err != nil {
-			return nil, fmt.Errorf("could not get hostname: %v", err)
+			return nil, fmt.Errorf("could not get hostname: %w", err)
 		}
 	}
 
 	// Validate the GiveUpSendAfter value.
 	if _, err := time.ParseDuration(c.GiveUpSendAfter); err != nil {
 		return nil, fmt.Errorf(
-			"invalid give_up_send_after value %q: %v", c.GiveUpSendAfter, err)
+			"invalid give_up_send_after value %q: %w", c.GiveUpSendAfter, err)
 	}
 
 	return c, nil
diff --git a/internal/courier/mda.go b/internal/courier/mda.go
index ea7ce6e..6898469 100644
--- a/internal/courier/mda.go
+++ b/internal/courier/mda.go
@@ -84,7 +84,7 @@ func (p *MDA) Deliver(from string, to string, data []byte) (error, bool) {
 				permanent = status.ExitStatus() != 75
 			}
 		}
-		err = tr.Errorf("MDA delivery failed: %v - %q", err, string(output))
+		err = tr.Errorf("MDA delivery failed: %w - %q", err, string(output))
 		return err, permanent
 	}
 
diff --git a/internal/courier/smtp.go b/internal/courier/smtp.go
index 6c68576..0af630a 100644
--- a/internal/courier/smtp.go
+++ b/internal/courier/smtp.go
@@ -160,18 +160,18 @@ func (a *attempt) deliver(mx string) (error, bool) {
 retry:
 	conn, err := net.DialTimeout("tcp", mx+":"+*smtpPort, smtpDialTimeout)
 	if err != nil {
-		return a.tr.Errorf("Could not dial: %v", err), false
+		return a.tr.Errorf("Could not dial: %w", err), false
 	}
 	defer conn.Close()
 	conn.SetDeadline(time.Now().Add(smtpTotalTimeout))
 
 	c, err := smtp.NewClient(conn, mx)
 	if err != nil {
-		return a.tr.Errorf("Error creating client: %v", err), false
+		return a.tr.Errorf("Error creating client: %w", err), false
 	}
 
 	if err = c.Hello(a.courier.HelloDomain); err != nil {
-		return a.tr.Errorf("Error saying hello: %v", err), false
+		return a.tr.Errorf("Error saying hello: %w", err), false
 	}
 
 	secLevel := domaininfo.SecLevel_PLAIN
@@ -235,21 +235,21 @@ retry:
 	}
 
 	if err = c.MailAndRcpt(a.from, a.to); err != nil {
-		return a.tr.Errorf("MAIL+RCPT %v", err), smtp.IsPermanent(err)
+		return a.tr.Errorf("MAIL+RCPT %w", err), smtp.IsPermanent(err)
 	}
 
 	w, err := c.Data()
 	if err != nil {
-		return a.tr.Errorf("DATA %v", err), smtp.IsPermanent(err)
+		return a.tr.Errorf("DATA %w", err), smtp.IsPermanent(err)
 	}
 	_, err = w.Write(a.data)
 	if err != nil {
-		return a.tr.Errorf("DATA writing: %v", err), smtp.IsPermanent(err)
+		return a.tr.Errorf("DATA writing: %w", err), smtp.IsPermanent(err)
 	}
 
 	err = w.Close()
 	if err != nil {
-		return a.tr.Errorf("DATA closing %v", err), smtp.IsPermanent(err)
+		return a.tr.Errorf("DATA closing %w", err), smtp.IsPermanent(err)
 	}
 
 	_ = c.Quit()
diff --git a/internal/domaininfo/domaininfo.go b/internal/domaininfo/domaininfo.go
index ae53d82..b206674 100644
--- a/internal/domaininfo/domaininfo.go
+++ b/internal/domaininfo/domaininfo.go
@@ -65,7 +65,7 @@ func (db *DB) Reload() error {
 		_, err := db.store.Get(id, d)
 		if err != nil {
 			tr.Errorf("id %q: %v", id, err)
-			return fmt.Errorf("error loading %q: %v", id, err)
+			return fmt.Errorf("error loading %q: %w", id, err)
 		}
 
 		db.info[d.Name] = d
diff --git a/internal/dovecot/dovecot.go b/internal/dovecot/dovecot.go
index 38eb18d..cb42b11 100644
--- a/internal/dovecot/dovecot.go
+++ b/internal/dovecot/dovecot.go
@@ -119,11 +119,11 @@ func (a *Auth) Exists(user string) (bool, error) {
 	// SPID\t<pid>
 	err = expect(conn, "VERSION\t1")
 	if err != nil {
-		return false, fmt.Errorf("error receiving version: %v", err)
+		return false, fmt.Errorf("error receiving version: %w", err)
 	}
 	err = expect(conn, "SPID\t")
 	if err != nil {
-		return false, fmt.Errorf("error receiving SPID: %v", err)
+		return false, fmt.Errorf("error receiving SPID: %w", err)
 	}
 
 	// Send our version, and then the request.
@@ -140,7 +140,7 @@ func (a *Auth) Exists(user string) (bool, error) {
 	// Get the response, and we're done.
 	resp, err := conn.ReadLine()
 	if err != nil {
-		return false, fmt.Errorf("error receiving response: %v", err)
+		return false, fmt.Errorf("error receiving response: %w", err)
 	} else if strings.HasPrefix(resp, "USER\t1\t") {
 		return true, nil
 	} else if strings.HasPrefix(resp, "NOTFOUND\t") {
@@ -178,7 +178,7 @@ func (a *Auth) Authenticate(user, passwd string) (bool, error) {
 	for {
 		resp, err := conn.ReadLine()
 		if err != nil {
-			return false, fmt.Errorf("error receiving handshake: %v", err)
+			return false, fmt.Errorf("error receiving handshake: %w", err)
 		}
 		if resp == "DONE" {
 			break
@@ -202,7 +202,7 @@ func (a *Auth) Authenticate(user, passwd string) (bool, error) {
 	// Get the response, and we're done.
 	resp, err = conn.ReadLine()
 	if err != nil {
-		return false, fmt.Errorf("error receiving response: %v", err)
+		return false, fmt.Errorf("error receiving response: %w", err)
 	} else if strings.HasPrefix(resp, "OK\t1") {
 		return true, nil
 	} else if strings.HasPrefix(resp, "FAIL\t1") {
diff --git a/internal/queue/queue.go b/internal/queue/queue.go
index cf168da..238cd2a 100644
--- a/internal/queue/queue.go
+++ b/internal/queue/queue.go
@@ -187,7 +187,7 @@ func (q *Queue) Put(tr *trace.Trace, from string, to []string, data []byte) (str
 
 		rcpts, err := q.aliases.Resolve(tr, t)
 		if err != nil {
-			return "", fmt.Errorf("error resolving aliases for %q: %v", t, err)
+			return "", fmt.Errorf("error resolving aliases for %q: %w", t, err)
 		}
 
 		// Add the recipients (after resolving aliases); this conversion is
@@ -218,7 +218,7 @@ func (q *Queue) Put(tr *trace.Trace, from string, to []string, data []byte) (str
 
 	err := item.WriteTo(q.path)
 	if err != nil {
-		return "", tr.Errorf("failed to write item: %v", err)
+		return "", tr.Errorf("failed to write item: %w", err)
 	}
 
 	q.mu.Lock()
diff --git a/internal/sts/sts.go b/internal/sts/sts.go
index aaa4e0f..4b6f369 100644
--- a/internal/sts/sts.go
+++ b/internal/sts/sts.go
@@ -256,7 +256,7 @@ func httpGet(ctx context.Context, url string) ([]byte, error) {
 	// https://tools.ietf.org/html/rfc8461#section-3.2
 	mt, _, err := mime.ParseMediaType(resp.Header.Get("Content-type"))
 	if err != nil {
-		return nil, fmt.Errorf("HTTP media type error: %v", err)
+		return nil, fmt.Errorf("HTTP media type error: %w", err)
 	}
 	if mt != "text/plain" {
 		return nil, ErrInvalidMediaType
@@ -399,7 +399,7 @@ func (c *PolicyCache) load(domain string) (*Policy, error) {
 	if err := p.Check(); err != nil {
 		cacheInvalid.Add(1)
 		return nil, fmt.Errorf(
-			"%s unmarshalled invalid policy %v: %v", domain, p, err)
+			"%s unmarshalled invalid policy %v: %w", domain, p, err)
 	}
 
 	return p, nil
@@ -409,7 +409,7 @@ func (c *PolicyCache) store(domain string, p *Policy) error {
 	data, err := json.Marshal(p)
 	if err != nil {
 		cacheMarshalErrors.Add(1)
-		return fmt.Errorf("%s failed to marshal policy %v, error: %v",
+		return fmt.Errorf("%s failed to marshal policy %v, error: %w",
 			domain, p, err)
 	}
 
diff --git a/internal/userdb/userdb.go b/internal/userdb/userdb.go
index 9dc4724..fe8c759 100644
--- a/internal/userdb/userdb.go
+++ b/internal/userdb/userdb.go
@@ -161,7 +161,7 @@ func (db *DB) AddUser(name, plainPassword string) error {
 	s.Encrypted, err = scrypt.Key([]byte(plainPassword), s.Salt,
 		1<<s.LogN, int(s.R), int(s.P), int(s.KeyLen))
 	if err != nil {
-		return fmt.Errorf("scrypt failed: %v", err)
+		return fmt.Errorf("scrypt failed: %w", err)
 	}
 
 	db.mu.Lock()
diff --git a/test/util/minidns/minidns.go b/test/util/minidns/minidns.go
index f445d4d..d5b33e1 100644
--- a/test/util/minidns/minidns.go
+++ b/test/util/minidns/minidns.go
@@ -181,7 +181,7 @@ func readTCPMessage(conn net.Conn) (*dnsmessage.Message, error) {
 	msg := &dnsmessage.Message{}
 	err = msg.Unpack(data)
 	if err != nil {
-		return nil, fmt.Errorf("%v error unpacking message: %v", addr, err)
+		return nil, fmt.Errorf("%v error unpacking message: %w", addr, err)
 	}
 
 	return msg, nil
@@ -190,7 +190,7 @@ func readTCPMessage(conn net.Conn) (*dnsmessage.Message, error) {
 func writeTCPMessage(conn net.Conn, msg *dnsmessage.Message) error {
 	rbuf, err := msg.Pack()
 	if err != nil {
-		return fmt.Errorf("error packing reply: %v", err)
+		return fmt.Errorf("error packing reply: %w", err)
 	}
 
 	lenHdr := struct{ Len uint16 }{Len: uint16(len(rbuf))}