ghapi

package
v0.2.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package ghapi is the typed GitHub REST boundary of the engine.

Every request is assembled from a validated base URL and individually escaped path segments, so an owner, a repository name, or a workflow file name can never become part of the URL's structure. The credential is supplied by an Authorizer and set as one Authorization header on the outgoing request. It never travels in a URL, a query string, or an error, and a redirect that leaves the configured origin is refused rather than followed, because following one would offer the header to a host the engine did not choose.

This package provides the typed HTTP boundary for authenticated GitHub API calls. The token is supplied by an Authorizer and set as one Authorization header on the outgoing request, so the base URL rules, the redirect refusal, the response size bound, and the typed errors all cover every endpoint.

Index

Constants

View Source
const (
	WorkflowActive             = "active"
	WorkflowDisabledManually   = "disabled_manually"
	WorkflowDisabledInactivity = "disabled_inactivity"
	WorkflowDisabledFork       = "disabled_fork"
)

Workflow states GitHub reports for an Actions workflow.

View Source
const DefaultBaseURL = "https://api.github.com"

DefaultBaseURL is the REST API root of github.com.

View Source
const DefaultHTTPTimeout = 30 * time.Second

DefaultHTTPTimeout bounds one REST call when the caller supplies no client.

A client with no timeout waits forever on a destination that accepts the connection and then says nothing, which in a scheduled publishing run is a job that never ends rather than one that fails.

View Source
const DefaultMaxResponseBytes = 4 << 20

DefaultMaxResponseBytes bounds a decoded response body.

Every response this package reads is a small JSON document, and the largest of them is one page of repositories. The bound exists so a destination that answers with an unbounded stream cannot exhaust memory before it is refused.

View Source
const DefaultUserAgent = "soapbox/" + buildinfo.Version

DefaultUserAgent identifies the engine and its exact version to GitHub, which asks every client to send one and answers an anonymous agent with a refusal.

Variables

View Source
var (
	// ErrUnauthorized reports that GitHub rejected the credential itself.
	ErrUnauthorized = errors.New("github rejected the credential")

	// ErrForbidden reports that the credential is valid but lacks the required
	// repository or workflow permission.
	ErrForbidden = errors.New("github refused the request")

	// ErrNotFound reports that GitHub has no visible such resource. A resource
	// hidden from the credential can be answered this way rather than with a
	// refusal, so a caller cannot tell the two apart from the status alone.
	ErrNotFound = errors.New("github reports no such resource")

	// ErrRateLimited reports a primary or secondary rate limit. The error also
	// carries the retry metadata GitHub returned.
	ErrRateLimited = errors.New("github rate limited the request")

	// ErrResponseTooLarge reports a response body past the configured bound.
	ErrResponseTooLarge = errors.New("github response exceeds the response limit")

	// ErrRedirectRefused reports a redirect that left the configured origin.
	ErrRedirectRefused = errors.New("github redirected the request off its origin")

	// ErrTooManyRedirects reports a redirect chain that never settled.
	ErrTooManyRedirects = errors.New("github redirected the request too many times")
)

Refusals a caller can branch on with errors.Is.

Functions

This section is empty.

Types

type Account

type Account struct {
	Login string `json:"login"`
	ID    int64  `json:"id"`
	Type  string `json:"type"`
}

Account is a GitHub user or organization.

type Authorizer

type Authorizer interface {
	AuthorizationHeader(ctx context.Context) (string, error)
}

Authorizer supplies the Authorization header value for one request.

It is an interface rather than a token string so the client remains ignorant of how a credential is sourced and tests can fail a request before transport. The production GITHUB_TOKEN implementation is static for one workflow job.

type AuthorizerFunc

type AuthorizerFunc func(ctx context.Context) (string, error)

AuthorizerFunc adapts a function to Authorizer.

func (AuthorizerFunc) AuthorizationHeader

func (f AuthorizerFunc) AuthorizationHeader(ctx context.Context) (string, error)

AuthorizationHeader calls f.

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is a typed GitHub REST client bound to one origin and one credential.

func New

func New(cfg Config) (*Client, error)

New builds a client from cfg.

func (*Client) BaseURL

func (c *Client) BaseURL() string

BaseURL reports the origin every request is built against.

func (*Client) CreateIssue

