client

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 6, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package client implements the Jira Cloud HTTP transport for gojira.

It handles Basic authentication (email:token base64-encoded), request construction, 429 rate-limit retry with Retry-After / exponential backoff, transient network-error retry, and typed sentinel errors for callers.

The package knows nothing about issues, ADF, links, Markdown, or crawling. Its only project-internal import is internal/config.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnauthorized is returned when the server responds with 401.
	// This is a total-failure condition: credentials are invalid.
	ErrUnauthorized = errors.New("client: unauthorized (401)")

	// ErrForbidden is returned when the server responds with 403.
	// The caller should render a permission-denied stub and continue.
	ErrForbidden = errors.New("client: forbidden (403)")

	// ErrNotFound is returned when the server responds with 404.
	// The caller should render a not-found stub and continue.
	ErrNotFound = errors.New("client: not found (404)")

	// ErrRateLimited is returned when the server responds with 429 and
	// all retry attempts are exhausted.
	ErrRateLimited = errors.New("client: rate limited (429) after retries")
)

Sentinel errors that callers can match with errors.Is.

Functions

This section is empty.

Types

type Client

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

Client is the Jira Cloud HTTP transport. Construct via New. The zero value is not valid.

func New

func New(cfg config.Config, opts ...Option) (*Client, error)

New constructs a Client from cfg and applies any provided options.

It validates cfg.Site as a parseable URL with a non-empty host and pre-computes the Basic auth header. Returns an error if cfg.Site is not a valid URL.

func (*Client) DevStatus

func (c *Client) DevStatus(ctx context.Context, issueNumericID, application, dataType string) (DevStatusResponse, error)

DevStatus fetches the development metadata (pull requests, branches, commits, repositories, builds — depending on dataType) that Jira surfaces in its UI Development panel for the issue identified by its numeric ID.

GET <site>/rest/dev-status/1.0/issue/detail
    ?issueId=<numeric>&applicationType=<application>&dataType=<dataType>

# Important: this endpoint is NOT in the documented Jira Cloud platform REST API

The /rest/dev-status/1.0 path is not part of the platform OpenAPI specification at developer.atlassian.com. It is the same endpoint Atlassian's own Jira UI consumes to populate the Development panel on every issue page; it has been stable for over a decade for that reason. gojira treats it as best-effort enrichment: callers can opt out cleanly by setting GOJIRA_INCLUDE_DEV_STATUS=false (handled at the devstatus enricher and crawl orchestrator layers), in which case this method is never invoked.

429, 401, 403, 404, and transient network errors are handled identically to Client.GetIssue, using the same retry/backoff machinery and propagating the same typed sentinel errors.

issueNumericID must be the numeric issue id (the top-level "id" field of the standard GET /issue/<KEY> response, e.g. "86679"). The endpoint silently returns an empty detail array for issue *keys* — passing the human-readable key by mistake therefore looks like "no entities" rather than an error. application is the upstream applicationType value (e.g. "GitHub", "Bitbucket", "GitLab", "GitHubEnterprise"); the caller is expected to fan out one call per configured application.

dataType selects which entity list the response will populate. Valid values mirror the Jira UI Development panel groupings:

The endpoint accepts any string and silently returns an empty detail for unrecognised values; callers (the devstatus enricher in production) restrict dataType to the configured set.

func (*Client) GetIssue

func (c *Client) GetIssue(ctx context.Context, key string, expand []string) ([]byte, error)

GetIssue fetches the raw JSON body for the Jira issue identified by key from the configured site's REST API v3 endpoint:

GET <site>/rest/api/3/issue/<key>[?expand=<csv>]

On success it returns the raw response bytes. On failure it returns one of the typed sentinel errors (ErrUnauthorized, ErrForbidden, ErrNotFound, ErrRateLimited) or a wrapped error containing the HTTP status code.

429 responses are retried up to maxRateLimitRetries times, honouring the Retry-After header when present. Transient network errors (timeouts and mid-stream io.EOF) are retried up to maxNetworkRetries times. Context cancellation is respected throughout.

