Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions .github/workflows/go-integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Go integration tests

# Language-specific workflow: only runs for the Go client. Triggers on PRs to master that
# touch Go client, test, or example code, and can be dispatched manually from any branch.
on:
pull_request:
branches: [master]
paths:
- '**/*.go'
- 'go.mod'
- 'go.sum'
- '.github/workflows/go-integration-tests.yml'
workflow_dispatch:

# Avoid concurrent runs of the same ref racing on the shared test account.
concurrency:
group: go-integration-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.23'
cache: true

- name: Check formatting
run: |
unformatted=$(gofmt -l .)
if [ -n "$unformatted" ]; then
echo "::error::The following files are not gofmt-formatted:"
echo "$unformatted"
exit 1
fi

- name: Vet
run: go vet ./...

# The idiomatic Go linter. `go vet` only catches a narrow set of correctness issues;
# golangci-lint bundles staticcheck and other analyzers, satisfying the coding-rules
# mandate that linting/type-checking run in CI. Config lives in .golangci.yml.
- name: Lint (golangci-lint)
# Use the v7 action and pin a golangci-lint v2 release: .golangci.yml uses the
# v2 config schema, which the v6 action's v1.x binary rejects ("additional
# properties 'version'/'default' not allowed").
uses: golangci/golangci-lint-action@v7
with:
version: v2.5.0

- name: Build
run: go build ./...

- name: Unit tests
# The unit tests are offline (mock HTTP backend) and prove the retry/error/signature
# logic without hitting the API.
run: go test . -v

# Fail fast if the integration-test secret is missing or empty. Without this guard the
# integration tests silently "pass" (they skip when APIFY_TOKEN is unset), so a green
# run would not prove the API logic actually executed.
- name: Require APIFY_TOKEN secret
env:
APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }}
run: |
if [ -z "${APIFY_TOKEN}" ]; then
echo "::error::APIFY_TOKEN secret is empty or missing; integration tests would not run against the API."
exit 1
fi

- name: Integration tests
env:
# The integration-test token is stored as a repository secret.
APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }}
# Limit parallelism to be gentle on the shared test account.
run: go test ./tests/ -v -timeout 900s -p 1 -parallel 4
44 changes: 18 additions & 26 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Code coverage profiles and other test artifacts
*.out
coverage.*
*.coverprofile
profile.cov
# Folders
_obj
_test

# Dependency directories (remove the comment below to include it)
# vendor/
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

# Go workspace file
go.work
go.work.sum
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

# env file
.env
_testmain.go

# Editor/IDE
# .idea/
# .vscode/
*.exe
*.test
*.prof
22 changes: 22 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# golangci-lint configuration for the Apify Go client (golangci-lint v2 schema).
#
# The coding rules require linting/type-checking to run in CI. We enable the standard,
# high-signal linters (the v2 defaults plus a few extras) rather than every available
# analyzer, to keep the signal-to-noise ratio high.
version: "2"

run:
timeout: 5m
tests: true

linters:
# The "standard" set bundles the high-signal analyzers: errcheck, govet, ineffassign,
# staticcheck (the idiomatic comprehensive Go linter), and unused.
default: standard
enable:
- misspell # common misspellings in comments/strings

issues:
# Show all issues (don't cap per-linter/per-file like the defaults do).
max-issues-per-linter: 0
max-same-issues: 0
106 changes: 106 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Changelog

All notable changes to the Apify Go client are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.0] - 2026-06-18

Initial release of the official Go client for the Apify API, verified against OpenAPI
specification version `v2-2026-06-16T064758Z`.

### Added

- Resource-oriented `ApifyClient` mirroring the official JavaScript and Rust clients, with
accessors for Actors, Actor versions and environment variables, builds, runs, datasets,
key-value stores, request queues, tasks, schedules, webhooks, webhook dispatches, the
Apify Store, users, and logs.
- Replaceable HTTP transport via the `HTTPBackend` interface (default `DefaultHTTPBackend`),
configurable through `WithHTTPBackend`.
- Cross-cutting request behaviour applied to every call: bearer-token authentication, the
mandated `User-Agent` header, exponential-backoff-with-jitter retries (429 and 5xx and
network errors), and a growing-but-capped per-attempt timeout.
- Convenience helpers matching the reference clients: `Actor.Call`/`Task.Call` (start and
wait), `Build.WaitForFinish`/`Run.WaitForFinish`, `Actor.DefaultBuild`,
`Actor.ValidateInput`, `Run.Metamorph`/`Reboot`/`Resurrect`/`Charge`,
run-nested default storages (`Run.Dataset`/`KeyValueStore`/`RequestQueue`/`Log`),
lazy `Store.Iterate` and `RequestQueue.PaginateRequests` iterators,
dataset `DownloadItems`/`GetStatistics`/`CreateItemsPublicURL`,
key-value-store `GetRecords` (ZIP), record public URLs and key-list public URLs with
HMAC-SHA256 signing, request-queue lock lifecycle, and `ApifyClient.SetStatusMessage`.
- Public version constants `CLIENT_VERSION` and `API_SPEC_VERSION`.
- Forward-compatible models that capture unmodelled API fields in an `Extra` map.
- Offline unit tests (mock HTTP backend) covering retries, error parsing, 404→absent
mapping, the User-Agent format, base-URL resolution, and the storage-signature scheme.
- Integration test suite (one simple GET plus one CRUD flow per resource) and runnable,
CI-tested documentation examples.
- A language-specific GitHub Actions workflow that runs gofmt, go vet, build, unit tests and
integration tests, triggered by PRs to master touching Go code or manual dispatch.

