refactor: remove goto statements

This commit is contained in:
Michael Smith 2025-04-08 19:28:33 +00:00
parent 629f4b3a65
commit 2b9da92259
3 changed files with 125 additions and 52 deletions

View File

@ -124,27 +124,33 @@ func extractFrontmatter(readmeText string) (string, error) {
} }
func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error { func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error {
// This function needs to aggregate a bunch of different errors, rather than // This function needs to aggregate a bunch of different problems, rather
// stopping at the first one found, so using code blocks to section off // than stopping at the first one found, so using code blocks to section off
// logic for different fields // logic for different fields
errors := []error{} problems := []error{}
// Using a bunch of closures to group validations for each field and add
// support for ending validations for a group early. The alternatives were
// making a bunch of functions in the top-level that would only be used
// once, or using goto statements, which would've made refactoring fragile
// GitHub Username // GitHub Username
{ func() {
if yml.GithubUsername == "" { if yml.GithubUsername == "" {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"missing GitHub username for %q", "missing GitHub username for %q",
yml.FilePath, yml.FilePath,
), ),
) )
return
} }
lower := strings.ToLower(yml.GithubUsername) lower := strings.ToLower(yml.GithubUsername)
if uriSafe := url.PathEscape(lower); uriSafe != lower { if uriSafe := url.PathEscape(lower); uriSafe != lower {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"gitHub username %q (%q) is not a valid URL path segment", "gitHub username %q (%q) is not a valid URL path segment",
yml.GithubUsername, yml.GithubUsername,
@ -152,24 +158,29 @@ func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error {
), ),
) )
} }
} }()
// Company GitHub // Company GitHub
if yml.EmployerGithubUsername != nil { func() {
if yml.EmployerGithubUsername == nil {
return
}
if *yml.EmployerGithubUsername == "" { if *yml.EmployerGithubUsername == "" {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"company_github field is defined but has empty value for %q", "company_github field is defined but has empty value for %q",
yml.FilePath, yml.FilePath,
), ),
) )
return
} }
lower := strings.ToLower(*yml.EmployerGithubUsername) lower := strings.ToLower(*yml.EmployerGithubUsername)
if uriSafe := url.PathEscape(lower); uriSafe != lower { if uriSafe := url.PathEscape(lower); uriSafe != lower {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"gitHub company username %q (%q) is not a valid URL path segment", "gitHub company username %q (%q) is not a valid URL path segment",
*yml.EmployerGithubUsername, *yml.EmployerGithubUsername,
@ -179,8 +190,8 @@ func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error {
} }
if *yml.EmployerGithubUsername == yml.GithubUsername { if *yml.EmployerGithubUsername == yml.GithubUsername {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"cannot list own GitHub name (%q) as employer (%q)", "cannot list own GitHub name (%q) as employer (%q)",
yml.GithubUsername, yml.GithubUsername,
@ -188,13 +199,13 @@ func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error {
), ),
) )
} }
} }()
// Display name // Display name
{ func() {
if yml.DisplayName == "" { if yml.DisplayName == "" {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"GitHub user %q (%q) is missing display name", "GitHub user %q (%q) is missing display name",
yml.GithubUsername, yml.GithubUsername,
@ -202,13 +213,17 @@ func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error {
), ),
) )
} }
} }()
// LinkedIn URL // LinkedIn URL
if yml.LinkedinURL != nil { func() {
if yml.LinkedinURL == nil {
return
}
if _, err := url.ParseRequestURI(*yml.LinkedinURL); err != nil { if _, err := url.ParseRequestURI(*yml.LinkedinURL); err != nil {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"linkedIn URL %q (%q) is not valid: %v", "linkedIn URL %q (%q) is not valid: %v",
*yml.LinkedinURL, *yml.LinkedinURL,
@ -217,30 +232,34 @@ func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error {
), ),
) )
} }
} }()
// Email // Email
if yml.SupportEmail != nil { func() {
if yml.SupportEmail == nil {
return
}
// Can't 100% validate that this is correct without actually sending // Can't 100% validate that this is correct without actually sending
// an email, and especially with some contributors being individual // an email, and especially with some contributors being individual
// developers, we don't want to do that on every single run of the CI // developers, we don't want to do that on every single run of the CI
// pipeline. Best we can do is verify the general structure // pipeline. Best we can do is verify the general structure
username, server, ok := strings.Cut(*yml.SupportEmail, "@") username, server, ok := strings.Cut(*yml.SupportEmail, "@")
if !ok { if !ok {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"email address %q (%q) is missing @ symbol", "email address %q (%q) is missing @ symbol",
*yml.LinkedinURL, *yml.LinkedinURL,
yml.FilePath, yml.FilePath,
), ),
) )
goto website return
} }
if username == "" { if username == "" {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"email address %q (%q) is missing username", "email address %q (%q) is missing username",
*yml.LinkedinURL, *yml.LinkedinURL,
@ -251,20 +270,20 @@ func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error {
domain, tld, ok := strings.Cut(server, ".") domain, tld, ok := strings.Cut(server, ".")
if !ok { if !ok {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"email address %q (%q) is missing period for server segment", "email address %q (%q) is missing period for server segment",
*yml.LinkedinURL, *yml.LinkedinURL,
yml.FilePath, yml.FilePath,
), ),
) )
goto website return
} }
if domain == "" { if domain == "" {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"email address %q (%q) is missing domain", "email address %q (%q) is missing domain",
*yml.LinkedinURL, *yml.LinkedinURL,
@ -274,8 +293,8 @@ func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error {
} }
if tld == "" { if tld == "" {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"email address %q (%q) is missing top-level domain", "email address %q (%q) is missing top-level domain",
*yml.LinkedinURL, *yml.LinkedinURL,
@ -283,14 +302,27 @@ func validateContributorYaml(yml contributorFrontmatterWithFilepath) []error {
), ),
) )
} }
}
if strings.Contains(*yml.AvatarUrl, "?") {
problems = append(
problems,
fmt.Errorf(
"email for %q is not allowed to contain search parameters",
yml.FilePath,
),
)
}
}()
// Website // Website
website: func() {
if yml.WebsiteURL != nil { if yml.WebsiteURL == nil {
return
}
if _, err := url.ParseRequestURI(*yml.WebsiteURL); err != nil { if _, err := url.ParseRequestURI(*yml.WebsiteURL); err != nil {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"LinkedIn URL %q (%q) is not valid: %v", "LinkedIn URL %q (%q) is not valid: %v",
*yml.WebsiteURL, *yml.WebsiteURL,
@ -299,14 +331,18 @@ website:
), ),
) )
} }
} }()
// Contributor status // Contributor status
if yml.ContributorStatus != nil { func() {
if yml.ContributorStatus == nil {
return
}
validStatuses := []string{"official", "partner", "community"} validStatuses := []string{"official", "partner", "community"}
if !slices.Contains(validStatuses, *yml.ContributorStatus) { if !slices.Contains(validStatuses, *yml.ContributorStatus) {
errors = append( problems = append(
errors, problems,
fmt.Errorf( fmt.Errorf(
"contributor status %q (%q) is not valid", "contributor status %q (%q) is not valid",
*yml.ContributorStatus, *yml.ContributorStatus,
@ -314,14 +350,51 @@ website:
), ),
) )
} }
} }()
// Avatar URL // Avatar URL - can't validate the image actually leads to a valid resource
if yml.AvatarUrl != nil { // in a pure function, but can at least catch obvious problems
func() {
if yml.AvatarUrl == nil {
return
}
} if *yml.AvatarUrl == "" {
problems = append(
problems,
fmt.Errorf(
"avatar URL for %q must be omitted or non-empty string",
yml.FilePath,
),
)
return
}
return errors // Have to use .Parse instead of .ParseRequestURI because this is the
// one field that's allowed to be a relative URL
if _, err := url.Parse(*yml.AvatarUrl); err != nil {
problems = append(
problems,
fmt.Errorf(
"error %q (%q) is not a valid relative or absolute URL",
*yml.AvatarUrl,
yml.FilePath,
),
)
}
if strings.Contains(*yml.AvatarUrl, "?") {
problems = append(
problems,
fmt.Errorf(
"avatar URL for %q is not allowed to contain search parameters",
yml.FilePath,
),
)
}
}()
return problems
} }
func remapContributorProfile( func remapContributorProfile(