gogcli/internal/errfmt/googleapi.go

116 lines
3.6 KiB
Go

package errfmt
import (
"fmt"
"regexp"
"strings"
ggoogleapi "google.golang.org/api/googleapi"
)
type googleAPIHint struct {
API string
DisplayName string
Service string
}
var googleAPIHints = []googleAPIHint{
{API: "admin.googleapis.com", DisplayName: "Admin SDK API", Service: "admin"},
{API: "appsactivity.googleapis.com", DisplayName: "Drive Activity API", Service: "drive"},
{API: "classroom.googleapis.com", DisplayName: "Classroom API", Service: "classroom"},
{API: "cloudidentity.googleapis.com", DisplayName: "Cloud Identity API", Service: "groups"},
{API: "docs.googleapis.com", DisplayName: "Docs API", Service: "docs"},
{API: "drive.googleapis.com", DisplayName: "Drive API", Service: "drive"},
{API: "forms.googleapis.com", DisplayName: "Forms API", Service: "forms"},
{API: "gmail.googleapis.com", DisplayName: "Gmail API", Service: "gmail"},
{API: "keep.googleapis.com", DisplayName: "Keep API", Service: "keep"},
{API: "people.googleapis.com", DisplayName: "People API", Service: "contacts"},
{API: "script.googleapis.com", DisplayName: "Apps Script API", Service: "appscript"},
{API: "sheets.googleapis.com", DisplayName: "Sheets API", Service: "sheets"},
{API: "slides.googleapis.com", DisplayName: "Slides API", Service: "slides"},
{API: "tasks.googleapis.com", DisplayName: "Tasks API", Service: "tasks"},
}
var apiNamePattern = regexp.MustCompile(`(?i)\b([a-z][a-z0-9-]*\.googleapis\.com)\b`)
func formatGoogleAPIError(gerr *ggoogleapi.Error) string {
reason := googleAPIErrorReason(gerr)
if googleAPIIsDisabled(gerr, reason) {
if hint, ok := googleAPIHintForError(gerr); ok {
return fmt.Sprintf(
"%s is not enabled for this OAuth project.\nEnable it at: %s\nThen retry the command. If you enabled it on a different OAuth client, re-authenticate with: gog auth add <account> --services %s",
hint.DisplayName,
googleAPIEnableURL(hint.API),
hint.Service,
)
}
}
if reason != "" {
return fmt.Sprintf("Google API error (%d %s): %s", gerr.Code, reason, gerr.Message)
}
return fmt.Sprintf("Google API error (%d): %s", gerr.Code, gerr.Message)
}
func googleAPIErrorReason(gerr *ggoogleapi.Error) string {
if gerr == nil || len(gerr.Errors) == 0 {
return ""
}
return strings.TrimSpace(gerr.Errors[0].Reason)
}
func googleAPIIsDisabled(gerr *ggoogleapi.Error, reason string) bool {
if gerr == nil || gerr.Code != 403 {
return false
}
reason = strings.ToLower(strings.TrimSpace(reason))
message := strings.ToLower(gerr.Message)
return reason == "accessnotconfigured" ||
strings.Contains(message, "has not been used") ||
strings.Contains(message, "it is disabled") ||
strings.Contains(message, "api has not been used")
}
func googleAPIHintForError(gerr *ggoogleapi.Error) (googleAPIHint, bool) {
if gerr == nil {
return googleAPIHint{}, false
}
message := strings.ToLower(gerr.Message)
for _, item := range gerr.Errors {
message += " " + strings.ToLower(item.Message)
}
if match := apiNamePattern.FindStringSubmatch(message); len(match) == 2 {
if hint, ok := googleAPIHintForAPI(match[1]); ok {
return hint, true
}
}
for _, hint := range googleAPIHints {
if strings.Contains(message, hint.API) || strings.Contains(message, strings.ToLower(hint.DisplayName)) {
return hint, true
}
}
return googleAPIHint{}, false
}
func googleAPIHintForAPI(api string) (googleAPIHint, bool) {
api = strings.ToLower(strings.TrimSpace(api))
for _, hint := range googleAPIHints {
if hint.API == api {
return hint, true
}
}
return googleAPIHint{}, false
}
func googleAPIEnableURL(api string) string {
return "https://console.developers.google.com/apis/api/" + api + "/overview"
}