chore: do not use sentinel errors when unneeded

- main reason being it's a burden to always define sentinel errors at global scope, wrap them with `%w` instead of using a string directly
- only use sentinel errors when it has to be checked using `errors.Is`
- replace all usage of these sentinel errors in `fmt.Errorf` with direct strings that were in the sentinel error
- exclude the sentinel error definition requirement from .golangci.yml
- update unit tests to use ContainersError instead of ErrorIs so it stays as a "not a change detector test" without requiring a sentinel error
This commit is contained in:
Quentin McGaw
2026-05-02 00:50:16 +00:00
parent 9b6f048fe8
commit 4a78989d9d
172 changed files with 666 additions and 1433 deletions
+5 -10
View File
@@ -1,7 +1,6 @@
package api
import (
"errors"
"fmt"
"maps"
"net/http"
@@ -58,8 +57,6 @@ func New(nameTokenPairs []NameToken, client *http.Client) (
var regexEchoipURL = regexp.MustCompile(`^http(s|):\/\/.+$`)
var ErrProviderNotValid = errors.New("API name is not valid")
func ParseProvider(s string) (provider Provider, err error) {
possibleProviders := []Provider{
Cloudflare,
@@ -97,12 +94,10 @@ func ParseProvider(s string) (provider Provider, err error) {
providerStrings = append(providerStrings, "a custom "+prefix+" url")
}
return "", fmt.Errorf(`%w: %q can only be %s`,
ErrProviderNotValid, s, orStrings(providerStrings))
return "", fmt.Errorf("API name is not valid: %q can only be %s",
s, orStrings(providerStrings))
}
var ErrCustomURLNotValid = errors.New("custom URL is not valid")
func checkCustomURL(s, prefix string, regex *regexp.Regexp) (match bool, err error) {
if !strings.HasPrefix(s, prefix) {
return false, nil
@@ -110,15 +105,15 @@ func checkCustomURL(s, prefix string, regex *regexp.Regexp) (match bool, err err
s = strings.TrimPrefix(s, prefix)
_, err = url.Parse(s)
if err != nil {
return true, fmt.Errorf("%s %w: %w", prefix, ErrCustomURLNotValid, err)
return true, fmt.Errorf("%s custom URL is not valid: %w", prefix, err)
}
if regex.MatchString(s) {
return true, nil
}
return true, fmt.Errorf("%s %w: %q does not match regular expression: %s",
prefix, ErrCustomURLNotValid, s, regex)
return true, fmt.Errorf("%s custom URL is not valid: "+
"%q does not match regular expression: %s", prefix, s, regex)
}
func orStrings(strings []string) (result string) {
+6 -10
View File
@@ -12,17 +12,14 @@ func Test_ParseProvider(t *testing.T) {
testCases := map[string]struct {
s string
provider Provider
errWrapped error
errMessage string
}{
"empty": {
errWrapped: ErrProviderNotValid,
errMessage: `API name is not valid: "" can only be ` +
`"cloudflare", "ifconfigco", "ip2location", "ipinfo" or a custom echoip# url`,
},
"invalid": {
s: "xyz",
errWrapped: ErrProviderNotValid,
s: "xyz",
errMessage: `API name is not valid: "xyz" can only be ` +
`"cloudflare", "ifconfigco", "ip2location", "ipinfo" or a custom echoip# url`,
},
@@ -35,14 +32,12 @@ func Test_ParseProvider(t *testing.T) {
provider: IPInfo,
},
"echoip_url_empty": {
s: "echoip#",
errWrapped: ErrCustomURLNotValid,
s: "echoip#",
errMessage: `echoip# custom URL is not valid: "" ` +
`does not match regular expression: ^http(s|):\/\/.+$`,
},
"echoip_url_invalid": {
s: "echoip#postgres://localhost:3451",
errWrapped: ErrCustomURLNotValid,
s: "echoip#postgres://localhost:3451",
errMessage: `echoip# custom URL is not valid: "postgres://localhost:3451" ` +
`does not match regular expression: ^http(s|):\/\/.+$`,
},
@@ -59,9 +54,10 @@ func Test_ParseProvider(t *testing.T) {
provider, err := ParseProvider(testCase.s)
assert.Equal(t, testCase.provider, provider)
assert.ErrorIs(t, err, testCase.errWrapped)
if testCase.errWrapped != nil {
if testCase.errMessage != "" {
assert.EqualError(t, err, testCase.errMessage)
} else {
assert.NoError(t, err)
}
})
}
+4 -4
View File
@@ -50,8 +50,8 @@ func (c *cloudflare) FetchInfo(ctx context.Context, ip netip.Addr) (
urlBase := "https://speed.cloudflare.com"
url := urlBase + "/meta"
if ip.IsValid() {
return result, fmt.Errorf("%w: cloudflare cannot provide information on the arbitrary IP address %s",
ErrServiceLimited, ip)
return result, fmt.Errorf("service is limited: "+
"cloudflare cannot provide information on the arbitrary IP address %s", ip)
}
request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
@@ -72,8 +72,8 @@ func (c *cloudflare) FetchInfo(ctx context.Context, ip netip.Addr) (
return result, fmt.Errorf("%w from %s: %d %s",
ErrTooManyRequests, url, response.StatusCode, response.Status)
default:
return result, fmt.Errorf("%w from %s: %d %s",
ErrBadHTTPStatus, url, response.StatusCode, response.Status)
return result, fmt.Errorf("bad HTTP status received from %s: %d %s",
url, response.StatusCode, response.Status)
}
decoder := json.NewDecoder(response.Body)
+2 -4
View File
@@ -70,11 +70,9 @@ func (e *echoip) FetchInfo(ctx context.Context, ip netip.Addr) (
switch response.StatusCode {
case http.StatusOK:
case http.StatusTooManyRequests:
return result, fmt.Errorf("%w from %s: %s",
ErrTooManyRequests, url, response.Status)
return result, fmt.Errorf("%w from %s: %s", ErrTooManyRequests, url, response.Status)
default:
return result, fmt.Errorf("%w from %s: %s",
ErrBadHTTPStatus, url, response.Status)
return result, fmt.Errorf("bad HTTP status received from %s: %s", url, response.Status)
}
decoder := json.NewDecoder(response.Body)
+1 -6
View File
@@ -2,9 +2,4 @@ package api
import "errors"
var (
ErrTokenNotValid = errors.New("token is not valid")
ErrTooManyRequests = errors.New("too many requests sent for this month")
ErrBadHTTPStatus = errors.New("bad HTTP status received")
ErrServiceLimited = errors.New("service is limited")
)
var ErrTooManyRequests = errors.New("too many requests sent for this month")
+3 -3
View File
@@ -75,7 +75,7 @@ func (i *ip2Location) FetchInfo(ctx context.Context, ip netip.Addr) (
defer response.Body.Close()
if i.token != "" && response.StatusCode == http.StatusUnauthorized {
return result, fmt.Errorf("%w: %s", ErrTokenNotValid, response.Status)
return result, fmt.Errorf("token is not valid: %s", response.Status)
}
switch response.StatusCode {
@@ -84,8 +84,8 @@ func (i *ip2Location) FetchInfo(ctx context.Context, ip netip.Addr) (
return result, fmt.Errorf("%w from %s: %d %s",
ErrTooManyRequests, url, response.StatusCode, response.Status)
default:
return result, fmt.Errorf("%w from %s: %d %s",
ErrBadHTTPStatus, url, response.StatusCode, response.Status)
return result, fmt.Errorf("bad HTTP status received from %s: %d %s",
url, response.StatusCode, response.Status)
}
decoder := json.NewDecoder(response.Body)
+3 -3
View File
@@ -70,7 +70,7 @@ func (i *ipInfo) FetchInfo(ctx context.Context, ip netip.Addr) (
defer response.Body.Close()
if i.token != "" && response.StatusCode == http.StatusUnauthorized {
return result, fmt.Errorf("%w: %s", ErrTokenNotValid, response.Status)
return result, fmt.Errorf("token is not valid: %s", response.Status)
}
switch response.StatusCode {
@@ -79,8 +79,8 @@ func (i *ipInfo) FetchInfo(ctx context.Context, ip netip.Addr) (
return result, fmt.Errorf("%w from %s: %d %s",
ErrTooManyRequests, url, response.StatusCode, response.Status)
default:
return result, fmt.Errorf("%w from %s: %d %s",
ErrBadHTTPStatus, url, response.StatusCode, response.Status)
return result, fmt.Errorf("bad HTTP status received from %s: %d %s",
url, response.StatusCode, response.Status)
}
decoder := json.NewDecoder(response.Body)