expand is the list of Jira expansion tokens to pass via the `expand` query parameter (e.g. []string{"names"} to receive the top-level "names" object mapping every field ID, including customfield_*, to its human-readable label). The values are comma-joined into a single `expand=<a>,<b>,...` query parameter exactly as documented at https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issues/#api-rest-api-3-issue-issueidorkey-get. When expand is empty or nil, no `expand` query parameter is sent and the response shape matches the pre-expansion behaviour.

expand is a parameter rather than a hard-coded default inside the body: callers know what they want from the response, and embedding a "blessed" value here would violate the project's signature-honesty rule (see docs/engineering-principles.md). Internal callers that want human-readable custom-field labels pass []string{"names"} explicitly; callers that want the legacy shape pass nil.

func (*Client) ListFields

func (c *Client) ListFields(ctx context.Context) ([]Field, error)

ListFields fetches the tenant's field metadata from:

GET <site>/rest/api/3/field

It is used by the hierarchy discoverer to auto-detect the Epic Link custom field ID, which varies per tenant. 429 and transient network errors are retried using the same machinery as GetIssue.

func (*Client) Search

func (c *Client) Search(ctx context.Context, jql string, maxResults int) (SearchResult, error)

Search executes a JQL query against the modern Jira Cloud search endpoint:

POST <site>/rest/api/3/search/jql
{"jql": "...", "fields": ["key"], "maxResults": N}

Only the issue keys from the response are returned; the rest of each issue payload is discarded. This is sufficient for hierarchy discovery, which only needs keys to feed back into the crawl queue.

429, 401, 403, 404, and transient network errors are handled identically to GetIssue, using the same retry/backoff machinery.

maxResults caps the maxResults parameter sent in the request body. When maxResults <= 0, the Jira API default is used (currently 50).

The modern endpoint uses cursor-based pagination via nextPageToken. Search currently returns only the first page (up to maxResults results) and surfaces NextPageToken on SearchResult so callers can detect when more results exist. Automatic multi-page aggregation is deferred to post-MVP.

type DevStatusBranch

type DevStatusBranch struct {
	Branch string `json:"branch"`
	URL    string `json:"url"`
}

DevStatusBranch holds a branch reference from a Dev Status PR entry (the source/destination "branch" field of a pull request). It is a distinct type from DevStatusBranchEntry which represents a top- level entry returned by a dataType=branch query.

type DevStatusBranchEntry

type DevStatusBranchEntry struct {
	Name                 string             `json:"name"`
	URL                  string             `json:"url"`
	CreatePullRequestURL string             `json:"createPullRequestUrl"`
	Repository           DevStatusRepoRef   `json:"repository"`
	LastCommit           DevStatusCommitRef `json:"lastCommit"`
}

DevStatusBranchEntry is a single entry returned by a Dev Status query for dataType=branch. The Jira UI surfaces these in the Development panel alongside pull requests and commits; gojira mirrors that grouping in its Markdown output.

type DevStatusBuild

type DevStatusBuild struct {
	ID          string                `json:"id"`
	BuildNumber int                   `json:"buildNumber"`
	Name        string                `json:"name"`
	Description string                `json:"description"`
	URL         string                `json:"url"`
	State       string                `json:"state"`
	LastUpdated string                `json:"lastUpdated"`
	TestSummary *DevStatusTestSummary `json:"testSummary,omitempty"`
	References  []DevStatusBuildRef   `json:"references"`
}

DevStatusBuild is a single entry returned by a Dev Status query for dataType=build. State is the upstream lifecycle string ("SUCCESSFUL", "FAILED", "IN_PROGRESS", "STOPPED", "PENDING", ...); gojira does not normalise it. TestSummary is a pointer so a build that does not report test counts can be distinguished from one that reports zero.

type DevStatusBuildRef

type DevStatusBuildRef struct {
	Name string `json:"name"`
	URI  string `json:"uri"`
}

DevStatusBuildRef is a single git reference (branch or tag) tied to a build. Jira surfaces it so the build can be attributed to the branch its CI ran on.

type DevStatusCommit

