Skip to content

Add support for FORBIDDEN GraphQL responses and simplify errors. #533

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 6 additions & 11 deletions internal/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,21 +32,13 @@ import (
"github.com/ossf/criticality_score/internal/githubapi"
)

// ErrUncollectableRepo is the base error returned when there is a problem with
// ErrUncollectableRepo is the error returned when there is a problem with
// the repo url passed in to be collected.
//
// For example, the URL may point to an invalid repository host, or the URL
// may point to a repo that is inaccessible or missing.
var ErrUncollectableRepo = errors.New("repo failed")

// ErrRepoNotFound wraps ErrUncollectableRepo and is used when a repo cannot be
// found for collection.
var ErrRepoNotFound = fmt.Errorf("%w: not found", ErrUncollectableRepo)

// ErrUnsupportedURL wraps ErrUncollectableRepo and is used when a repo url
// does not match any of the supported hosts.
var ErrUnsupportedURL = fmt.Errorf("%w: unsupported url", ErrUncollectableRepo)

type Collector struct {
config *config
logger *zap.Logger
Expand Down Expand Up @@ -109,9 +101,12 @@ func (c *Collector) Collect(ctx context.Context, u *url.URL, jobID string) ([]si
if err != nil {
switch {
case errors.Is(err, projectrepo.ErrNoFactoryFound):
return nil, fmt.Errorf("%w: %s", ErrUnsupportedURL, u)
fallthrough
case errors.Is(err, projectrepo.ErrNoRepoFound):
return nil, fmt.Errorf("%w: %s", ErrRepoNotFound, u)
fallthrough
case errors.Is(err, projectrepo.ErrRepoInaccessible):
// TODO: replace %v with %w after upgrading Go from 1.19 to 1.21
return nil, fmt.Errorf("%w (%s): %v", ErrUncollectableRepo, u, err)
default:
return nil, fmt.Errorf("resolving project: %w", err)
}
Expand Down
6 changes: 5 additions & 1 deletion internal/collector/github/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ func (f *factory) New(ctx context.Context, u *url.URL) (projectrepo.Repo, error)
}
if err := r.init(ctx); err != nil {
if errors.Is(err, githubapi.ErrGraphQLNotFound) {
return nil, fmt.Errorf("%w: %s", projectrepo.ErrNoRepoFound, u)
// TODO: replace %v with %w after upgrading Go from 1.19 to 1.21
return nil, fmt.Errorf("%w (%s): %v", projectrepo.ErrNoRepoFound, u, err)
} else if errors.Is(err, githubapi.ErrGraphQLForbidden) {
// TODO: replace %v with %w after upgrading Go from 1.19 to 1.21
return nil, fmt.Errorf("%w (%s): %v", projectrepo.ErrRepoInaccessible, u, err)
} else {
return nil, err
}
Expand Down
6 changes: 5 additions & 1 deletion internal/collector/projectrepo/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ import (

// ErrNoFactoryFound is returned when there is no factory that can be used for
// a given URL.
var ErrNoFactoryFound = errors.New("factory not found")
var ErrNoFactoryFound = errors.New("factory not found for url")

// ErrNoRepoFound is returned when a factory cannot create a Repo for a given
// URL.
var ErrNoRepoFound = errors.New("repo not found")

// ErrRepoInaccessible is returned when a the Repo may exist, but is unable to
// access the repository for some reason.
var ErrRepoInaccessible = errors.New("repo inaccessible")

// Resolver is used to resolve a Repo url against a set of Factory instances
// registered with the resolver.
type Resolver struct {
Expand Down
42 changes: 31 additions & 11 deletions internal/githubapi/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,35 @@ func ErrorResponseStatusCode(err error) int {
return e.Response.StatusCode
}

// ErrGraphQLNotFound is an error used to test when GitHub GraphQL query
// returns a single error with the type "NOT_FOUND".
//
// It should be used with errors.Is.
var ErrGraphQLNotFound = errors.New("GraphQL resource not found")
var (
// ErrGraphQLNotFound is an error used to test when GitHub GraphQL query
// returns a single error with the type "NOT_FOUND".
//
// It should be used with errors.Is.
ErrGraphQLNotFound = errors.New("GraphQL resource not found")

// gitHubGraphQLNotFoundType matches the NOT_FOUND type field returned
// in GitHub's GraphQL errors.
//
// GraphQL errors are required to have a Message, and optional Path and
// Locations. Type is a non-standard field available on GitHub's API.
const gitHubGraphQLNotFoundType = "NOT_FOUND"
// ErrGraphQLForbidden is an error used to test when GitHub GraphQL query
// returns a single error with the type "FORBIDDEN".
//
// It should be used with errors.Is.
ErrGraphQLForbidden = errors.New("GraphQL access forbidden")
)

const (
// gitHubGraphQLNotFoundType matches the NOT_FOUND type field returned
// in GitHub's GraphQL errors.
//
// GraphQL errors are required to have a Message, and optional Path and
// Locations. Type is a non-standard field available on GitHub's API.
gitHubGraphQLNotFoundType = "NOT_FOUND"

// gitHubGraphQLForbiddenType matches the FORBIDDEN type field returned
// in GitHub's GraphQL errors.
//
// This error type is used when the authorization token has been blocked
// from accessing the repository. Usually due to an IP address block.
gitHubGraphQLForbiddenType = "FORBIDDEN"
)

// GraphQLError stores the error result from a GitHub GraphQL query.
type GraphQLError struct {
Expand Down Expand Up @@ -98,5 +115,8 @@ func (e *GraphQLErrors) Is(target error) bool {
if target == ErrGraphQLNotFound {
return len(e.errors) == 1 && e.HasType(gitHubGraphQLNotFoundType)
}
if target == ErrGraphQLForbidden {
return len(e.errors) == 1 && e.HasType(gitHubGraphQLForbiddenType)
}
return false
}
26 changes: 26 additions & 0 deletions internal/githubapi/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,22 @@ func TestGraphQLErrors_Is(t *testing.T) {
target: ErrGraphQLNotFound,
want: false,
},
{
name: "target equals ErrGraphQLForbidden and returns true",
errors: []GraphQLError{
{Message: "one", Type: "FORBIDDEN"},
},
target: ErrGraphQLForbidden,
want: true,
},
{
name: "target equals ErrGraphQLForbidden and returns false",
errors: []GraphQLError{
{Message: "one"},
},
target: ErrGraphQLForbidden,
want: false,
},
{
name: "regular testcase",
errors: []GraphQLError{
Expand All @@ -196,6 +212,16 @@ func TestGraphQLErrors_Is(t *testing.T) {
target: ErrGraphQLNotFound,
want: false,
},
{
name: "multiple errors with only one having the error type as 'FORBIDDEN'",
errors: []GraphQLError{
{Message: "one"},
{Message: "two", Type: "FORBIDDEN"},
{Message: "three"},
},
target: ErrGraphQLForbidden,
want: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
Expand Down