func (c *Client) CreateIssue(ctx context.Context, owner, repo string, issue IssueRequest) (Issue, error)

CreateIssue opens an issue.

func (*Client) CreateIssueComment

func (c *Client) CreateIssueComment(ctx context.Context, owner, repo string, number int64, body string) (IssueComment, error)

CreateIssueComment adds a comment to an issue.

func (*Client) DefaultBranch

func (c *Client) DefaultBranch(ctx context.Context, owner, name string) (string, error)

DefaultBranch reads one repository's default branch.

func (*Client) ListOpenIssues

func (c *Client) ListOpenIssues(ctx context.Context, owner, repo string, labels []string) ([]Issue, error)

ListOpenIssues lists the open issues carrying every one of labels, with pull requests dropped.

func (*Client) Repository

func (c *Client) Repository(ctx context.Context, owner, name string) (Repository, error)

Repository reads one repository's metadata, including its default branch.

func (*Client) UpdateIssue

func (c *Client) UpdateIssue(ctx context.Context, owner, repo string, number int64, update IssueUpdate) (Issue, error)

UpdateIssue edits an existing issue.

func (*Client) Workflow

func (c *Client) Workflow(ctx context.Context, owner, repo, file string) (Workflow, error)

Workflow reads one Actions workflow by its file name, such as sync.yml.

type Config

type Config struct {
	// Authorizer supplies the credential presented on every request. It is
	// required because an anonymous client could silently read a public subset
	// instead of the authenticated repository view the caller intends.
	Authorizer Authorizer

	// BaseURL is the REST API root. It defaults to DefaultBaseURL and must be an
	// https URL with no user information, query, or fragment.
	BaseURL string

	// HTTPClient is the transport. It defaults to a client with a request
	// timeout. Whatever is supplied is copied before its redirect policy and
	// cookie jar are replaced, so the caller's client is never mutated.
	HTTPClient *http.Client

	// UserAgent defaults to DefaultUserAgent.
	UserAgent string

	// MaxResponseBytes defaults to DefaultMaxResponseBytes.
	MaxResponseBytes int64

	// Clock reports the current time. It defaults to time.Now and is injected so
	// a Retry-After deadline resolves to an exact delay in tests.
	Clock func() time.Time

	// AllowPlaintextLoopback permits an http base URL that names a loopback
	// address. It exists for httptest servers and nothing else: a plaintext
	// request to any routable host would put the Authorization header on the
	// wire in clear text, so the loopback restriction is enforced rather than
	// documented.
	AllowPlaintextLoopback bool
}

Config describes one GitHub REST client.

type Issue

type Issue struct {
	Number    int64     `json:"number"`
	Title     string    `json:"title"`
	Body      string    `json:"body"`
	State     string    `json:"state"`
	Labels    []Label   `json:"labels"`
	User      Account   `json:"user"`
	HTMLURL   string    `json:"html_url"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`

	// PullRequest is present when this issue is really a pull request. The
	// issues endpoint returns both kinds, and a pull request is never a
	// tracking issue, so listings drop the entries that carry it.
	PullRequest *IssueLink `json:"pull_request"`
}

Issue is the subset of a GitHub issue the engine reads and writes. The engine keeps at most one tracking issue per repository.

type IssueComment

type IssueComment struct {
	ID        int64     `json:"id"`
	Body      string    `json:"body"`
	User      Account   `json:"user"`
	HTMLURL   string    `json:"html_url"`
	CreatedAt time.Time `json:"created_at"`
}

IssueComment is a comment on an issue.

type IssueLink struct {
	HTMLURL string `json:"html_url"`
}

IssueLink is the pull request reference attached to an issue.

type IssueRequest

type IssueRequest struct {
	Title  string   `json:"title"`
	Body   string   `json:"body,omitempty"`
	Labels []string `json:"labels,omitempty"`
}

IssueRequest creates an issue.

type IssueUpdate

type IssueUpdate struct {
	Title  string   `json:"title,omitempty"`
	Body   string   `json:"body,omitempty"`
	State  string   `json:"state,omitempty"`
	Labels []string `json:"labels,omitempty"`
}

IssueUpdate edits an existing issue. An empty field is left as it is.

type Label

type Label struct {
	Name string `json:"name"`
}

Label is an issue label.

type RateLimit

