Revert "refactor: deduplicate scope error handling between api/client.go and project queries"

This commit is contained in:
William Martin 2026-03-12 12:45:48 +01:00 committed by GitHub
parent 2bf1669a6b
commit d45acae604
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 66 additions and 4 deletions

View file

@ -203,6 +203,7 @@ func GenerateScopeErrorForGQL(gqlErr *ghAPI.GraphQLError) error {
}
if missing.Len() > 0 {
s := missing.ToSlice()
// TODO: this duplicates parts of generateScopesSuggestion
return fmt.Errorf(
"error: your authentication token is missing required scopes %v\n"+
"To request it, run: gh auth refresh -s %s",

View file

@ -5,11 +5,13 @@ import (
"fmt"
"net/http"
"net/url"
"regexp"
"strings"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/prompter"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/pkg/set"
"github.com/shurcooL/githubv4"
)
@ -1662,15 +1664,42 @@ func (c *Client) UnlinkProjectFromTeam(projectID string, teamID string) error {
}
func handleError(err error) error {
var gqlErr api.GraphQLError
if errors.As(err, &gqlErr) {
if scopeErr := api.GenerateScopeErrorForGQL(gqlErr.GraphQLError); scopeErr != nil {
return scopeErr
var gerr api.GraphQLError
if errors.As(err, &gerr) {
missing := set.NewStringSet()
for _, e := range gerr.Errors {
if e.Type != "INSUFFICIENT_SCOPES" {
continue
}
missing.AddValues(requiredScopesFromServerMessage(e.Message))
}
if missing.Len() > 0 {
s := missing.ToSlice()
// TODO: this duplicates parts of generateScopesSuggestion
return fmt.Errorf(
"error: your authentication token is missing required scopes %v\n"+
"To request it, run: gh auth refresh -s %s",
s,
strings.Join(s, ","))
}
}
return err
}
var scopesRE = regexp.MustCompile(`one of the following scopes: \[(.+?)]`)
func requiredScopesFromServerMessage(msg string) []string {
m := scopesRE.FindStringSubmatch(msg)
if m == nil {
return nil
}
var scopes []string
for _, mm := range strings.Split(m[1], ",") {
scopes = append(scopes, strings.Trim(mm, "' "))
}
return scopes
}
func projectFieldValueData(v FieldValueNodes) interface{} {
switch v.Type {
case "ProjectV2ItemFieldDateValue":

View file

@ -3,6 +3,7 @@ package queries
import (
"io"
"net/http"
"reflect"
"strings"
"testing"
@ -563,6 +564,37 @@ func TestProjectFields_NoLimit(t *testing.T) {
assert.Len(t, project.Fields.Nodes, 3)
}
func Test_requiredScopesFromServerMessage(t *testing.T) {
tests := []struct {
name string
msg string
want []string
}{
{
name: "no scopes",
msg: "SERVER OOPSIE",
want: []string(nil),
},
{
name: "one scope",
msg: "Your token has not been granted the required scopes to execute this query. The 'dataType' field requires one of the following scopes: ['read:project'], but your token has only been granted the: ['codespace', repo'] scopes. Please modify your token's scopes at: https://github.com/settings/tokens.",
want: []string{"read:project"},
},
{
name: "multiple scopes",
msg: "Your token has not been granted the required scopes to execute this query. The 'dataType' field requires one of the following scopes: ['read:project', 'read:discussion', 'codespace'], but your token has only been granted the: [repo'] scopes. Please modify your token's scopes at: https://github.com/settings/tokens.",
want: []string{"read:project", "read:discussion", "codespace"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := requiredScopesFromServerMessage(tt.msg); !reflect.DeepEqual(got, tt.want) {
t.Errorf("requiredScopesFromServerMessage() = %v, want %v", got, tt.want)
}
})
}
}
func TestNewProject_nonTTY(t *testing.T) {
client := NewTestClient()
_, err := client.NewProject(false, &Owner{}, 0, false)