~ knotless Search/
↑↓ move↵ open⇧↵ all results
0
0
main
80 lines · 1.7 KB Raw History
package authretry

import (
	"context"
	"errors"
	"net/http"
	"strings"
)

type Invalidator interface {
	Invalidate()
}

type InvalidateFunc func()

func (f InvalidateFunc) Invalidate() { f() }

type StatusError interface {
	error
	HTTPStatus() int
}

func Do[T any](ctx context.Context, src Invalidator, call func(context.Context) (T, error), isAuthErr func(error) bool) (T, error) {
	if isAuthErr == nil {
		isAuthErr = IsAuthErr
	}
	value, err := call(ctx)
	if err == nil || ctx.Err() != nil || !isAuthErr(err) {
		return value, err
	}
	if src != nil {
		src.Invalidate()
	}
	return call(ctx)
}

func Run(ctx context.Context, src Invalidator, call func(context.Context) error, isAuthErr func(error) bool) error {
	_, err := Do(ctx, src, func(ctx context.Context) (struct{}, error) { return struct{}{}, call(ctx) }, isAuthErr)
	return err
}

func Status(err error) int {
	var se StatusError
	if errors.As(err, &se) {
		return se.HTTPStatus()
	}
	return 0
}

func IsAuthErr(err error) bool {
	switch Status(err) {
	case http.StatusUnauthorized:
		return true
	case http.StatusBadRequest, http.StatusForbidden:
		return ExpiredToken(err.Error())
	}
	return false
}

func IsAuthOrForbidden(err error) bool {
	switch Status(err) {
	case http.StatusUnauthorized, http.StatusForbidden:
		return true
	}
	return false
}

func IsAuthStatus(status int) bool {
	return status == http.StatusUnauthorized || status == http.StatusForbidden
}

func ExpiredToken(message string) bool {
	m := strings.ToLower(message)
	for _, needle := range [...]string{"expiredtoken", "invalidtoken", "invalid_token", "token expired", "token has expired", "expired token", "invalid token", "token is expired"} {
		if strings.Contains(m, needle) {
			return true
		}
	}
	return false
}