type RateLimit struct {
	// Limit, Remaining, and Used are the primary rate limit counters. They are
	// zero when GitHub did not report them.
	Limit     int
	Remaining int
	Used      int

	// Resource names the budget the request was charged against, such as core
	// or search.
	Resource string

	// Reset is when the primary budget refills. It is zero when unreported.
	Reset time.Time

	// RetryAfter is how long GitHub asked the client to wait. Secondary limits
	// report this and no counters, so it is the only field a caller can rely on
	// for an abuse refusal. It is clamped at zero for a deadline that has
	// already passed, which means retry now rather than never.
	RetryAfter time.Duration

	// RetryAfterSet reports whether GitHub sent a Retry-After header at all.
	// It is separate from RetryAfter because a delay of zero is a real answer:
	// a header naming a moment in the past asks for an immediate retry, and a
	// missing header asks for nothing.
	RetryAfterSet bool
}

RateLimit is the retry metadata GitHub attaches to a throttled response.

type Repository

type Repository struct {
	ID            int64   `json:"id"`
	Name          string  `json:"name"`
	FullName      string  `json:"full_name"`
	Owner         Account `json:"owner"`
	Private       bool    `json:"private"`
	Fork          bool    `json:"fork"`
	Archived      bool    `json:"archived"`
	Disabled      bool    `json:"disabled"`
	DefaultBranch string  `json:"default_branch"`
}

Repository is the subset of a GitHub repository the engine reads.

type StaticBearer added in v0.2.0

type StaticBearer struct {
	// contains filtered or unexported fields
}

StaticBearer is an Authorizer that presents a fixed bearer token.

It is the production credential for GITHUB_TOKEN workflows: the token is minted per-job by Actions and does not expire within the job's lifetime, so it needs no renewal and a static holder is the honest representation.

The token is validated at construction: empty, whitespace-bearing, and control-character-bearing values are refused so a misconfigured caller fails immediately rather than after a network round trip with a malformed header.

func NewStaticBearer added in v0.2.0

func NewStaticBearer(token string) (*StaticBearer, error)

NewStaticBearer builds an authorizer from a raw token value.

The token must not be empty, must not contain control characters (including newlines), and must not contain whitespace. These are the characters that would either break the HTTP header or indicate a configuration error (such as passing the full "Bearer xxx" string instead of just the token).

func (*StaticBearer) AuthorizationHeader added in v0.2.0

func (s *StaticBearer) AuthorizationHeader(_ context.Context) (string, error)

AuthorizationHeader returns the bearer header value.

func (*StaticBearer) GoString added in v0.2.0

func (*StaticBearer) GoString() string

GoString renders the authorizer safely for the %#v format.

func (*StaticBearer) String added in v0.2.0

func (*StaticBearer) String() string

String renders the authorizer without exposing the bearer token.

type StatusError

type StatusError struct {
	// Method and Path locate the request.
	Method string
	Path   string

	// Status is the HTTP status code.
	Status int

	// Message and DocumentationURL are GitHub's own explanation, empty when the
	// body was not an error envelope.
	Message          string
	DocumentationURL string

	// RateLimit is the retry metadata, nil when GitHub reported none.
	RateLimit *RateLimit
	// contains filtered or unexported fields
}

StatusError reports a GitHub response the client refused to treat as success.

It carries the request's method and escaped path rather than its URL, so a caller that logs it cannot write down a query string, and it carries only GitHub's own message and documentation link out of the body. Response bytes never reach an error.

func (*StatusError) Error

func (e *StatusError) Error() string

Error renders the refusal without the response body.

func (*StatusError) Is

func (e *StatusError) Is(target error) bool

Is maps a status onto the package refusals.

func (*StatusError) RetryAfter

func (e *StatusError) RetryAfter() (time.Duration, bool)

RetryAfter reports how long GitHub asked the client to wait, and whether it asked at all. A zero delay with ok true means retry now.

type Workflow

type Workflow struct {
	ID    int64  `json:"id"`
	Name  string `json:"name"`
	Path  string `json:"path"`
	State string `json:"state"`
}

Workflow is an Actions workflow as GitHub reports it.

The scheduled publishing workflow is disabled by GitHub after a period of repository inactivity, and a disabled workflow fails silently by simply never running, so each run checks that it is still active.

func (Workflow) Enabled

func (w Workflow) Enabled() bool

Enabled reports whether the workflow still runs.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL