// Client to Gitea API package gitea import ( "encoding/json" "errors" "fmt" "io" "net/http" "net/url" "os" "path" "strconv" "strings" "time" "sirherobrine23.com.br/go-bds/request/gitea/structs" "sirherobrine23.com.br/go-bds/request/v3" ) var ( ErrToken error = errors.New("require Token to request") ErrBackend error = errors.New("catch 500 error in server api") ErrNoExist error = errors.New("repository, organization or endpoint dont exists") processCodes = request.CodeProcess{ 500: errRes5xx("500 error"), 501: errRes5xx("501 error"), 401: func(req *request.RequestStatus) (res *http.Response, err error) { if req.Res.Body != nil { req.Res.Body.Close() } return nil, ErrToken }, 403: func(req *request.RequestStatus) (res *http.Response, err error) { if req.Res.Body != nil { req.Res.Body.Close() } return nil, errors.New("cannot process request, authorization or not have permission") }, 429: func(req *request.RequestStatus) (res *http.Response, err error) { if req.Res.Body != nil { res.Body.Close() } // x-ratelimit-reset if window, err := strconv.Atoi(res.Header.Get("x-ratelimit-reset")); err == nil { <-time.After(time.Duration(window)) } else { <-time.After(time.Second) } return request.Request(req.URL, req.Options) }, 413: func(req *request.RequestStatus) (res *http.Response, err error) { d, _ := json.MarshalIndent(req.Res.Header, "", " ") println(string(d)) if strings.Contains(req.Res.Header.Get("Content-Type"), "application/json") { js := json.NewDecoder(req.Res.Body) var all structs.APIError if err = js.Decode(&all); err != nil { return } return req.Res, fmt.Errorf("api Error %s - %s: %w", all.URL, all.Message, req) } io.Copy(os.Stderr, req.Res.Body) return req.Res, req }, } ) type giteaError = structs.APIError // Gitea client type Gitea struct { Host *url.URL // Gitea api host Username, Repository string // Repository and owner names Token string // Gitea token Settings *structs.GeneralAPISettings // API settings } func errRes5xx(txt string) request.ProcessRequest { return func(req *request.RequestStatus) (res *http.Response, err error) { if strings.Contains(req.Res.Header.Get("Content-Type"), "application/json") { var data giteaError defer req.Res.Body.Close() bodyBuff, err := io.ReadAll(req.Res.Body) if err != nil { return nil, fmt.Errorf(txt, ErrBackend, err) } if err := json.Unmarshal(bodyBuff, &data); err != nil { return nil, fmt.Errorf(txt, ErrBackend, ErrNoExist) } else if data.Message != "" { return nil, fmt.Errorf(txt, ErrBackend, fmt.Errorf("backed error: %s", data.Message)) } } return nil, fmt.Errorf(txt, ErrBackend) } } // Return escapated Owner+/+Repository func (client Gitea) repoPath() string { return fmt.Sprintf("%s/%s", url.PathEscape(client.Username), url.PathEscape(client.Repository)) } func (client Gitea) pathJoin(nodes ...string) *url.URL { return client.Host.ResolveReference(&url.URL{Path: path.Join(client.Host.Path, path.Join(nodes...))}) } // Set token to header func (client Gitea) authHeader(header *http.Header) { if client.Token != "" { (*header)["Authorization"] = []string{fmt.Sprintf("token %s", client.Token)} } } // Make new gitea client api rest from env info func DefaultClient() (*Gitea, error) { Owner, Repository := EnvRepository() return NewClient(EnvApiHost(), Owner, Repository, EnvToken()) } // Make new gitea client to Make API requests, host example: "https://gitea.com/api" func NewClient(giteaHost, Owner, Repository, Token string) (*Gitea, error) { apiUrl, err := url.Parse(giteaHost) if err != nil { return nil, err } client := &Gitea{ Host: apiUrl, Username: Owner, Repository: Repository, Token: Token, } appendApi := true retry: // to attemp with api path append // Check if API is OK reqOptions := &request.Options{Method: "GET", Header: http.Header{}, Codes: processCodes} client.authHeader(&reqOptions.Header) res, err := request.Request(apiUrl.ResolveReference(&url.URL{Path: path.Join(apiUrl.Path, "v1/version")}), reqOptions) if err != nil { return nil, fmt.Errorf("cannot get gitea version: %s", err) } else if res.StatusCode != 200 { if appendApi { appendApi = false apiUrl.Path = path.Join(apiUrl.Path, "api") goto retry } return nil, ErrNoExist } res.Body.Close() client.Settings = new(structs.GeneralAPISettings) if _, err = request.DecodeJSON(client.pathJoin("v1/settings/api"), reqOptions, client.Settings); err != nil { client.Settings = nil // ignore api info } return client, nil }