Skip to main content

Crate cratefield_adapter_github_issues

Crate cratefield_adapter_github_issues 

Source
Expand description

cratefield-adapter-github-issues: the Tracker port over the GitHub Issues REST API (issue #432). Uses the runtime’s HttpClient and Clock ports — no vendor SDK — so the same adapter runs on Workers and natively (ADR 0002: the port lives in core, the vendor client here).

At-least-once means search-before-create. The outbox may call Tracker::file twice for one TicketDraft, so the adapter stamps every issue body with an invisible HTML comment carrying the draft’s idempotency key and looks for its own stamp before creating. A lookup that fails fails the whole call — creating after an unreliable dedupe check is exactly the double-file the check exists to prevent.

cratefield-adapter-github-issues on crates.io cratefield-adapter-github-issues documentation MIT

§cratefield-adapter-github-issues

The Tracker port over the GitHub Issues REST API for the Cratefield harness (issue #432). It speaks https://api.github.com through the runtime’s HttpClient port — no vendor SDK, no reqwest — so the same adapter runs unchanged on Cloudflare Workers and natively (ADR 0002: the port is core’s, the vendor client is this crate’s).

§Usage

ⓘ
use std::sync::Arc;
use cratefield_adapter_github_issues::GitHubIssues;
use cratefield_core::{Destination, TicketDraft};
use cratefield_runtime_cloudflare::{FetchClient, WorkersClock};

let tracker = GitHubIssues::new(Arc::new(FetchClient), Arc::new(WorkersClock), token);
let filed = tracker
    .file(
        &TicketDraft::new(
            "Outbox: refund failed",
            "The refund webhook failed twice.",
            Destination::GitHubIssues {
                owner: "acme".into(),
                repo: "widgets".into(),
            },
            "01JFILED000000000000000000",
        )
        .labels(["from-outbox"]),
    )
    .await?;

The Clock is a constructor argument rather than a builder default so a deployment that forgets it fails to compile — it is what lets the adapter read the HTTP-date form of Retry-After on a 429 instead of retrying immediately (issue #278). GitHubIssues::with_base points the adapter at GitHub Enterprise Server, or at a fake in tests.

§Search before create

The outbox is at-least-once: Tracker::file will be called twice for one ticket. So the adapter stamps every issue body with an invisible HTML comment —

<!-- cratefield-idem: <idempotency key> -->

— and looks for its own stamp before creating: first GET /search/issues?q=..., then (the search index is eventually consistent, so a fast retry can predate it) the most recent issues page. A hit only counts when the marker really appears in the issue body — search token-matches and must not be trusted blind. On a verified hit the adapter returns Filed { deduplicated: true } and never POSTs.

Fail closed. A lookup that itself fails — a non-2xx or a transport error — fails the whole call instead of falling through to the create. Creating after an unreliable dedupe check is exactly the double-file the check exists to prevent.

§Error mapping

To cratefield_core::TrackerError: 401/403 → Unauthorized, 404 → Rejected (the repository is gone or the token cannot see it — retrying will not fix it), 422 → Rejected, 429/5xx → Transient { retry_after } (Retry-After, both the seconds and the HTTP-date form), any other 4xx → Rejected. No error Display ever includes the token. A draft whose destination is not Destination::GitHubIssues is Rejected before any network call.

Headers on every request: Authorization: Bearer <token>, Accept: application/vnd.github+json, X-GitHub-Api-Version: 2022-11-28, and a User-Agent — GitHub rejects requests without one.


MIT. Built in the open for Cratefield, a Factory Zero venture.

Structs§

GitHubIssues
Tracker over the GitHub Issues REST API.