Add must.Do utility function (#6955)

This can take two values (typically the return values of a two-value
function) and panic if the error is non-nil, returning the interesting
value. This is particularly useful for cases where we statically know
the call will succeed.

Thanks to @mcpherrinm for the idea!
This commit is contained in:
Jacob Hoffman-Andrews 2023-06-26 14:43:30 -07:00 committed by GitHub
parent 620699216f
commit 8dcbc4c92f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 48 additions and 35 deletions

View File

@ -39,6 +39,7 @@ import (
"github.com/letsencrypt/boulder/linter"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/must"
"github.com/letsencrypt/boulder/policy"
sapb "github.com/letsencrypt/boulder/sa/proto"
"github.com/letsencrypt/boulder/test"
@ -107,11 +108,7 @@ const caCertFile = "../test/test-ca.pem"
const caCertFile2 = "../test/test-ca2.pem"
func mustRead(path string) []byte {
b, err := os.ReadFile(path)
if err != nil {
panic(fmt.Sprintf("unable to read %#v: %s", path, err))
}
return b
return must.Do(os.ReadFile(path))
}
type testCtx struct {

15
must/must.go Normal file
View File

@ -0,0 +1,15 @@
package must
// Do panics if err is not nil, otherwise returns t.
// It is useful in wrapping a two-value function call
// where you know statically that the call will succeed.
//
// Example:
//
// url := must.Do(url.Parse("http://example.com"))
func Do[T any](t T, err error) T {
if err != nil {
panic(err)
}
return t
}

13
must/must_test.go Normal file
View File

@ -0,0 +1,13 @@
package must
import (
"net/url"
"testing"
)
func TestDo(t *testing.T) {
url := Do(url.Parse("http://example.com"))
if url.Host != "example.com" {
t.Errorf("expected host to be example.com, got %s", url.Host)
}
}

View File

@ -9,6 +9,7 @@ import (
"github.com/letsencrypt/boulder/features"
"github.com/letsencrypt/boulder/identifier"
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/must"
"github.com/letsencrypt/boulder/test"
"gopkg.in/yaml.v3"
)
@ -394,19 +395,13 @@ func TestChallengesForWildcard(t *testing.T) {
Value: "*.zombo.com",
}
mustConstructPA := func(t *testing.T, enabledChallenges map[core.AcmeChallenge]bool) *AuthorityImpl {
pa, err := New(enabledChallenges, blog.NewMock())
test.AssertNotError(t, err, "Couldn't create policy implementation")
return pa
}
// First try to get a challenge for the wildcard ident without the
// DNS-01 challenge type enabled. This should produce an error
var enabledChallenges = map[core.AcmeChallenge]bool{
core.ChallengeTypeHTTP01: true,
core.ChallengeTypeDNS01: false,
}
pa := mustConstructPA(t, enabledChallenges)
pa := must.Do(New(enabledChallenges, blog.NewMock()))
_, err := pa.ChallengesFor(wildcardIdent)
test.AssertError(t, err, "ChallengesFor did not error for a wildcard ident "+
"when DNS-01 was disabled")
@ -416,7 +411,7 @@ func TestChallengesForWildcard(t *testing.T) {
// Try again with DNS-01 enabled. It should not error and
// should return only one DNS-01 type challenge
enabledChallenges[core.ChallengeTypeDNS01] = true
pa = mustConstructPA(t, enabledChallenges)
pa = must.Do(New(enabledChallenges, blog.NewMock()))
challenges, err := pa.ChallengesFor(wildcardIdent)
test.AssertNotError(t, err, "ChallengesFor errored for a wildcard ident "+
"unexpectedly")

View File

@ -21,6 +21,7 @@ import (
"github.com/letsencrypt/boulder/core"
berrors "github.com/letsencrypt/boulder/errors"
"github.com/letsencrypt/boulder/identifier"
"github.com/letsencrypt/boulder/must"
"github.com/letsencrypt/boulder/probs"
"github.com/letsencrypt/boulder/test"
"github.com/miekg/dns"
@ -195,13 +196,8 @@ func TestHTTPValidationTarget(t *testing.T) {
}
func TestExtractRequestTarget(t *testing.T) {
mustURL := func(t *testing.T, rawURL string) *url.URL {
urlOb, err := url.Parse(rawURL)
if err != nil {
t.Fatalf("Unable to parse raw URL %q: %v", rawURL, err)
return nil
}
return urlOb
mustURL := func(rawURL string) *url.URL {
return must.Do(url.Parse(rawURL))
}
testCases := []struct {
@ -218,7 +214,7 @@ func TestExtractRequestTarget(t *testing.T) {
{
Name: "invalid protocol scheme",
Req: &http.Request{
URL: mustURL(t, "gopher://letsencrypt.org"),
URL: mustURL("gopher://letsencrypt.org"),
},
ExpectedError: fmt.Errorf("Invalid protocol scheme in redirect target. " +
`Only "http" and "https" protocol schemes are supported, ` +
@ -227,7 +223,7 @@ func TestExtractRequestTarget(t *testing.T) {
{
Name: "invalid explicit port",
Req: &http.Request{
URL: mustURL(t, "https://weird.port.letsencrypt.org:9999"),
URL: mustURL("https://weird.port.letsencrypt.org:9999"),
},
ExpectedError: fmt.Errorf("Invalid port in redirect target. Only ports 80 " +
"and 443 are supported, not 9999"),
@ -235,28 +231,28 @@ func TestExtractRequestTarget(t *testing.T) {
{
Name: "invalid empty hostname",
Req: &http.Request{
URL: mustURL(t, "https:///who/needs/a/hostname?not=me"),
URL: mustURL("https:///who/needs/a/hostname?not=me"),
},
ExpectedError: errors.New("Invalid empty hostname in redirect target"),
},
{
Name: "invalid .well-known hostname",
Req: &http.Request{
URL: mustURL(t, "https://my.webserver.is.misconfigured.well-known/acme-challenge/xxx"),
URL: mustURL("https://my.webserver.is.misconfigured.well-known/acme-challenge/xxx"),
},
ExpectedError: errors.New(`Invalid host in redirect target "my.webserver.is.misconfigured.well-known". Check webserver config for missing '/' in redirect target.`),
},
{
Name: "invalid non-iana hostname",
Req: &http.Request{
URL: mustURL(t, "https://my.tld.is.cpu/pretty/cool/right?yeah=Ithoughtsotoo"),
URL: mustURL("https://my.tld.is.cpu/pretty/cool/right?yeah=Ithoughtsotoo"),
},
ExpectedError: errors.New("Invalid hostname in redirect target, must end in IANA registered TLD"),
},
{
Name: "bare IP",
Req: &http.Request{
URL: mustURL(t, "https://10.10.10.10"),
URL: mustURL("https://10.10.10.10"),
},
ExpectedError: fmt.Errorf(`Invalid host in redirect target "10.10.10.10". ` +
"Only domain names are supported, not IP addresses"),
@ -264,7 +260,7 @@ func TestExtractRequestTarget(t *testing.T) {
{
Name: "valid HTTP redirect, explicit port",
Req: &http.Request{
URL: mustURL(t, "http://cpu.letsencrypt.org:80"),
URL: mustURL("http://cpu.letsencrypt.org:80"),
},
ExpectedHost: "cpu.letsencrypt.org",
ExpectedPort: 80,
@ -272,7 +268,7 @@ func TestExtractRequestTarget(t *testing.T) {
{
Name: "valid HTTP redirect, implicit port",
Req: &http.Request{
URL: mustURL(t, "http://cpu.letsencrypt.org"),
URL: mustURL("http://cpu.letsencrypt.org"),
},
ExpectedHost: "cpu.letsencrypt.org",
ExpectedPort: 80,
@ -280,7 +276,7 @@ func TestExtractRequestTarget(t *testing.T) {
{
Name: "valid HTTPS redirect, explicit port",
Req: &http.Request{
URL: mustURL(t, "https://cpu.letsencrypt.org:443/hello.world"),
URL: mustURL("https://cpu.letsencrypt.org:443/hello.world"),
},
ExpectedHost: "cpu.letsencrypt.org",
ExpectedPort: 443,
@ -288,7 +284,7 @@ func TestExtractRequestTarget(t *testing.T) {
{
Name: "valid HTTPS redirect, implicit port",
Req: &http.Request{
URL: mustURL(t, "https://cpu.letsencrypt.org/hello.world"),
URL: mustURL("https://cpu.letsencrypt.org/hello.world"),
},
ExpectedHost: "cpu.letsencrypt.org",
ExpectedPort: 443,

View File

@ -47,6 +47,7 @@ import (
blog "github.com/letsencrypt/boulder/log"
"github.com/letsencrypt/boulder/metrics"
"github.com/letsencrypt/boulder/mocks"
"github.com/letsencrypt/boulder/must"
"github.com/letsencrypt/boulder/nonce"
noncepb "github.com/letsencrypt/boulder/nonce/proto"
"github.com/letsencrypt/boulder/probs"
@ -400,11 +401,7 @@ func signAndPost(signer requestSigner, path, signedURL, payload string) *http.Re
}
func mustParseURL(s string) *url.URL {
if u, err := url.Parse(s); err != nil {
panic("Cannot parse URL " + s)
} else {
return u
}
return must.Do(url.Parse(s))
}
func sortHeader(s string) string {