package gitrpc import ( "context" "fmt" "io" "net/http" "net/url" "time" "connectrpc.com/connect" "simplegit/gitrpc/v1/v1connect" ) type GitRpcClient struct { baseURL string http *http.Client svc v1connect.GitServiceClient } func NewClient(baseURL string) *GitRpcClient { httpClient := &http.Client{Timeout: 30 * time.Second} svc := v1connect.NewGitServiceClient( httpClient, baseURL, connect.WithInterceptors(tokenInterceptor()), ) return &GitRpcClient{ baseURL: baseURL, http: httpClient, svc: svc, } } func (c *GitRpcClient) GitService() v1connect.GitServiceClient { return c.svc } type tokenCtxKey struct{} func WithToken(ctx context.Context, token string) context.Context { return context.WithValue(ctx, tokenCtxKey{}, token) } func tokenFromContext(ctx context.Context) (string, bool) { t, ok := ctx.Value(tokenCtxKey{}).(string) return t, ok } func tokenInterceptor() connect.UnaryInterceptorFunc { return func(next connect.UnaryFunc) connect.UnaryFunc { return func(ctx context.Context, req connect.AnyRequest) (connect.AnyResponse, error) { if token, ok := tokenFromContext(ctx); ok { req.Header().Set("Authorization", "Bearer "+token) } return next(ctx, req) } } } func IsNotFound(err error) bool { return err != nil && connect.CodeOf(err) == connect.CodeNotFound } func (c *GitRpcClient) GetRaw(ctx context.Context, repo, ref, path string) (io.ReadCloser, error) { return c.getBinary(ctx, "/raw", repo, url.Values{"ref": {ref}, "file": {path}}) } func (c *GitRpcClient) GetPatch(ctx context.Context, repo, sha string) (io.ReadCloser, error) { return c.getBinary(ctx, "/patch", repo, url.Values{"sha": {sha}}) } func (c *GitRpcClient) GetArchive(ctx context.Context, repo, ref, format string) (io.ReadCloser, error) { v := url.Values{"ref": {ref}} if format != "" { v.Set("format", format) } return c.getBinary(ctx, "/archive", repo, v) } func (c *GitRpcClient) getBinary(ctx context.Context, endpoint, repo string, extra url.Values) (io.ReadCloser, error) { v := url.Values{"repo": {repo}} for k, vs := range extra { v[k] = vs } req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.baseURL+endpoint+"?"+v.Encode(), nil) if err != nil { return nil, err } if token, ok := tokenFromContext(ctx); ok { req.Header.Set("Authorization", "Bearer "+token) } resp, err := c.http.Do(req) if err != nil { return nil, err } if resp.StatusCode >= 400 { body, _ := io.ReadAll(resp.Body) resp.Body.Close() return nil, fmt.Errorf("gitrpc %s: %s: %s", endpoint, resp.Status, body) } return resp.Body, nil }