type DevStatusCommit struct {
	ID              string           `json:"id"`
	DisplayID       string           `json:"displayId"`
	URL             string           `json:"url"`
	Message         string           `json:"message"`
	Author          DevStatusPerson  `json:"author"`
	AuthorTimestamp string           `json:"authorTimestamp"`
	FileCount       int              `json:"fileCount"`
	Merge           bool             `json:"merge"`
	Repository      DevStatusRepoRef `json:"repository"`
}

DevStatusCommit is a single entry returned by a Dev Status query for dataType=commit. Fields mirror the upstream response exactly; gojira preserves the full SHA in ID and the seven-character abbreviation in DisplayID rather than re-deriving the latter, in case future Jira versions change the abbreviation length.

type DevStatusCommitRef

type DevStatusCommitRef struct {
	ID              string          `json:"id"`
	DisplayID       string          `json:"displayId"`
	URL             string          `json:"url"`
	Message         string          `json:"message"`
	Author          DevStatusPerson `json:"author"`
	AuthorTimestamp string          `json:"authorTimestamp"`
}

DevStatusCommitRef is the last-commit pointer embedded inside a Dev Status branch entry. It mirrors the same fields as DevStatusCommit but without the standalone-entity fields (fileCount, merge, repository) that only appear on a top-level commit query.

type DevStatusInstance

type DevStatusInstance struct {
	Instance     DevStatusInstanceMeta  `json:"_instance"`
	PullRequests []DevStatusPR          `json:"pullRequests"`
	Branches     []DevStatusBranchEntry `json:"branches"`
	Commits      []DevStatusCommit      `json:"commits"`
	Repositories []DevStatusRepository  `json:"repositories"`
	Builds       []DevStatusBuild       `json:"builds"`
}

DevStatusInstance groups the development entities returned by a single development-tool integration (e.g. one connected GitHub app).

Exactly one of the entity lists is non-empty for a given response, depending on the dataType the caller requested. The struct unmarshals all five tolerantly so a single Go type can carry the response for any dataType the Client.DevStatus caller asked for.

type DevStatusInstanceMeta

type DevStatusInstanceMeta struct {
	Type    string `json:"type"`
	Name    string `json:"name"`
	BaseURL string `json:"baseUrl"`
}

DevStatusInstanceMeta describes the development-tool integration that owns a DevStatusInstance's entries.

type DevStatusPR

type DevStatusPR struct {
	ID            string              `json:"id"`
	URL           string              `json:"url"`
	Name          string              `json:"name"`
	Status        string              `json:"status"`
	LastUpdate    string              `json:"lastUpdate"`
	Source        DevStatusBranch     `json:"source"`
	Destination   DevStatusBranch     `json:"destination"`
	Author        DevStatusPerson     `json:"author"`
	Reviewers     []DevStatusReviewer `json:"reviewers"`
	Repository    string              `json:"repositoryName"`
	RepositoryURL string              `json:"repositoryUrl"`
	CommentCount  int                 `json:"commentCount"`
}

DevStatusPR is a single pull-request entry returned by the Dev Status endpoint. Field names match Jira's response shape exactly; semantic translation (e.g. mapping Name → Title) happens in the devstatus package, not here.

type DevStatusPerson

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

DevStatusPerson holds the author of a Dev Status PR/commit/branch entry. The Jira response also carries an "avatar" URL, which gojira does not consume.

type DevStatusRepoRef

type DevStatusRepoRef struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

DevStatusRepoRef is the small repository pointer embedded inside other Dev Status entities (branches, commits). The Jira response nests a {"name", "url"} pair; gojira preserves both so the renderer can label the parent entity with its repository.

type DevStatusRepository

type DevStatusRepository struct {
	Name   string `json:"name"`
	URL    string `json:"url"`
	Avatar string `json:"avatar"`
}

DevStatusRepository is a single entry returned by a Dev Status query for dataType=repository. The Jira UI surfaces these so users can see which repositories have referenced an issue even when no PR or branch carries the issue key directly.