### Fixed

- `RunClient.Charge` now always sends an `idempotency-key` header (auto-generated when not
supplied), so a transport-retried charge is applied at most once — matching the reference
client and preventing double-charging.
- `KeyValueStoreClient.GetRecord` now defaults `attachment` to the truthy form, matching the
reference client's record-fetch behaviour. (On the wire this client serialises booleans as
`1`/`0`, so the request carries `attachment=1`; functionally equivalent to the reference
client's `attachment=true`.)
- `WaitForFinish` (used by `Run.WaitForFinish`, `Build.WaitForFinish`, `Actor.Call`,
`Task.Call`) no longer hangs forever on a transient `404` during an indefinite wait
(`waitSecs == nil`). A just-started run/build can briefly return `404` because of
database-replica lag; the wait now polls through `404`s on a pure time bound (defaulting an
indefinite wait to a finite upper bound, mirroring the reference client's
`MAX_WAIT_FOR_FINISH`) and, if the resource never becomes available within the budget,
returns a descriptive error instead of spinning until the context is cancelled.

### Changed

- `RunClient.Charge` now takes a `RunChargeOptions{EventName, Count, IdempotencyKey}` struct.
- `RunClient.Metamorph` now takes a `MetamorphOptions{Build, ContentType}` struct.
- `RunResurrectOptions` gained `MaxItems`, `MaxTotalChargeUsd`, `RestartOnError` (all in the
spec and the reference client).
- `RunClient.GetWithWait` and `BuildClient.GetWithWait` expose the spec's `waitForFinish`
query parameter for a server-side synchronous fetch.
- `RunCollectionClient.List` `Status` filter accepts multiple statuses (`[]string`,
comma-separated), as the API allows.
- `RequestQueueClient.BatchAddRequests` auto-chunks inputs at the API's 25-per-call limit and
returns a typed `BatchAddResult{ProcessedRequests, UnprocessedRequests}`.
- `RequestQueueClient.ListRequests` validates its options (mutually-exclusive
`ExclusiveStartID`/`Cursor`; `Filter` restricted to `locked`/`pending`).
- Tasks use a dedicated `TaskStartOptions` that omits the Actor-only `contentType` and
`forcePermissionLevel` fields (which the task run endpoint does not accept).
- `LogClient` exposes the spec's `raw`/`download` options via `GetWithOptions`/
`StreamWithOptions`, and `RunClient.GetStreamedLog` provides a raw live-stream convenience.
- `RunClient.Abort` now takes `gracefully *bool` instead of `bool`. Passing `nil` omits the
`gracefully` query parameter entirely (letting the server apply its default, immediate
abort), matching the reference client's optional `gracefully` option; pass a pointer to
`true`/`false` to request a graceful/immediate abort explicitly.

### Notes

- Intentionally unimplemented endpoints (a deliberate, parity-driven decision matching the
JavaScript reference and the Rust sibling — not an accidental gap):
- The synchronous run endpoints (`run-sync`, `run-sync-get-dataset-items`).
- The keyed-`POST` record aliases.
- The cryptographic tools `POST /v2/tools/encode-and-sign` and
`POST /v2/tools/decode-and-verify`. These are server-side conveniences for the same
HMAC-SHA256 signing this client already performs locally in `signature.go`; the reference
clients do not expose them, so the Go client omits them for cross-client parity. They can
be added alongside `signature.go` if a future requirement needs them.
- `/v2/browser-info`. Not exposed by the reference clients; omitted for parity.
Only documented endpoints are implemented; the omissions above are the conscious exceptions.
- User-Agent `isAtHome` flag: the canonical reference (JS) reads the `APIFY_IS_AT_HOME`
environment variable, while `client_requirements.md`'s worked example uses the bare name
`isAtHome`. These two same-priority requirements conflict, so the client honours **both**
variable names (either being set marks the client "at home"). The flag is rendered
lowercase (`true`/`false`) to stay byte-consistent with the JS reference; the requirements'
`True`/`False` capitalisation is treated as a cosmetic example, with JS consistency winning.
- `Dataset`/`KeyValueStore` `GetOrCreate` take only a name: this spec version's create
endpoints declare no request body or `schema` parameter, so a schema argument would be an
undocumented extension. The `runs/last` endpoints accept only `status` (no `origin`) in
this spec version. These choices favour strict OpenAPI compliance over the JS superset.
- CI runs `golangci-lint` (default high-signal analyzers — errcheck, govet, ineffassign,
staticcheck, unused — plus `misspell`) in addition to `gofmt`, `go vet`, and `go build`,
satisfying the coding-rule mandate that linting run in CI. Config in `.golangci.yml`.
Loading
Loading