The upstream response also nests truncated commits[] and branches[] arrays inside each repository entry; gojira does not consume them (they are sparser than the top-level dataType=commit/branch queries and would risk duplication if rendered separately).

type DevStatusResponse

type DevStatusResponse struct {
	Errors []json.RawMessage   `json:"errors"`
	Detail []DevStatusInstance `json:"detail"`
}

DevStatusResponse is the parsed shape of a successful Dev Status API response. The endpoint returns a single envelope for any of the five supported dataType values (pullrequest, branch, commit, repository, build); the per-entity list inside each DevStatusInstance differs per dataType. A query for dataType=branch populates Branches and leaves PullRequests/Commits/Repositories/Builds empty, and so on.

Errors is the upstream response's "errors" array. A non-empty Errors slice does not by itself cause Client.DevStatus to return a Go error: the server returns HTTP 200 with embedded soft-error entries (e.g. when one connected GitHub instance is unreachable while another succeeded). Callers decide how to surface these.

The element type is intentionally json.RawMessage rather than a typed struct. The Dev Status endpoint is undocumented and the per-error entry shape is not guaranteed stable: production responses have been observed carrying both string entries (older tenants) and JSON objects with shape `{"code": <int>, "message": <string>, "userId": <string>, ...}` (newer tenants — observed on PLATENG-1417 for dataType=commit and dataType=build). Modelling Errors as a typed slice against a single observed shape would break the unmarshal as soon as the other shape appears. json.RawMessage accepts any valid JSON element and lets the (rare) caller that wants to introspect the entries decode each one lazily. This is the "signature honesty" rule from docs/engineering-principles.md applied to response models: do not pretend to know a shape we have not verified across the entire observed input space.

type DevStatusReviewer

type DevStatusReviewer struct {
	Name     string `json:"name"`
	Approved bool   `json:"approved"`
}

DevStatusReviewer holds a single reviewer entry from a Dev Status PR.

type DevStatusTestSummary

type DevStatusTestSummary struct {
	TotalNumber   int `json:"totalNumber"`
	PassedNumber  int `json:"passedNumber"`
	FailedNumber  int `json:"failedNumber"`
	SkippedNumber int `json:"skippedNumber"`
}

DevStatusTestSummary holds the per-build test counts surfaced by Dev Status for CI integrations that publish them (Bitbucket Pipelines, GitHub Actions via the Atlassian app, etc.). Fields with no value default to zero.

type Field

type Field struct {
	ID     string
	Key    string
	Name   string
	Custom bool
}

Field is the subset of Jira field metadata that gojira needs. It mirrors the relevant fields in the GET /rest/api/3/field response:

[{"id": "...", "key": "...", "name": "...", "custom": true|false, ...}]

Only ID, Key, Name, and Custom are populated; everything else is dropped.

type Option

type Option func(*Client)

Option is a functional option for New.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient replaces the entire http.Client used for requests. Useful in tests that inject an httptest.Server-backed client.

func WithMaxRetries

func WithMaxRetries(n int) Option

WithMaxRetries sets the maximum number of 429 retry attempts.

func WithNetworkBackoff

func WithNetworkBackoff(base, max time.Duration) Option

WithNetworkBackoff overrides the base and max backoff durations for transient network-error retries. Exposed primarily for tests.

func WithRateLimitBackoff

func WithRateLimitBackoff(base, max time.Duration) Option

WithRateLimitBackoff overrides the base and max backoff durations for 429 retries. Exposed primarily for tests that need sub-second backoffs.

func WithRoundTripper

func WithRoundTripper(rt http.RoundTripper) Option

WithRoundTripper replaces only the transport on the default http.Client. Useful when a custom transport is needed without a full http.Client.

type SearchResult

type SearchResult struct {
	Keys          []string
	NextPageToken string
}

SearchResult is the result of a Search call. Keys lists the issue keys returned by the JQL query, in the order the Jira API returned them. NextPageToken is the opaque cursor for the next page; an empty value means there are no more pages. v0.1 does not paginate automatically; callers that need more than maxResults must call Search again with the returned token (post-MVP capability).

Jump to

Keyboard shortcuts

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