diff --git a/CHANGELOG.md b/CHANGELOG.md index 09d3ff0..78fa200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,52 @@ All notable changes to the Apify Java 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.4.0] - 2026-07-20 + +### Changed (breaking) + +- Reorganized `com.apify.client` into resource-oriented sub-packages: `actor`, `build`, `run`, + `dataset`, `keyvalue`, `requestqueue`, `task`, `schedule`, `webhook`, `user`, `log`, `store`, and + `http` (the replaceable transport). `ApifyClient`, `ApifyClientBuilder`, `ApifyApiException`, + `ApifyTransportException`, `Version`, and the shared list/pagination types (`ListOptions`, + `StorageListOptions`, `PaginationList`) remain in the root `com.apify.client` package. +- Renamed `HttpBackend` to `HttpClient` and `DefaultHttpBackend` to `DefaultApifyHttpClient`, to + avoid confusion with the JDK's own `java.net.http.HttpClient`. +- Renamed `HttpClient.sendStreaming` to `sendStreamingResponse` (it streams the response, not the + request). +- `HttpClientCore.TransportException` is now the top-level, public `ApifyTransportException` + (previously a nested, internal-only type, despite already being thrown across the public API on + an exhausted transport-failure retry budget). +- `ApifyClientBuilder.build()` now validates its configuration (a blank base URL, a negative retry + count, or a negative duration) and throws `IllegalArgumentException`, instead of failing later + with a confusing, indirect error. + +### Changed + +- `DatasetClient`/`KeyValueStoreClient` are now fully immutable after construction: the + public-base-URL override is resolved in the constructor instead of by a post-construction + mutator. +- `HttpClientCore` deserializes the API's error envelope via a typed Jackson DTO instead of manual + `JsonNode` navigation, and parses the request path via `java.net.URI` instead of manual string + slicing. +- Broadened transport-timeout detection (`doNotRetryTimeouts`) to also recognize + `SocketTimeoutException`, not just the default backend's `HttpTimeoutException`, so a custom + `HttpClient` implementation gets the same retry behavior. +- Consolidated every resource's API path segment (`datasets`, `actor-builds`, `webhooks`, ...) into + a single internal `ResourcePaths` class instead of duplicating literals between `ApifyClient` and + each resource client. +- Lowered `DefaultApifyHttpClient`'s connection-establishment timeout from 30s to 10s. +- Bumped the Jackson dependency to 2.19.4. +- The brotli4j native compression codec is no longer bundled by default; gzip remains the automatic + fallback, and brotli can be opted into by adding the platform-appropriate native artifact. +- Added an SLF4J logging facade dependency (no implementation bundled); the client now logs + retry/backoff and give-up events. +- Tightened the "official, but experimental" disclaimer wording (it repeated itself) across the + README, documentation, and Javadoc. +- README: trimmed the quick-start example, clarified what `Version.API_SPEC_VERSION` means, used + `Optional.ifPresent` in the single-resource example, documented the client's synchronous and + thread-safe nature, and added a resource-to-package table. + ## [0.3.1] - 2026-07-14 ### Added diff --git a/README.md b/README.md index 4a08490..2a6ff5f 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # Apify API client for Java -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. A resource-oriented Java client for the [Apify API](https://docs.apify.com/api/v2), mirroring the official [JavaScript](https://github.com/apify/apify-client-js) reference client: start from an @@ -23,7 +22,7 @@ Maven (Maven Central is a default repository, so no extra configuration is neede com.apify apify-client - 0.3.1 + 0.4.0 ``` @@ -35,40 +34,19 @@ repositories { } dependencies { - implementation 'com.apify:apify-client:0.3.1' + implementation 'com.apify:apify-client:0.4.0' } ``` ## Quick start -A complete, copy-pasteable first program (save as `HelloApify.java`). First scaffold a minimal -`pom.xml` next to it so Maven can resolve the client and its runtime dependencies: - -```xml - - 4.0.0 - com.example - hello-apify - 1.0.0 - - 17 - - - - com.apify - apify-client - 0.3.1 - - - -``` - -Create `HelloApify.java`: +A complete, copy-pasteable first program. Add the client as a dependency in your project (see +Installation above), then run this as `HelloApify.java`: ```java import com.apify.client.ApifyClient; -import com.apify.client.ActorRun; -import com.apify.client.ActorStartOptions; +import com.apify.client.actor.ActorStartOptions; +import com.apify.client.run.ActorRun; class HelloApify { public static void main(String[] args) { @@ -80,23 +58,21 @@ class HelloApify { } ``` -Then populate a `lib/` directory with the client and its runtime dependencies, and compile and run -against the JVM's `lib/*` classpath wildcard — quote it so the shell does not expand it: - -```bash -# 1. Collect apify-client and its runtime dependencies (Jackson, brotli4j codecs, …) into lib/. -mvn dependency:copy-dependencies -DoutputDirectory=lib -DincludeScope=runtime - -# 2. Compile and run. '.' is for the compiled HelloApify.class; lib/* is the JVM classpath wildcard. -javac -cp '.:lib/*' HelloApify.java # Windows: javac -cp ".;lib/*" HelloApify.java -java -cp '.:lib/*' HelloApify # Windows: java -cp ".;lib/*" HelloApify -``` - -The remaining snippets below are fragments that assume a configured `client` and these imports: all -public client types live in the `com.apify.client` package (e.g. `import com.apify.client.*;`); the -snippets also use `com.fasterxml.jackson.databind.JsonNode` (from the Jackson dependency) for untyped -data, `java.time.Duration` in the configuration examples, and standard JDK types such as -`java.util.Optional` and `java.util.Map` (`import java.util.*;`). +The client is synchronous (each call blocks until the HTTP response arrives, there is no async or +reactive variant) and, once built via [`ApifyClient.create`](#quick-start) or +[`ApifyClient.builder()`](#configuration), safe for concurrent use from multiple threads: an +`ApifyClient` and the resource clients it returns carry no mutable state after construction. + +The remaining snippets below are fragments that assume a configured `client` and these imports: the +client's types are organized by resource into sub-packages of `com.apify.client` (`actor`, `build`, +`run`, `dataset`, `keyvalue`, `requestqueue`, `task`, `schedule`, `webhook`, `user`, `log`, `store`, +and `http` for the replaceable transport), with `ApifyClient`, `Version`, `ApifyApiException` and the +shared list/pagination types staying in the `com.apify.client` root package — see +[`docs/`](docs/README.md) for the exact package of each type, or import every package with one +wildcard each (`import com.apify.client.*; import com.apify.client.actor.*; …`). The snippets also +use `com.fasterxml.jackson.databind.JsonNode` (from the Jackson dependency) for untyped data, +`java.time.Duration` in the configuration examples, and standard JDK types such as `java.util.Optional` +and `java.util.Map` (`import java.util.*;`). ```java ApifyClient client = ApifyClient.create("my-api-token"); @@ -137,12 +113,14 @@ ApifyClient configured = ### Replaceable HTTP transport -The transport is a replaceable component. The default is `DefaultHttpBackend` (backed by the JDK's -`java.net.http.HttpClient`); provide your own `HttpBackend` to share a connection pool or customize -proxy/TLS: +The transport is a replaceable component, defined by the `com.apify.client.http.HttpClient` +interface (distinct from the JDK's own `java.net.http.HttpClient`, which the default implementation +happens to use under the hood — always refer to the JDK one by its fully-qualified name to avoid +ambiguity, as the snippet below does). The default is `DefaultApifyHttpClient`; provide your own +`HttpClient` to share a connection pool or customize proxy/TLS: ```java -HttpBackend backend = new DefaultHttpBackend(java.net.http.HttpClient.newHttpClient()); +HttpClient backend = new DefaultApifyHttpClient(java.net.http.HttpClient.newHttpClient()); ApifyClient withBackend = ApifyClient.builder().token("t").httpBackend(backend).build(); ``` @@ -150,16 +128,31 @@ Cross-cutting behaviour applied to every request lives in the client, not the ba bearer-token authentication, the mandated `User-Agent` header, and retries with exponential backoff and jitter on `429`, `5xx` and network errors. +### Logging + +The client logs retry/backoff and give-up events through [SLF4J](https://www.slf4j.org/) (a facade +only — no logging implementation is bundled). Add an SLF4J binding of your choice (e.g. Logback) to +your own project's dependencies to see these logs; with no binding present, SLF4J silently discards +them, so this is safe to leave unconfigured. + +### Request-body compression + +Request bodies of 1024 bytes or more are compressed before sending, preferring +[brotli](https://github.com/hyperxpro/Brotli4j) (`Content-Encoding: br`) and falling back to gzip. +The brotli native codec is **not** bundled by default (it is platform-specific, and forcing every +consumer to pull down every OS/architecture's native binary is wasteful) — without it the client +transparently uses gzip, which is fully functional on its own. To opt into brotli, add both +`com.aayushatharva.brotli4j:brotli4j` and your platform's `com.aayushatharva.brotli4j:native--` +artifact (matching the brotli4j version this client compiles against — see `pom.xml`) as +dependencies of your own project. + ## Fetching single resources Methods that fetch a single resource return an `Optional`: a missing resource is reported by an empty `Optional` rather than an exception. ```java -Optional maybeActor = client.actor("apify/hello-world").get(); -if (maybeActor.isPresent()) { - System.out.println(maybeActor.get().getTitle()); -} +client.actor("apify/hello-world").get().ifPresent(actor -> System.out.println(actor.getTitle())); ``` ## Error handling @@ -189,12 +182,16 @@ try { The public `com.apify.client.Version` class (`import com.apify.client.Version;`) exposes two constants: -- `Version.CLIENT_VERSION` — the semantic version of this client (`0.3.1`). -- `Version.API_SPEC_VERSION` — the Apify OpenAPI specification version this client was verified - against (`v2-2026-07-13T092445Z`). +- `Version.CLIENT_VERSION` — the semantic version of this client (`0.4.0`). +- `Version.API_SPEC_VERSION` — the version of the [Apify OpenAPI specification](https://docs.apify.com/api/openapi.json) + (its `info.version`, e.g. `v2-2026-07-13T092445Z`) that this release of the client was last + checked and updated against. It is a point-in-time reference for maintainers, not a compatibility + guarantee: the client also works against other spec versions, since the Apify API is additive and + backwards-compatible in practice. Changes to the public interface other than additive ones are considered breaking changes and follow -[Semantic Versioning](https://semver.org/). +[Semantic Versioning](https://semver.org/). See [`CHANGELOG.md`](CHANGELOG.md) for the list of +changes in each release, including breaking ones (e.g. `0.4.0`'s package reorganization). ### Releasing @@ -234,21 +231,25 @@ Full documentation is in the [`docs/`](docs/README.md) directory, organized by r ## Resources -| Accessor | Client | Description | -|---|---|---| -| `actors()` / `actor(id)` | `ActorCollectionClient` / `ActorClient` | Actors | -| `builds()` / `build(id)` | `BuildCollectionClient` / `BuildClient` | Actor builds | -| `runs()` / `run(id)` | `RunCollectionClient` / `RunClient` | Actor runs | -| `datasets()` / `dataset(id)` | `DatasetCollectionClient` / `DatasetClient` | Datasets | -| `keyValueStores()` / `keyValueStore(id)` | `KeyValueStoreCollectionClient` / `KeyValueStoreClient` | Key-value stores | -| `requestQueues()` / `requestQueue(id)` | `RequestQueueCollectionClient` / `RequestQueueClient` | Request queues | -| `tasks()` / `task(id)` | `TaskCollectionClient` / `TaskClient` | Actor tasks | -| `schedules()` / `schedule(id)` | `ScheduleCollectionClient` / `ScheduleClient` | Schedules | -| `webhooks()` / `webhook(id)` | `WebhookCollectionClient` / `WebhookClient` | Webhooks | -| `webhookDispatches()` / `webhookDispatch(id)` | `WebhookDispatchCollectionClient` / `WebhookDispatchClient` | Webhook dispatches | -| `store()` | `StoreCollectionClient` | Apify Store | -| `me()` / `user(id)` | `UserClient` | Users | -| `log(id)` | `LogClient` | Build/run logs | +Each resource's classes live in their own sub-package of `com.apify.client` (see the Package +column); `ApifyClient` itself, and the shared list/pagination types, stay in the root +`com.apify.client` package. + +| Accessor | Client | Package | Description | +|---|---|---|---| +| `actors()` / `actor(id)` | `ActorCollectionClient` / `ActorClient` | `com.apify.client.actor` | Actors | +| `builds()` / `build(id)` | `BuildCollectionClient` / `BuildClient` | `com.apify.client.build` | Actor builds | +| `runs()` / `run(id)` | `RunCollectionClient` / `RunClient` | `com.apify.client.run` | Actor runs | +| `datasets()` / `dataset(id)` | `DatasetCollectionClient` / `DatasetClient` | `com.apify.client.dataset` | Datasets | +| `keyValueStores()` / `keyValueStore(id)` | `KeyValueStoreCollectionClient` / `KeyValueStoreClient` | `com.apify.client.keyvalue` | Key-value stores | +| `requestQueues()` / `requestQueue(id)` | `RequestQueueCollectionClient` / `RequestQueueClient` | `com.apify.client.requestqueue` | Request queues | +| `tasks()` / `task(id)` | `TaskCollectionClient` / `TaskClient` | `com.apify.client.task` | Actor tasks | +| `schedules()` / `schedule(id)` | `ScheduleCollectionClient` / `ScheduleClient` | `com.apify.client.schedule` | Schedules | +| `webhooks()` / `webhook(id)` | `WebhookCollectionClient` / `WebhookClient` | `com.apify.client.webhook` | Webhooks | +| `webhookDispatches()` / `webhookDispatch(id)` | `WebhookDispatchCollectionClient` / `WebhookDispatchClient` | `com.apify.client.webhook` | Webhook dispatches | +| `store()` | `StoreCollectionClient` | `com.apify.client.store` | Apify Store | +| `me()` / `user(id)` | `UserClient` | `com.apify.client.user` | Users | +| `log(id)` | `LogClient` | `com.apify.client.log` | Build/run logs | ## License diff --git a/docs/README.md b/docs/README.md index f2459be..385d118 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,8 +1,7 @@ # Apify Java client documentation -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. This directory documents the public API of the Apify Java client, organized by resource. Each page lists the available methods with their parameters and short snippets. The snippets are code @@ -31,10 +30,18 @@ empty `Optional` rather than an exception. API failures are thrown as `ApifyApiE ## Imports and dependencies -Snippets in these docs assume the client types are imported from `com.apify.client` (e.g. -`import com.apify.client.*;`) plus standard-library types (`java.util.List`, `java.util.ArrayList`, -`java.util.Map`, `java.util.Optional`, `java.util.Iterator`, `java.util.function.Consumer`, -`java.time.Duration`, `java.io.InputStream`). +The client's classes are organized by resource into sub-packages of `com.apify.client`: `actor`, +`build`, `run`, `dataset`, `keyvalue`, `requestqueue`, `task`, `schedule`, `webhook`, `user`, `log`, +`store`, and `http` (the replaceable transport). `ApifyClient`, `ApifyClientBuilder`, +`ApifyApiException`, `Version`, and the shared list/pagination types (`ListOptions`, +`StorageListOptions`, `PaginationList`) stay in the `com.apify.client` root package. See each +resource page for its exact package, or import every sub-package's classes with one wildcard +per package (e.g. `import com.apify.client.*; import com.apify.client.actor.*; import +com.apify.client.dataset.*; …`). + +Snippets in these docs additionally assume standard-library types (`java.util.List`, +`java.util.ArrayList`, `java.util.Map`, `java.util.Optional`, `java.util.Iterator`, +`java.util.function.Consumer`, `java.time.Duration`, `java.io.InputStream`). Raw-JSON return values use Jackson's `com.fasterxml.jackson.databind.JsonNode`. Jackson is a transitive dependency of this client, so it is already on your classpath. diff --git a/docs/actors.md b/docs/actors.md index 0c640c4..5cfdb1e 100644 --- a/docs/actors.md +++ b/docs/actors.md @@ -1,8 +1,7 @@ # Actors, versions & environment variables -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. Access the Actor collection with `client.actors()` and a single Actor with `client.actor(id)`, where `id` is an Actor ID or `username~name` (a `/` in the id is accepted and normalized). diff --git a/docs/builds.md b/docs/builds.md index 8854024..4d8aafd 100644 --- a/docs/builds.md +++ b/docs/builds.md @@ -1,8 +1,7 @@ # Builds -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. Access the build collection with `client.builds()` (or `client.actor(id).builds()` for an Actor's builds) and a single build with `client.build(id)`. diff --git a/docs/examples.md b/docs/examples.md index 4743610..5a7284c 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -1,8 +1,7 @@ # Examples -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. Each example below is a code fragment (not a standalone `main`) that assumes a configured `client` and the imports listed in the [documentation index](README.md#imports-and-dependencies). The diff --git a/docs/misc.md b/docs/misc.md index dc5bf13..8ed40df 100644 --- a/docs/misc.md +++ b/docs/misc.md @@ -1,8 +1,7 @@ # Store, users & logs -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. ## Apify Store — `client.store()` diff --git a/docs/runs.md b/docs/runs.md index e1c03a3..32a7925 100644 --- a/docs/runs.md +++ b/docs/runs.md @@ -1,8 +1,7 @@ # Runs -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. Access the run collection with `client.runs()` (or `client.actor(id).runs()` / `client.task(id).runs()`) and a single run with `client.run(id)`. diff --git a/docs/schedules.md b/docs/schedules.md index d820b6d..4de8b53 100644 --- a/docs/schedules.md +++ b/docs/schedules.md @@ -1,8 +1,7 @@ # Schedules -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. Schedules automatically start Actor or task runs at specified times. Access the collection with `client.schedules()` and a single schedule with `client.schedule(id)`. diff --git a/docs/storages.md b/docs/storages.md index d118ba3..54faeae 100644 --- a/docs/storages.md +++ b/docs/storages.md @@ -1,8 +1,7 @@ # Storages: datasets, key-value stores, request queues -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. The three storage types share a consistent shape: a collection client (`list`, `getOrCreate`) and a single-resource client (`get`, `update`, `delete`, plus storage-specific operations). Run-nested diff --git a/docs/tasks.md b/docs/tasks.md index 9fe6f19..70064e0 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -1,8 +1,7 @@ # Tasks -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. Tasks are pre-configured Actor runs with stored input. Access the task collection with `client.tasks()` and a single task with `client.task(id)`. diff --git a/docs/webhooks.md b/docs/webhooks.md index 44236e3..ff0ba96 100644 --- a/docs/webhooks.md +++ b/docs/webhooks.md @@ -1,8 +1,7 @@ # Webhooks & dispatches -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify client, -> but it is experimental: it is generated and maintained by AI. Review the code before relying on it -> in production and report issues on the repository. +> **Official, but experimental — AI-generated and AI-maintained.** Review the code before relying +> on it in production and report issues on the repository. Webhooks notify an external service when specific events occur. Access the collection with `client.webhooks()` and a single webhook with `client.webhook(id)`. Dispatches (individual diff --git a/pom.xml b/pom.xml index 7d62df7..ca74362 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.apify apify-client - 0.3.1 + 0.4.0 jar Apify Java Client @@ -39,8 +39,9 @@ 17 UTF-8 - 2.17.2 + 2.19.4 5.10.2 + 2.0.16 1.23.0 @@ -56,44 +57,41 @@ ${jackson.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + falls back to gzip when no native codec is available for the running platform. Only the + core Java API is a direct (and `optional`, so it is not forced on consumers transitively) + dependency; the platform-specific native codec is deliberately NOT bundled by default — + every consumer of this library would otherwise be forced to pull down five separate + per-OS/arch native binaries whether or not they want brotli. Add brotli4j's + platform-appropriate `native--` artifact yourself to opt in; without it, the + client transparently falls back to the JDK's built-in gzip codec (see + HttpClientCore.detectBrotli()), so compression is fully functional either way. --> com.aayushatharva.brotli4j brotli4j ${brotli4j.version} + true + + com.aayushatharva.brotli4j native-linux-x86_64 ${brotli4j.version} - runtime - - - com.aayushatharva.brotli4j - native-linux-aarch64 - ${brotli4j.version} - runtime - - - com.aayushatharva.brotli4j - native-osx-x86_64 - ${brotli4j.version} - runtime - - - com.aayushatharva.brotli4j - native-osx-aarch64 - ${brotli4j.version} - runtime - - - com.aayushatharva.brotli4j - native-windows-x86_64 - ${brotli4j.version} - runtime + test diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 252e702..1b956b0 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -1,26 +1,54 @@ - + - + + + + + + + + + + + + + + + + + diff --git a/src/main/java/com/apify/client/ApifyApiException.java b/src/main/java/com/apify/client/ApifyApiException.java index ced3cbd..1045672 100644 --- a/src/main/java/com/apify/client/ApifyApiException.java +++ b/src/main/java/com/apify/client/ApifyApiException.java @@ -1,6 +1,7 @@ package com.apify.client; import java.util.Collections; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -25,7 +26,7 @@ public class ApifyApiException extends RuntimeException { private final String path; private final transient Map data; - ApifyApiException( + public ApifyApiException( int statusCode, String type, String message, @@ -39,7 +40,10 @@ public class ApifyApiException extends RuntimeException { this.attempt = attempt; this.httpMethod = httpMethod; this.path = path; - this.data = data; + // Defensive copy: the constructor is now public (required for the internal HTTP layer, in a + // different package since the package split, to construct this exception), so a caller-held + // reference to the original map must not be able to mutate this exception's state afterwards. + this.data = data == null ? null : new LinkedHashMap<>(data); } /** The HTTP status code of the error response. */ diff --git a/src/main/java/com/apify/client/ApifyClient.java b/src/main/java/com/apify/client/ApifyClient.java index bc59f35..a2e7a8f 100644 --- a/src/main/java/com/apify/client/ApifyClient.java +++ b/src/main/java/com/apify/client/ApifyClient.java @@ -1,13 +1,40 @@ package com.apify.client; +import com.apify.client.actor.ActorClient; +import com.apify.client.actor.ActorCollectionClient; +import com.apify.client.build.BuildClient; +import com.apify.client.build.BuildCollectionClient; +import com.apify.client.dataset.DatasetClient; +import com.apify.client.dataset.DatasetCollectionClient; +import com.apify.client.http.DefaultApifyHttpClient; +import com.apify.client.http.HttpClient; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.ResourcePaths; +import com.apify.client.keyvalue.KeyValueStoreClient; +import com.apify.client.keyvalue.KeyValueStoreCollectionClient; +import com.apify.client.log.LogClient; +import com.apify.client.requestqueue.RequestQueueClient; +import com.apify.client.requestqueue.RequestQueueCollectionClient; +import com.apify.client.run.ActorRun; +import com.apify.client.run.RunClient; +import com.apify.client.run.RunCollectionClient; +import com.apify.client.schedule.ScheduleClient; +import com.apify.client.schedule.ScheduleCollectionClient; +import com.apify.client.store.StoreCollectionClient; +import com.apify.client.task.TaskClient; +import com.apify.client.task.TaskCollectionClient; +import com.apify.client.user.UserClient; +import com.apify.client.webhook.WebhookClient; +import com.apify.client.webhook.WebhookCollectionClient; +import com.apify.client.webhook.WebhookDispatchClient; +import com.apify.client.webhook.WebhookDispatchCollectionClient; import java.util.LinkedHashMap; import java.util.Map; /** * The entry point for interacting with the Apify API. * - *

Official, but experimental — AI-generated and AI-maintained. This is an official Apify - * client, but it is experimental: it is generated and maintained by AI. Review the code before + *

Official, but experimental — AI-generated and AI-maintained. Review the code before * relying on it in production and report issues on the repository. * *

Construct it with {@link #create(String)} (token-only) or {@link #builder()}, then obtain @@ -27,8 +54,8 @@ * *

    *
  • Public interface: {@link ApifyClient} and the resource clients it returns. - *
  • Replaceable transport: the {@link HttpBackend} interface, with a default {@link - * DefaultHttpBackend}; swap it via {@link ApifyClientBuilder#httpBackend(HttpBackend)}. + *
  • Replaceable transport: the {@link HttpClient} interface, with a default {@link + * DefaultApifyHttpClient}; swap it via {@link ApifyClientBuilder#httpBackend(HttpClient)}. *
  • Cross-cutting behaviour (auth, User-Agent, retries with exponential backoff, timeouts) * lives in the internal HTTP client and is applied to every request. *
@@ -58,14 +85,21 @@ public static ApifyClientBuilder builder() { return new ApifyClientBuilder(); } - /** Returns the {@code User-Agent} header value this client sends. */ + /** + * Returns the {@code User-Agent} header value this client sends on every API call. Exposed for + * introspection/debugging (e.g. logging it alongside a request, or reusing the same value on an + * adjacent raw HTTP call for consistent observability) — the client itself does not need callers + * to read this back. + */ public String getUserAgent() { return http.userAgent(); } /** * Returns the fully-qualified API base URL this client targets (including the {@code /v2} - * suffix). + * suffix). Exposed for introspection/debugging (e.g. confirming which environment a configured + * client points at, or building an adjacent raw HTTP call against the same base URL) — the client + * itself does not need callers to read this back. */ public String getApiBaseUrl() { return baseUrl; @@ -87,7 +121,7 @@ public ActorClient actor(String id) { /** A client for the Actor build collection (list builds). */ public BuildCollectionClient builds() { - return new BuildCollectionClient(http, baseUrl, "actor-builds"); + return new BuildCollectionClient(http, baseUrl, ResourcePaths.ACTOR_BUILDS); } /** A client for a specific Actor build. */ @@ -99,12 +133,12 @@ public BuildClient build(String id) { /** A client for the Actor run collection (list runs). */ public RunCollectionClient runs() { - return new RunCollectionClient(http, baseUrl, "actor-runs"); + return new RunCollectionClient(http, baseUrl, ResourcePaths.ACTOR_RUNS); } /** A client for a specific Actor run. */ public RunClient run(String id) { - return new RunClient(this, http, baseUrl, "actor-runs", id); + return new RunClient(this, http, baseUrl, ResourcePaths.ACTOR_RUNS, id); } // ----- Dataset accessors --------------------------------------------------- @@ -116,7 +150,7 @@ public DatasetCollectionClient datasets() { /** A client for a specific dataset, addressed by ID or name. */ public DatasetClient dataset(String id) { - return new DatasetClient(http, baseUrl, "datasets", id).withPublicBase(publicBaseUrl); + return new DatasetClient(http, baseUrl, ResourcePaths.DATASETS, id, publicBaseUrl); } // ----- Key-value store accessors ------------------------------------------- @@ -128,8 +162,8 @@ public KeyValueStoreCollectionClient keyValueStores() { /** A client for a specific key-value store, addressed by ID or name. */ public KeyValueStoreClient keyValueStore(String id) { - return new KeyValueStoreClient(http, baseUrl, "key-value-stores", id) - .withPublicBase(publicBaseUrl); + return new KeyValueStoreClient( + http, baseUrl, ResourcePaths.KEY_VALUE_STORES, id, publicBaseUrl); } // ----- Request queue accessors --------------------------------------------- @@ -141,7 +175,7 @@ public RequestQueueCollectionClient requestQueues() { /** A client for a specific request queue, addressed by ID or name. */ public RequestQueueClient requestQueue(String id) { - return new RequestQueueClient(http, baseUrl, "request-queues", id); + return new RequestQueueClient(http, baseUrl, ResourcePaths.REQUEST_QUEUES, id); } // ----- Task accessors ------------------------------------------------------ @@ -182,7 +216,7 @@ public WebhookClient webhook(String id) { /** A client for the webhook dispatch collection. */ public WebhookDispatchCollectionClient webhookDispatches() { - return new WebhookDispatchCollectionClient(http, baseUrl, "webhook-dispatches"); + return new WebhookDispatchCollectionClient(http, baseUrl, ResourcePaths.WEBHOOK_DISPATCHES); } /** A client for a specific webhook dispatch. */ @@ -199,7 +233,7 @@ public StoreCollectionClient store() { /** A client for accessing a build's or run's log. */ public LogClient log(String buildOrRunId) { - return new LogClient(http, baseUrl, "logs", buildOrRunId); + return new LogClient(http, baseUrl, ResourcePaths.LOGS, buildOrRunId); } /** A client for the current user ({@code /users/me}). */ @@ -216,9 +250,13 @@ public UserClient user(String id) { * Sets the status message of the current Actor run. * *

This convenience method updates the run identified by the {@code ACTOR_RUN_ID} environment - * variable, so it only works when called from inside an Actor run. If {@code isTerminal} is true, - * the message becomes final and won't be overwritten. Throws {@link IllegalStateException} if - * {@code ACTOR_RUN_ID} is not set. + * variable, so it only works when called from inside an Actor run. This mirrors the reference + * JavaScript client's equivalent helper, which reads the same platform-injected variable — code + * running as an Actor already has {@code ACTOR_RUN_ID} in its environment, so there is nothing + * else meaningful to pass here (the alternative, {@link #run(String)}{@code .update(...)}, is + * always available for updating a run by an explicit, arbitrary ID). If {@code isTerminal} is + * true, the message becomes final and won't be overwritten. Throws {@link IllegalStateException} + * if {@code ACTOR_RUN_ID} is not set. */ public ActorRun setStatusMessage(String message, boolean isTerminal) { String runId = System.getenv("ACTOR_RUN_ID"); diff --git a/src/main/java/com/apify/client/ApifyClientBuilder.java b/src/main/java/com/apify/client/ApifyClientBuilder.java index ef4a377..e9c9ea5 100644 --- a/src/main/java/com/apify/client/ApifyClientBuilder.java +++ b/src/main/java/com/apify/client/ApifyClientBuilder.java @@ -1,5 +1,9 @@ package com.apify.client; +import com.apify.client.http.DefaultApifyHttpClient; +import com.apify.client.http.HttpClient; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.RetryConfig; import java.time.Duration; import java.util.function.BooleanSupplier; @@ -14,6 +18,16 @@ public final class ApifyClientBuilder { static final int DEFAULT_MAX_RETRIES = 8; static final Duration DEFAULT_MIN_DELAY = Duration.ofMillis(500); + + /** + * Default per-attempt request timeout, deliberately generous (matching the reference JS client): + * it bounds a single HTTP round-trip, which for this client can mean uploading/downloading a + * large dataset or key-value-store payload, or a long-polling {@code waitForFinish} call — not + * just a quick metadata fetch. Lower it via {@link #timeout(Duration)} for latency-sensitive + * calls; {@link ApifyClient} does not offer separate per-call-shape timeouts, since every call + * already shares this one configurable ceiling and the client has no way to know in advance which + * calls in a given application are "small" versus "large". + */ static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(360); /** Environment variable that signals the client is running on the Apify platform. */ @@ -27,7 +41,7 @@ public final class ApifyClientBuilder { private Duration maxDelayBetweenRetries = DEFAULT_TIMEOUT; private Duration timeout = DEFAULT_TIMEOUT; private String userAgentSuffix; - private HttpBackend httpBackend; + private HttpClient httpBackend; private BooleanSupplier isAtHomeFn = ApifyClientBuilder::defaultIsAtHome; ApifyClientBuilder() {} @@ -85,7 +99,7 @@ public ApifyClientBuilder userAgentSuffix(String userAgentSuffix) { } /** Replaces the default HTTP backend with a custom implementation (the replaceable transport). */ - public ApifyClientBuilder httpBackend(HttpBackend httpBackend) { + public ApifyClientBuilder httpBackend(HttpClient httpBackend) { this.httpBackend = httpBackend; return this; } @@ -96,9 +110,16 @@ ApifyClientBuilder isAtHomeFn(BooleanSupplier isAtHomeFn) { return this; } - /** Builds the configured {@link ApifyClient}. */ + /** + * Builds the configured {@link ApifyClient}. + * + * @throws IllegalArgumentException if a configured value is invalid (a blank base URL, a negative + * retry count, or a negative duration) — surfaced here, at configuration time, rather than as + * a confusing failure on the client's first API call. + */ public ApifyClient build() { - HttpBackend backend = httpBackend != null ? httpBackend : new DefaultHttpBackend(); + validate(); + HttpClient backend = httpBackend != null ? httpBackend : new DefaultApifyHttpClient(); String userAgent = buildUserAgent(userAgentSuffix, isAtHomeFn); RetryConfig retry = new RetryConfig(maxRetries, minDelayBetweenRetries, maxDelayBetweenRetries, timeout); @@ -110,12 +131,39 @@ public ApifyClient build() { return new ApifyClient(http, apiBase, publicBase); } + /** + * Fails fast on configuration mistakes that would otherwise surface as a confusing failure much + * later (e.g. a malformed request URL, or a retry loop that never sleeps). Does not validate + * {@code token}: the API itself is the authority on whether a token is required/valid (some + * proxies or Actor-local setups legitimately run without one). + */ + private void validate() { + if (baseUrl == null || baseUrl.isBlank()) { + throw new IllegalArgumentException("baseUrl must not be null or blank"); + } + if (publicBaseUrl != null && publicBaseUrl.isBlank()) { + throw new IllegalArgumentException("publicBaseUrl must not be blank when set"); + } + if (maxRetries < 0) { + throw new IllegalArgumentException("maxRetries must not be negative: " + maxRetries); + } + requireNonNegative(minDelayBetweenRetries, "minDelayBetweenRetries"); + requireNonNegative(maxDelayBetweenRetries, "maxDelayBetweenRetries"); + requireNonNegative(timeout, "timeout"); + } + + private static void requireNonNegative(Duration duration, String name) { + if (duration == null || duration.isNegative()) { + throw new IllegalArgumentException(name + " must not be null or negative: " + duration); + } + } + + /** Strips every trailing {@code /} from {@code s} (e.g. so {@code /v2} is never doubled up). */ private static String trimTrailingSlash(String s) { - int end = s.length(); - while (end > 0 && s.charAt(end - 1) == '/') { - end--; + while (s.endsWith("/")) { + s = s.substring(0, s.length() - 1); } - return s.substring(0, end); + return s; } /** diff --git a/src/main/java/com/apify/client/ApifyTransportException.java b/src/main/java/com/apify/client/ApifyTransportException.java new file mode 100644 index 0000000..1c2b6f4 --- /dev/null +++ b/src/main/java/com/apify/client/ApifyTransportException.java @@ -0,0 +1,23 @@ +package com.apify.client; + +/** + * Thrown when a request fails at the transport level — connection refused, DNS failure, a request + * timeout — before any HTTP response is received from the Apify API. + * + *

This is distinct from {@link ApifyApiException}, which is thrown when the API does + * respond, but with a non-success status. Transport failures are retried the same way as retryable + * HTTP statuses (see the client's retry/timeout configuration), and this exception is thrown only + * once the retry budget is exhausted (or immediately, for a timeout, when the caller has opted out + * of retrying timeouts). + * + *

It is an unchecked exception, consistent with {@link ApifyApiException}, so callers are not + * forced to wrap every call. + */ +public class ApifyTransportException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public ApifyTransportException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/apify/client/ListOptions.java b/src/main/java/com/apify/client/ListOptions.java index 6e952c1..74d185a 100644 --- a/src/main/java/com/apify/client/ListOptions.java +++ b/src/main/java/com/apify/client/ListOptions.java @@ -1,5 +1,7 @@ package com.apify.client; +import com.apify.client.internal.QueryParams; + /** * The standard offset/limit pagination shared by most {@code list} endpoints (builds, runs, tasks, * schedules, webhooks, Actor versions). All fields are optional; leave one unset to use the API @@ -33,15 +35,15 @@ public ListOptions desc(Boolean desc) { return this; } - Long offsetValue() { + public Long offsetValue() { return offset; } - Long limitValue() { + public Long limitValue() { return limit; } - void apply(QueryParams q) { + public void apply(QueryParams q) { q.addLong("offset", offset).addLong("limit", limit); applyFilters(q); } @@ -49,7 +51,7 @@ void apply(QueryParams q) { /** * Applies every filter except {@code offset}/{@code limit}, which the iterator drives per page. */ - void applyFilters(QueryParams q) { + public void applyFilters(QueryParams q) { q.addBool("desc", desc); } } diff --git a/src/main/java/com/apify/client/PaginationList.java b/src/main/java/com/apify/client/PaginationList.java index 01f3a1b..d3997a1 100644 --- a/src/main/java/com/apify/client/PaginationList.java +++ b/src/main/java/com/apify/client/PaginationList.java @@ -55,28 +55,42 @@ public List getItems() { return Collections.unmodifiableList(items); } - // Package-private setters used by the dataset-items path, which builds pages from headers. - void setTotal(long total) { + // These setters are not intended for application use — the API always returns fully-populated + // pages. They are public only because com.apify.client.dataset.DatasetClient (a different + // package, post package-split) builds a page from response headers for the dataset-items + // endpoint, which reports pagination metadata via headers rather than a JSON envelope. + + /** Not for application use; see the class-level note above. */ + public void setTotal(long total) { this.total = total; } - void setOffset(long offset) { + /** Not for application use; see the class-level note above. */ + public void setOffset(long offset) { this.offset = offset; } - void setLimit(long limit) { + /** Not for application use; see the class-level note above. */ + public void setLimit(long limit) { this.limit = limit; } - void setCount(long count) { + /** Not for application use; see the class-level note above. */ + public void setCount(long count) { this.count = count; } - void setDesc(boolean desc) { + /** Not for application use; see the class-level note above. */ + public void setDesc(boolean desc) { this.desc = desc; } - void setItems(List items) { - this.items = items; + /** + * Not for application use; see the class-level note above. Defensively copies {@code items} so a + * caller-held reference to the original list cannot mutate this page afterwards (the constructor + * is public only for the same cross-package-wiring reason as the other setters here). + */ + public void setItems(List items) { + this.items = items == null ? List.of() : List.copyOf(items); } } diff --git a/src/main/java/com/apify/client/StorageListOptions.java b/src/main/java/com/apify/client/StorageListOptions.java index f9b9e6e..09112e8 100644 --- a/src/main/java/com/apify/client/StorageListOptions.java +++ b/src/main/java/com/apify/client/StorageListOptions.java @@ -1,5 +1,7 @@ package com.apify.client; +import com.apify.client.internal.QueryParams; + /** * Options for the storage collection list endpoints ({@code GET /v2/datasets}, {@code * /v2/key-value-stores}, {@code /v2/request-queues}), which add {@code unnamed} and {@code @@ -46,15 +48,15 @@ public StorageListOptions ownership(String ownership) { return this; } - Long offsetValue() { + public Long offsetValue() { return offset; } - Long limitValue() { + public Long limitValue() { return limit; } - void apply(QueryParams q) { + public void apply(QueryParams q) { q.addLong("offset", offset).addLong("limit", limit); applyFilters(q); } @@ -62,7 +64,7 @@ void apply(QueryParams q) { /** * Applies every filter except {@code offset}/{@code limit}, which the iterator drives per page. */ - void applyFilters(QueryParams q) { + public void applyFilters(QueryParams q) { q.addBool("desc", desc).addBool("unnamed", unnamed).addString("ownership", ownership); } } diff --git a/src/main/java/com/apify/client/Version.java b/src/main/java/com/apify/client/Version.java index e4d344f..7008a23 100644 --- a/src/main/java/com/apify/client/Version.java +++ b/src/main/java/com/apify/client/Version.java @@ -13,7 +13,7 @@ public final class Version { * The semantic version of this client library (see SemVer). * Changes to the public interface other than additive ones are considered breaking changes. */ - public static final String CLIENT_VERSION = "0.3.1"; + public static final String CLIENT_VERSION = "0.4.0"; /** * The version of the Apify OpenAPI specification this client was generated and verified against. diff --git a/src/main/java/com/apify/client/WebhookDispatchClient.java b/src/main/java/com/apify/client/WebhookDispatchClient.java deleted file mode 100644 index 7234a21..0000000 --- a/src/main/java/com/apify/client/WebhookDispatchClient.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.apify.client; - -import java.util.Optional; - -/** A client for a specific webhook dispatch ({@code /v2/webhook-dispatches/{dispatchId}}). */ -public final class WebhookDispatchClient { - private final ResourceContext ctx; - - WebhookDispatchClient(HttpClientCore http, String baseUrl, String id) { - this.ctx = ResourceContext.single(http, baseUrl, "webhook-dispatches", id); - } - - /** Fetches the dispatch, or empty if it does not exist. */ - public Optional get() { - return ctx.getResource("", new QueryParams(), WebhookDispatch.class); - } -} diff --git a/src/main/java/com/apify/client/Actor.java b/src/main/java/com/apify/client/actor/Actor.java similarity index 94% rename from src/main/java/com/apify/client/Actor.java rename to src/main/java/com/apify/client/actor/Actor.java index 135fa92..23bb4db 100644 --- a/src/main/java/com/apify/client/Actor.java +++ b/src/main/java/com/apify/client/actor/Actor.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.actor; +import com.apify.client.ApifyResource; import java.time.Instant; /** An Actor on the Apify platform. */ diff --git a/src/main/java/com/apify/client/ActorBuildOptions.java b/src/main/java/com/apify/client/actor/ActorBuildOptions.java similarity index 93% rename from src/main/java/com/apify/client/ActorBuildOptions.java rename to src/main/java/com/apify/client/actor/ActorBuildOptions.java index 990ee5a..1121425 100644 --- a/src/main/java/com/apify/client/ActorBuildOptions.java +++ b/src/main/java/com/apify/client/actor/ActorBuildOptions.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.actor; + +import com.apify.client.internal.QueryParams; /** Configures {@link ActorClient#build(String, ActorBuildOptions)}. */ public final class ActorBuildOptions { diff --git a/src/main/java/com/apify/client/ActorClient.java b/src/main/java/com/apify/client/actor/ActorClient.java similarity index 87% rename from src/main/java/com/apify/client/ActorClient.java rename to src/main/java/com/apify/client/actor/ActorClient.java index ad05ddd..e1a2b5c 100644 --- a/src/main/java/com/apify/client/ActorClient.java +++ b/src/main/java/com/apify/client/actor/ActorClient.java @@ -1,5 +1,19 @@ -package com.apify.client; - +package com.apify.client.actor; + +import com.apify.client.ApifyClient; +import com.apify.client.build.Build; +import com.apify.client.build.BuildClient; +import com.apify.client.build.BuildCollectionClient; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.Json; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; +import com.apify.client.run.ActorRun; +import com.apify.client.run.LastRunOptions; +import com.apify.client.run.RunClient; +import com.apify.client.run.RunCollectionClient; +import com.apify.client.webhook.NestedWebhookCollectionClient; import com.fasterxml.jackson.databind.JsonNode; import java.util.Optional; @@ -16,10 +30,10 @@ public final class ActorClient { private final String baseUrl; private final String id; - ActorClient(ApifyClient root, HttpClientCore http, String baseUrl, String id) { + public ActorClient(ApifyClient root, HttpClientCore http, String baseUrl, String id) { this.root = root; this.http = http; - this.ctx = ResourceContext.single(http, baseUrl, "actors", id); + this.ctx = ResourceContext.single(http, baseUrl, ResourcePaths.ACTORS, id); this.baseUrl = baseUrl; this.id = id; } diff --git a/src/main/java/com/apify/client/ActorCollectionClient.java b/src/main/java/com/apify/client/actor/ActorCollectionClient.java similarity index 76% rename from src/main/java/com/apify/client/ActorCollectionClient.java rename to src/main/java/com/apify/client/actor/ActorCollectionClient.java index 799e2f2..7ddfb3c 100644 --- a/src/main/java/com/apify/client/ActorCollectionClient.java +++ b/src/main/java/com/apify/client/actor/ActorCollectionClient.java @@ -1,13 +1,18 @@ -package com.apify.client; +package com.apify.client.actor; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.util.Iterator; /** A client for the Actor collection ({@code GET/POST /v2/actors}). */ public final class ActorCollectionClient { private final ResourceContext ctx; - ActorCollectionClient(HttpClientCore http, String baseUrl) { - this.ctx = ResourceContext.collection(http, baseUrl, "actors"); + public ActorCollectionClient(HttpClientCore http, String baseUrl) { + this.ctx = ResourceContext.collection(http, baseUrl, ResourcePaths.ACTORS); } /** Lists the account's Actors. */ diff --git a/src/main/java/com/apify/client/ActorEnvVar.java b/src/main/java/com/apify/client/actor/ActorEnvVar.java similarity index 92% rename from src/main/java/com/apify/client/ActorEnvVar.java rename to src/main/java/com/apify/client/actor/ActorEnvVar.java index 7a0f99f..69ce243 100644 --- a/src/main/java/com/apify/client/ActorEnvVar.java +++ b/src/main/java/com/apify/client/actor/ActorEnvVar.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.actor; + +import com.apify.client.ApifyResource; /** An environment variable attached to an Actor version. */ public final class ActorEnvVar extends ApifyResource { diff --git a/src/main/java/com/apify/client/ActorEnvVarClient.java b/src/main/java/com/apify/client/actor/ActorEnvVarClient.java similarity index 83% rename from src/main/java/com/apify/client/ActorEnvVarClient.java rename to src/main/java/com/apify/client/actor/ActorEnvVarClient.java index 7e6760c..745661d 100644 --- a/src/main/java/com/apify/client/ActorEnvVarClient.java +++ b/src/main/java/com/apify/client/actor/ActorEnvVarClient.java @@ -1,5 +1,8 @@ -package com.apify.client; +package com.apify.client.actor; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import java.util.Optional; /** diff --git a/src/main/java/com/apify/client/ActorEnvVarCollectionClient.java b/src/main/java/com/apify/client/actor/ActorEnvVarCollectionClient.java similarity index 84% rename from src/main/java/com/apify/client/ActorEnvVarCollectionClient.java rename to src/main/java/com/apify/client/actor/ActorEnvVarCollectionClient.java index 4c31dea..871cf19 100644 --- a/src/main/java/com/apify/client/ActorEnvVarCollectionClient.java +++ b/src/main/java/com/apify/client/actor/ActorEnvVarCollectionClient.java @@ -1,5 +1,9 @@ -package com.apify.client; +package com.apify.client.actor; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import java.util.Iterator; /** diff --git a/src/main/java/com/apify/client/ActorListOptions.java b/src/main/java/com/apify/client/actor/ActorListOptions.java similarity index 95% rename from src/main/java/com/apify/client/ActorListOptions.java rename to src/main/java/com/apify/client/actor/ActorListOptions.java index cdf9c57..86b0e38 100644 --- a/src/main/java/com/apify/client/ActorListOptions.java +++ b/src/main/java/com/apify/client/actor/ActorListOptions.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.actor; + +import com.apify.client.internal.QueryParams; /** Options for {@link ActorCollectionClient#list(ActorListOptions)}. */ public final class ActorListOptions { diff --git a/src/main/java/com/apify/client/ActorStartOptions.java b/src/main/java/com/apify/client/actor/ActorStartOptions.java similarity index 94% rename from src/main/java/com/apify/client/ActorStartOptions.java rename to src/main/java/com/apify/client/actor/ActorStartOptions.java index 3d5c111..0692f42 100644 --- a/src/main/java/com/apify/client/ActorStartOptions.java +++ b/src/main/java/com/apify/client/actor/ActorStartOptions.java @@ -1,5 +1,8 @@ -package com.apify.client; +package com.apify.client.actor; +import com.apify.client.internal.Json; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import java.util.Base64; import java.util.List; @@ -108,7 +111,7 @@ void apply(QueryParams q) { * parameter requires. Returns {@code null} for a {@code null} list. Shared by Actor and task * start options (DRY). */ - static String encodeWebhooks(List webhooks) { + public static String encodeWebhooks(List webhooks) { if (webhooks == null) { return null; } diff --git a/src/main/java/com/apify/client/ActorVersion.java b/src/main/java/com/apify/client/actor/ActorVersion.java similarity index 85% rename from src/main/java/com/apify/client/ActorVersion.java rename to src/main/java/com/apify/client/actor/ActorVersion.java index 53f088f..e114b4e 100644 --- a/src/main/java/com/apify/client/ActorVersion.java +++ b/src/main/java/com/apify/client/actor/ActorVersion.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.actor; + +import com.apify.client.ApifyResource; /** A single version of an Actor. */ public final class ActorVersion extends ApifyResource { diff --git a/src/main/java/com/apify/client/ActorVersionClient.java b/src/main/java/com/apify/client/actor/ActorVersionClient.java similarity index 88% rename from src/main/java/com/apify/client/ActorVersionClient.java rename to src/main/java/com/apify/client/actor/ActorVersionClient.java index 9f9c86d..0d0c60b 100644 --- a/src/main/java/com/apify/client/ActorVersionClient.java +++ b/src/main/java/com/apify/client/actor/ActorVersionClient.java @@ -1,5 +1,8 @@ -package com.apify.client; +package com.apify.client.actor; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import java.util.Optional; /** diff --git a/src/main/java/com/apify/client/ActorVersionCollectionClient.java b/src/main/java/com/apify/client/actor/ActorVersionCollectionClient.java similarity index 89% rename from src/main/java/com/apify/client/ActorVersionCollectionClient.java rename to src/main/java/com/apify/client/actor/ActorVersionCollectionClient.java index 5c4b936..962c4ab 100644 --- a/src/main/java/com/apify/client/ActorVersionCollectionClient.java +++ b/src/main/java/com/apify/client/actor/ActorVersionCollectionClient.java @@ -1,5 +1,10 @@ -package com.apify.client; +package com.apify.client.actor; +import com.apify.client.ListOptions; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import java.util.Iterator; import java.util.List; diff --git a/src/main/java/com/apify/client/ValidateInputOptions.java b/src/main/java/com/apify/client/actor/ValidateInputOptions.java similarity index 86% rename from src/main/java/com/apify/client/ValidateInputOptions.java rename to src/main/java/com/apify/client/actor/ValidateInputOptions.java index f0ead2a..54af781 100644 --- a/src/main/java/com/apify/client/ValidateInputOptions.java +++ b/src/main/java/com/apify/client/actor/ValidateInputOptions.java @@ -1,4 +1,7 @@ -package com.apify.client; +package com.apify.client.actor; + +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; /** * Configures {@link ActorClient#validateInput(Object, ValidateInputOptions)}. All fields are diff --git a/src/main/java/com/apify/client/Build.java b/src/main/java/com/apify/client/build/Build.java similarity index 91% rename from src/main/java/com/apify/client/Build.java rename to src/main/java/com/apify/client/build/Build.java index ebbc867..484ff24 100644 --- a/src/main/java/com/apify/client/Build.java +++ b/src/main/java/com/apify/client/build/Build.java @@ -1,5 +1,7 @@ -package com.apify.client; +package com.apify.client.build; +import com.apify.client.ApifyResource; +import com.apify.client.internal.Statuses; import java.time.Instant; /** A single build of an Actor. */ diff --git a/src/main/java/com/apify/client/BuildClient.java b/src/main/java/com/apify/client/build/BuildClient.java similarity index 81% rename from src/main/java/com/apify/client/BuildClient.java rename to src/main/java/com/apify/client/build/BuildClient.java index 77d7ce9..a7df3eb 100644 --- a/src/main/java/com/apify/client/BuildClient.java +++ b/src/main/java/com/apify/client/build/BuildClient.java @@ -1,5 +1,12 @@ -package com.apify.client; +package com.apify.client.build; +import com.apify.client.internal.ApiResponse; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.Json; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; +import com.apify.client.log.LogClient; import com.fasterxml.jackson.databind.JsonNode; import java.util.Optional; @@ -8,9 +15,9 @@ public final class BuildClient { private final HttpClientCore http; private final ResourceContext ctx; - BuildClient(HttpClientCore http, String baseUrl, String id) { + public BuildClient(HttpClientCore http, String baseUrl, String id) { this.http = http; - this.ctx = ResourceContext.single(http, baseUrl, "actor-builds", id); + this.ctx = ResourceContext.single(http, baseUrl, ResourcePaths.ACTOR_BUILDS, id); } /** Fetches the build object, or empty if it does not exist. */ diff --git a/src/main/java/com/apify/client/BuildCollectionClient.java b/src/main/java/com/apify/client/build/BuildCollectionClient.java similarity index 78% rename from src/main/java/com/apify/client/BuildCollectionClient.java rename to src/main/java/com/apify/client/build/BuildCollectionClient.java index 91d7656..96b164c 100644 --- a/src/main/java/com/apify/client/BuildCollectionClient.java +++ b/src/main/java/com/apify/client/build/BuildCollectionClient.java @@ -1,5 +1,10 @@ -package com.apify.client; +package com.apify.client.build; +import com.apify.client.ListOptions; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import java.util.Iterator; /** @@ -9,7 +14,7 @@ public final class BuildCollectionClient { private final ResourceContext ctx; - BuildCollectionClient(HttpClientCore http, String baseUrl, String resourcePath) { + public BuildCollectionClient(HttpClientCore http, String baseUrl, String resourcePath) { this.ctx = ResourceContext.collection(http, baseUrl, resourcePath); } diff --git a/src/main/java/com/apify/client/Dataset.java b/src/main/java/com/apify/client/dataset/Dataset.java similarity index 92% rename from src/main/java/com/apify/client/Dataset.java rename to src/main/java/com/apify/client/dataset/Dataset.java index 7efb24d..06c941d 100644 --- a/src/main/java/com/apify/client/Dataset.java +++ b/src/main/java/com/apify/client/dataset/Dataset.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.dataset; +import com.apify.client.ApifyResource; import java.time.Instant; /** A dataset stores structured results from Actor runs. */ diff --git a/src/main/java/com/apify/client/DatasetClient.java b/src/main/java/com/apify/client/dataset/DatasetClient.java similarity index 85% rename from src/main/java/com/apify/client/DatasetClient.java rename to src/main/java/com/apify/client/dataset/DatasetClient.java index 00f2132..1fb9612 100644 --- a/src/main/java/com/apify/client/DatasetClient.java +++ b/src/main/java/com/apify/client/dataset/DatasetClient.java @@ -1,5 +1,13 @@ -package com.apify.client; - +package com.apify.client.dataset; + +import com.apify.client.PaginationList; +import com.apify.client.internal.ApiResponse; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.Json; +import com.apify.client.internal.PaginatedIterator; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.Signatures; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; import java.util.Iterator; @@ -9,35 +17,44 @@ /** A client for a specific dataset (and run-nested variants). */ public final class DatasetClient { private final HttpClientCore http; - private ResourceContext ctx; + private final ResourceContext ctx; - DatasetClient(HttpClientCore http, String baseUrl, String resourcePath, String id) { + public DatasetClient(HttpClientCore http, String baseUrl, String resourcePath, String id) { this.http = http; this.ctx = ResourceContext.single(http, baseUrl, resourcePath, id); } + /** + * As {@link #DatasetClient(HttpClientCore, String, String, String)}, additionally overriding the + * origin used for public, shareable URLs. Used by {@code ApifyClient.dataset(String)} for + * top-level datasets, which support a public-base-URL override; run-nested datasets (via {@link + * #nested}) never do. Resolving the override here, before the field assignment, keeps {@code ctx} + * (and so this client) immutable from construction on rather than mutated after the fact. + */ + public DatasetClient( + HttpClientCore http, String baseUrl, String resourcePath, String id, String publicBaseUrl) { + this.http = http; + ResourceContext base = ResourceContext.single(http, baseUrl, resourcePath, id); + this.ctx = publicBaseUrl != null ? base.withPublicOrigin(publicBaseUrl) : base; + } + private DatasetClient(HttpClientCore http, ResourceContext ctx) { this.http = http; this.ctx = ctx; } /** Creates a dataset client for a run's default dataset (nested path only, no ID). */ - static DatasetClient nested(HttpClientCore http, String base, String subPath) { + public static DatasetClient nested(HttpClientCore http, String base, String subPath) { return nested(http, base, subPath, null); } /** As {@link #nested(HttpClientCore, String, String)} but inheriting parent query params. */ - static DatasetClient nested( + public static DatasetClient nested( HttpClientCore http, String base, String subPath, QueryParams inherited) { return new DatasetClient( http, ResourceContext.collection(http, base, subPath).seedParams(inherited)); } - DatasetClient withPublicBase(String publicBaseUrl) { - this.ctx = ctx.withPublicOrigin(publicBaseUrl); - return this; - } - /** Fetches the dataset metadata, or empty if it does not exist. */ public Optional get() { return ctx.getResource("", new QueryParams(), Dataset.class); @@ -215,7 +232,7 @@ private static long headerLong(ApiResponse resp, String name, long fallback) { } /** Reads a string field from an extra map, returning {@code null} if absent or not a string. */ - static String extractString(java.util.Map extra, String key) { + public static String extractString(java.util.Map extra, String key) { Object v = extra.get(key); return (v instanceof String) ? (String) v : null; } diff --git a/src/main/java/com/apify/client/DatasetCollectionClient.java b/src/main/java/com/apify/client/dataset/DatasetCollectionClient.java similarity index 80% rename from src/main/java/com/apify/client/DatasetCollectionClient.java rename to src/main/java/com/apify/client/dataset/DatasetCollectionClient.java index bdd4aaa..cadfa87 100644 --- a/src/main/java/com/apify/client/DatasetCollectionClient.java +++ b/src/main/java/com/apify/client/dataset/DatasetCollectionClient.java @@ -1,13 +1,19 @@ -package com.apify.client; - +package com.apify.client.dataset; + +import com.apify.client.PaginationList; +import com.apify.client.StorageListOptions; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.util.Iterator; /** A client for the dataset collection ({@code GET/POST /v2/datasets}). */ public final class DatasetCollectionClient { private final ResourceContext ctx; - DatasetCollectionClient(HttpClientCore http, String baseUrl) { - this.ctx = ResourceContext.collection(http, baseUrl, "datasets"); + public DatasetCollectionClient(HttpClientCore http, String baseUrl) { + this.ctx = ResourceContext.collection(http, baseUrl, ResourcePaths.DATASETS); } /** Lists datasets. */ diff --git a/src/main/java/com/apify/client/DatasetDownloadOptions.java b/src/main/java/com/apify/client/dataset/DatasetDownloadOptions.java similarity index 96% rename from src/main/java/com/apify/client/DatasetDownloadOptions.java rename to src/main/java/com/apify/client/dataset/DatasetDownloadOptions.java index 529ae6f..a4c0512 100644 --- a/src/main/java/com/apify/client/DatasetDownloadOptions.java +++ b/src/main/java/com/apify/client/dataset/DatasetDownloadOptions.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.dataset; + +import com.apify.client.internal.QueryParams; /** * Adds format-specific options for {@link DatasetClient#downloadItems} on top of the shared item diff --git a/src/main/java/com/apify/client/DatasetListItemsOptions.java b/src/main/java/com/apify/client/dataset/DatasetListItemsOptions.java similarity index 98% rename from src/main/java/com/apify/client/DatasetListItemsOptions.java rename to src/main/java/com/apify/client/dataset/DatasetListItemsOptions.java index 94bc0ab..6960086 100644 --- a/src/main/java/com/apify/client/DatasetListItemsOptions.java +++ b/src/main/java/com/apify/client/dataset/DatasetListItemsOptions.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.dataset; +import com.apify.client.internal.QueryParams; import java.util.List; /** diff --git a/src/main/java/com/apify/client/DownloadItemsFormat.java b/src/main/java/com/apify/client/dataset/DownloadItemsFormat.java similarity index 94% rename from src/main/java/com/apify/client/DownloadItemsFormat.java rename to src/main/java/com/apify/client/dataset/DownloadItemsFormat.java index a165746..88d0fd2 100644 --- a/src/main/java/com/apify/client/DownloadItemsFormat.java +++ b/src/main/java/com/apify/client/dataset/DownloadItemsFormat.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.dataset; /** An output format for {@link DatasetClient#downloadItems}. */ public enum DownloadItemsFormat { diff --git a/src/main/java/com/apify/client/DefaultHttpBackend.java b/src/main/java/com/apify/client/http/DefaultApifyHttpClient.java similarity index 54% rename from src/main/java/com/apify/client/DefaultHttpBackend.java rename to src/main/java/com/apify/client/http/DefaultApifyHttpClient.java index d8c219f..aa0acf5 100644 --- a/src/main/java/com/apify/client/DefaultHttpBackend.java +++ b/src/main/java/com/apify/client/http/DefaultApifyHttpClient.java @@ -1,30 +1,39 @@ -package com.apify.client; +package com.apify.client.http; import java.io.IOException; import java.io.InputStream; -import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.time.Duration; /** - * The default {@link HttpBackend}, backed by the JDK's {@link java.net.http.HttpClient}. + * The default {@link HttpClient}, backed by the JDK's {@link java.net.http.HttpClient}. * *

The per-attempt timeout is applied to each {@link HttpRequest} by the orchestrating client, so * this backend sets only a connection timeout of its own. It follows normal redirects and reuses * connections from the underlying client's pool. + * + *

The JDK's own {@code java.net.http.HttpClient} is always referenced by its fully-qualified + * name in this class (never imported unqualified): an unqualified single-type import would clash + * with this package's own {@link HttpClient} interface of the same simple name. */ -public final class DefaultHttpBackend implements HttpBackend { +public final class DefaultApifyHttpClient implements HttpClient { /** - * Connection-establishment timeout (distinct from the per-request timeout the client applies). + * Connection-establishment timeout (distinct from the per-request timeout the client applies, + * which bounds an entire request/response round-trip and can reasonably be long for large + * dataset/key-value-store payloads). 10 seconds is generous for establishing a TCP+TLS connection + * to the Apify API even over a slow network, while still failing well before the per-request + * timeout on a genuinely unreachable host. To use a different value (or no separate connect + * timeout at all), configure your own {@link java.net.http.HttpClient} and pass it to {@link + * #DefaultApifyHttpClient(java.net.http.HttpClient)}. */ - private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(30); + private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(10); - private final HttpClient client; + private final java.net.http.HttpClient client; /** Creates a backend with a sensible default {@link java.net.http.HttpClient}. */ - public DefaultHttpBackend() { + public DefaultApifyHttpClient() { // Follow redirects (NORMAL, matching the reference clients): some endpoints — e.g. a // non-attachment key-value-store record GET — answer with a 302 to external storage, which the // JDK's default Redirect.NEVER would otherwise surface as an error. NORMAL does not follow an @@ -32,14 +41,14 @@ public DefaultHttpBackend() { // the // bearer token is not leaked to the redirect target. this( - HttpClient.newBuilder() + java.net.http.HttpClient.newBuilder() .connectTimeout(CONNECT_TIMEOUT) - .followRedirects(HttpClient.Redirect.NORMAL) + .followRedirects(java.net.http.HttpClient.Redirect.NORMAL) .build()); } /** Wraps a caller-provided {@link java.net.http.HttpClient} (share a pool, custom proxy/TLS). */ - public DefaultHttpBackend(HttpClient client) { + public DefaultApifyHttpClient(java.net.http.HttpClient client) { this.client = client; } @@ -49,7 +58,7 @@ public HttpResponse send(HttpRequest request) throws IOException, Interr } @Override - public HttpResponse sendStreaming(HttpRequest request) + public HttpResponse sendStreamingResponse(HttpRequest request) throws IOException, InterruptedException { return client.send(request, HttpResponse.BodyHandlers.ofInputStream()); } diff --git a/src/main/java/com/apify/client/HttpBackend.java b/src/main/java/com/apify/client/http/HttpClient.java similarity index 85% rename from src/main/java/com/apify/client/HttpBackend.java rename to src/main/java/com/apify/client/http/HttpClient.java index f75202a..ed6bd37 100644 --- a/src/main/java/com/apify/client/HttpBackend.java +++ b/src/main/java/com/apify/client/http/HttpClient.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.http; +import com.apify.client.ApifyClientBuilder; import java.io.IOException; import java.io.InputStream; import java.net.http.HttpRequest; @@ -16,10 +17,10 @@ *

A non-2xx HTTP status is not an error at this layer — return it as a normal {@link * HttpResponse}. Only transport-level failures (connection refused, DNS, timeout) should be thrown. * - *

Swap the default implementation via {@link ApifyClientBuilder#httpBackend(HttpBackend)} to + *

Swap the default implementation via {@link ApifyClientBuilder#httpBackend(HttpClient)} to * share a connection pool, customize TLS/proxy settings, or inject a mock in tests. */ -public interface HttpBackend { +public interface HttpClient { /** Sends a single request and buffers the whole response body as bytes. */ HttpResponse send(HttpRequest request) throws IOException, InterruptedException; @@ -28,6 +29,6 @@ public interface HttpBackend { * Sends a single request and returns the response body as a live {@link InputStream}, for * incremental consumption (used by log streaming). The caller closes the stream. */ - HttpResponse sendStreaming(HttpRequest request) + HttpResponse sendStreamingResponse(HttpRequest request) throws IOException, InterruptedException; } diff --git a/src/main/java/com/apify/client/internal/ApiErrorEnvelope.java b/src/main/java/com/apify/client/internal/ApiErrorEnvelope.java new file mode 100644 index 0000000..5bf27ad --- /dev/null +++ b/src/main/java/com/apify/client/internal/ApiErrorEnvelope.java @@ -0,0 +1,23 @@ +package com.apify.client.internal; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import java.util.Map; + +/** + * The {@code {"error": {...}}} envelope the API returns for a non-success response, deserialized + * directly by Jackson (in place of manual {@code JsonNode} field-by-field navigation). Internal to + * the client. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public final class ApiErrorEnvelope { + + public Body error; + + /** The nested {@code error} object: a machine-readable type, a message, and optional data. */ + @JsonIgnoreProperties(ignoreUnknown = true) + public static final class Body { + public String type; + public String message; + public Map data; + } +} diff --git a/src/main/java/com/apify/client/ApiResponse.java b/src/main/java/com/apify/client/internal/ApiResponse.java similarity index 53% rename from src/main/java/com/apify/client/ApiResponse.java rename to src/main/java/com/apify/client/internal/ApiResponse.java index a888f12..4eedb4d 100644 --- a/src/main/java/com/apify/client/ApiResponse.java +++ b/src/main/java/com/apify/client/internal/ApiResponse.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.internal; import java.net.http.HttpHeaders; @@ -6,12 +6,12 @@ * The parsed result of a single API call: the status code, headers and the fully-buffered response * body. Internal to the client. */ -final class ApiResponse { - final int statusCode; - final HttpHeaders headers; - final byte[] body; +public final class ApiResponse { + public final int statusCode; + public final HttpHeaders headers; + public final byte[] body; - ApiResponse(int statusCode, HttpHeaders headers, byte[] body) { + public ApiResponse(int statusCode, HttpHeaders headers, byte[] body) { this.statusCode = statusCode; this.headers = headers; this.body = body; diff --git a/src/main/java/com/apify/client/DataEnvelope.java b/src/main/java/com/apify/client/internal/DataEnvelope.java similarity index 70% rename from src/main/java/com/apify/client/DataEnvelope.java rename to src/main/java/com/apify/client/internal/DataEnvelope.java index 73bd7b2..295f32d 100644 --- a/src/main/java/com/apify/client/DataEnvelope.java +++ b/src/main/java/com/apify/client/internal/DataEnvelope.java @@ -1,10 +1,10 @@ -package com.apify.client; +package com.apify.client.internal; /** * Unwraps the top-level {@code {"data": ...}} wrapper used by most Apify endpoints. Internal. * * @param the type of the wrapped payload */ -final class DataEnvelope { +public final class DataEnvelope { public T data; } diff --git a/src/main/java/com/apify/client/HttpClientCore.java b/src/main/java/com/apify/client/internal/HttpClientCore.java similarity index 73% rename from src/main/java/com/apify/client/HttpClientCore.java rename to src/main/java/com/apify/client/internal/HttpClientCore.java index 24d04b7..0999b2d 100644 --- a/src/main/java/com/apify/client/HttpClientCore.java +++ b/src/main/java/com/apify/client/internal/HttpClientCore.java @@ -1,26 +1,35 @@ -package com.apify.client; +package com.apify.client.internal; import com.aayushatharva.brotli4j.Brotli4jLoader; import com.aayushatharva.brotli4j.encoder.Encoder; -import com.fasterxml.jackson.databind.JsonNode; +import com.apify.client.ApifyApiException; +import com.apify.client.ApifyTransportException; +import com.apify.client.http.HttpClient; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.SocketTimeoutException; import java.net.URI; +import java.net.URISyntaxException; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.net.http.HttpTimeoutException; import java.time.Duration; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ThreadLocalRandom; import java.util.zip.GZIPOutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The orchestrating HTTP client shared by every resource client. It owns the backend, the optional * API token, the {@code User-Agent}, and the retry/timeout policy, and applies them to every * request. Internal to the client; safe for concurrent use. */ -final class HttpClientCore { +public final class HttpClientCore { + + private static final Logger LOG = LoggerFactory.getLogger(HttpClientCore.class); /** Status returned when the per-resource rate limit is hit. */ private static final int RATE_LIMIT_EXCEEDED = 429; @@ -29,7 +38,7 @@ final class HttpClientCore { private static final int MIN_SERVER_ERROR = 500; /** Responses with status below this value are treated as success. */ - static final int MAX_SUCCESS_STATUS = 300; + public static final int MAX_SUCCESS_STATUS = 300; /** Exponential-backoff multiplier applied to the inter-retry delay after each attempt. */ private static final int BACKOFF_FACTOR = 2; @@ -71,32 +80,32 @@ private static boolean detectBrotli() { } /** Reports whether the brotli path is active on this platform (package-private for tests). */ - static boolean brotliAvailable() { + public static boolean brotliAvailable() { return BROTLI_AVAILABLE; } - private final HttpBackend backend; + private final HttpClient backend; private final String token; private final String userAgent; private final RetryConfig retry; - HttpClientCore(HttpBackend backend, String token, String userAgent, RetryConfig retry) { + public HttpClientCore(HttpClient backend, String token, String userAgent, RetryConfig retry) { this.backend = backend; this.token = token; this.userAgent = userAgent; this.retry = retry; } - String userAgent() { + public String userAgent() { return userAgent; } - HttpBackend backend() { + public HttpClient backend() { return backend; } /** The configured maximum per-attempt request timeout, in whole seconds. */ - long requestTimeoutSeconds() { + public long requestTimeoutSeconds() { return retry.timeout.getSeconds(); } @@ -106,12 +115,12 @@ long requestTimeoutSeconds() { * the first attempt too. Individual calls may pass a smaller base (e.g. key-value-store uploads), * which then grows back up toward this value on retries. */ - Duration baseRequestTimeout() { + public Duration baseRequestTimeout() { return retry.timeout; } /** Sends a request with auth, User-Agent and the retry policy applied. */ - ApiResponse call( + public ApiResponse call( String method, String url, byte[] body, String contentType, Duration baseTimeout) { return callWithHeaders(method, url, body, contentType, null, baseTimeout, false); } @@ -121,7 +130,7 @@ ApiResponse call( * treated as a terminal failure rather than being retried (other transport/network errors and * retryable statuses are still retried). */ - ApiResponse call( + public ApiResponse call( String method, String url, byte[] body, @@ -132,7 +141,7 @@ ApiResponse call( } /** Like {@link #call} but additionally sets the given extra headers on every attempt. */ - ApiResponse callWithHeaders( + public ApiResponse callWithHeaders( String method, String url, byte[] body, @@ -142,7 +151,7 @@ ApiResponse callWithHeaders( return callWithHeaders(method, url, body, contentType, extraHeaders, baseTimeout, false); } - ApiResponse callWithHeaders( + public ApiResponse callWithHeaders( String method, String url, byte[] body, @@ -182,17 +191,33 @@ ApiResponse callWithHeaders( } lastError = buildApiError(resp.statusCode, resp.body, attempt, method, path); retryable = isStatusRetryable(resp.statusCode); - } catch (TransportException e) { + } catch (ApifyTransportException e) { lastError = e; // Network/timeout failures are retryable, unless the caller opted out of retrying timeouts. retryable = !(doNotRetryTimeouts && isTimeout(e)); } if (!retryable || attempt == maxAttempts) { + LOG.warn( + "{} {} failed on attempt {}/{}, giving up: {}", + method, + path, + attempt, + maxAttempts, + lastError.getMessage()); throw lastError; } - sleep(randomizedDelay(delay)); + Duration sleepFor = randomizedDelay(delay); + LOG.debug( + "{} {} failed on attempt {}/{} ({}), retrying in {}ms", + method, + path, + attempt, + maxAttempts, + lastError.getMessage(), + sleepFor.toMillis()); + sleep(sleepFor); delay = minDuration(delay.multipliedBy(BACKOFF_FACTOR), retry.maxDelayBetweenRetries); } throw lastError; // unreachable in practice (maxAttempts >= 1), defensive @@ -201,7 +226,7 @@ ApiResponse callWithHeaders( /** * Builds a fully-prepared {@link HttpRequest} with auth, User-Agent, timeout and extra headers. */ - HttpRequest buildRequest( + public HttpRequest buildRequest( String method, String url, byte[] body, @@ -239,10 +264,10 @@ private ApiResponse doAttempt( HttpResponse resp = backend.send(request); return new ApiResponse(resp.statusCode(), resp.headers(), resp.body()); } catch (IOException e) { - throw new TransportException(e); + throw new ApifyTransportException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new TransportException(e); + throw new ApifyTransportException(e); } } @@ -287,11 +312,11 @@ private static boolean shouldCompress(byte[] body, Map extraHead /** * A compressed request body together with the {@code Content-Encoding} token that describes it. */ - static final class Compressed { - final byte[] body; - final String encoding; + public static final class Compressed { + public final byte[] body; + public final String encoding; - Compressed(byte[] body, String encoding) { + public Compressed(byte[] body, String encoding) { this.body = body; this.encoding = encoding; } @@ -304,7 +329,7 @@ static final class Compressed { * pass {@link #BROTLI_AVAILABLE}; making the coding an explicit parameter keeps this a pure * function of its inputs rather than of hidden static state. Package-private. */ - static Compressed compress(byte[] data, boolean preferBrotli) { + public static Compressed compress(byte[] data, boolean preferBrotli) { return preferBrotli ? new Compressed(brotli(data), ENCODING_BROTLI) : new Compressed(gzip(data), ENCODING_GZIP); @@ -316,7 +341,9 @@ private static byte[] brotli(byte[] data) { return Encoder.compress(data); } catch (IOException e) { // Encoding an in-memory byte[] performs no real I/O, so this is unreachable in practice. - throw new TransportException(e); + // UncheckedIOException, not ApifyTransportException: a local encoding failure never touched + // the network, so it is not a transport failure. + throw new java.io.UncheckedIOException(e); } } @@ -331,8 +358,9 @@ private static byte[] gzip(byte[] data) { gz.write(data); } catch (IOException e) { // Compressing an in-memory byte[] cannot perform real I/O, so this is unreachable in - // practice. - throw new TransportException(e); + // practice. UncheckedIOException, not ApifyTransportException: a local encoding failure + // never touched the network, so it is not a transport failure. + throw new java.io.UncheckedIOException(e); } return out.toByteArray(); } @@ -341,9 +369,17 @@ private static boolean isStatusRetryable(int status) { return status == RATE_LIMIT_EXCEEDED || status >= MIN_SERVER_ERROR; } - /** Reports whether a transport failure was caused by a request timeout. */ - private static boolean isTimeout(TransportException e) { - return e.getCause() instanceof java.net.http.HttpTimeoutException; + /** + * Reports whether a transport failure was caused by a request timeout. Recognizes both the JDK + * default backend's {@link HttpTimeoutException} and the more general {@link + * SocketTimeoutException} some custom {@link HttpClient} implementations throw for a timeout (the + * {@link HttpClient} interface contract only guarantees the cause is an {@code IOException}, not + * a specific backend's exception type) — a custom backend that throws one of these for a timeout + * gets the same {@code doNotRetryTimeouts} behavior as the default backend. + */ + private static boolean isTimeout(ApifyTransportException e) { + Throwable cause = e.getCause(); + return cause instanceof HttpTimeoutException || cause instanceof SocketTimeoutException; } /** @@ -362,7 +398,7 @@ private static void sleep(Duration d) { Thread.sleep(Math.max(0, d.toMillis())); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new TransportException(e); + throw new ApifyTransportException(e); } } @@ -370,28 +406,26 @@ private static Duration minDuration(Duration a, Duration b) { return a.compareTo(b) < 0 ? a : b; } - /** Builds an {@link ApifyApiException} from an API error response body. */ - static ApifyApiException buildApiError( + /** + * Builds an {@link ApifyApiException} from an API error response body, deserializing the {@code + * {"error": {...}}} envelope directly into a typed {@link ApiErrorEnvelope} rather than + * navigating a raw {@code JsonNode} field by field. + */ + public static ApifyApiException buildApiError( int status, byte[] body, int attempt, String method, String path) { String type = null; String message = null; Map data = null; try { - JsonNode root = Json.MAPPER.readTree(body); - JsonNode error = root.get("error"); - if (error != null && error.hasNonNull("message")) { - type = error.path("type").asText(null); - message = error.path("message").asText(null); - JsonNode dataNode = error.get("data"); - if (dataNode != null && dataNode.isObject()) { - data = - Json.MAPPER.convertValue( - dataNode, - new com.fasterxml.jackson.core.type.TypeReference>() {}); - } + ApiErrorEnvelope envelope = Json.parse(body, ApiErrorEnvelope.class); + if (envelope != null && envelope.error != null && envelope.error.message != null) { + type = envelope.error.type; + message = envelope.error.message; + data = envelope.error.data; } - } catch (IOException ignored) { - // Fall through to the generic message below. + } catch (RuntimeException ignored) { + // Body was not the expected error envelope (e.g. an HTML error page from a proxy, or + // malformed/empty JSON). Fall through to the generic message below. } if (message == null) { message = @@ -402,36 +436,33 @@ static ApifyApiException buildApiError( return new ApifyApiException(status, type, message, attempt, method, path, data); } - /** Returns the path+query portion of a URL, for error reporting. */ - static String extractPath(String url) { - String rest = url; - int scheme = rest.indexOf("://"); - if (scheme >= 0) { - rest = rest.substring(scheme + 3); - } - int slash = rest.indexOf('/'); - return slash >= 0 ? rest.substring(slash) : ""; - } - - /** Internal marker for transport-level (network/timeout) failures, which are retryable. */ - static final class TransportException extends RuntimeException { - private static final long serialVersionUID = 1L; - - TransportException(Throwable cause) { - super(cause); + /** + * Returns the path+query portion of a URL, for error reporting, by parsing it as a {@link URI} + * rather than with manual string slicing. + */ + public static String extractPath(String url) { + try { + URI uri = new URI(url); + String path = uri.getRawPath() == null ? "" : uri.getRawPath(); + String query = uri.getRawQuery(); + return query == null ? path : path + "?" + query; + } catch (URISyntaxException e) { + // Not expected for a URL this client built itself; fall back to an empty path rather than + // let error reporting itself fail. + return ""; } } /** Opens a live streaming response (single attempt, no retry). Used by log streaming. */ - HttpResponse stream(String url) { + public HttpResponse stream(String url) { HttpRequest request = buildRequest("GET", url, null, null, null, retry.timeout); try { - return backend.sendStreaming(request); + return backend.sendStreamingResponse(request); } catch (IOException e) { - throw new TransportException(e); + throw new ApifyTransportException(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new TransportException(e); + throw new ApifyTransportException(e); } } } diff --git a/src/main/java/com/apify/client/Json.java b/src/main/java/com/apify/client/internal/Json.java similarity index 86% rename from src/main/java/com/apify/client/Json.java rename to src/main/java/com/apify/client/internal/Json.java index 5d6382d..0c97ce5 100644 --- a/src/main/java/com/apify/client/Json.java +++ b/src/main/java/com/apify/client/internal/Json.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.internal; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; @@ -20,7 +20,7 @@ * forward compatibility), renders dates as ISO-8601 strings, and omits {@code null} fields when * serializing request bodies. */ -final class Json { +public final class Json { static final ObjectMapper MAPPER = new ObjectMapper() @@ -37,7 +37,7 @@ final class Json { private Json() {} /** Serializes a value to JSON bytes. */ - static byte[] toBytes(Object value) { + public static byte[] toBytes(Object value) { try { return MAPPER.writeValueAsBytes(value); } catch (IOException e) { @@ -46,7 +46,7 @@ static byte[] toBytes(Object value) { } /** Parses JSON bytes into the given type. */ - static T parse(byte[] body, JavaType type) { + public static T parse(byte[] body, JavaType type) { try { return MAPPER.readValue(body, type); } catch (IOException e) { @@ -55,7 +55,7 @@ static T parse(byte[] body, JavaType type) { } /** Parses JSON bytes into the given class. */ - static T parse(byte[] body, Class type) { + public static T parse(byte[] body, Class type) { try { return MAPPER.readValue(body, type); } catch (IOException e) { @@ -64,7 +64,7 @@ static T parse(byte[] body, Class type) { } /** Parses JSON bytes into the given {@link TypeReference} (for generic types). */ - static T parse(byte[] body, TypeReference type) { + public static T parse(byte[] body, TypeReference type) { try { return MAPPER.readValue(body, type); } catch (IOException e) { @@ -73,12 +73,12 @@ static T parse(byte[] body, TypeReference type) { } /** Constructs a {@link JavaType} for a raw class. */ - static JavaType type(Class raw) { + public static JavaType type(Class raw) { return MAPPER.getTypeFactory().constructType(raw); } /** Constructs a parametric {@link JavaType}, e.g. {@code PaginationList}. */ - static JavaType parametric(Class raw, JavaType... params) { + public static JavaType parametric(Class raw, JavaType... params) { return MAPPER.getTypeFactory().constructParametricType(raw, params); } @@ -86,14 +86,14 @@ static JavaType parametric(Class raw, JavaType... params) { * Parses a JSON response body wrapped in a {@code {"data": ...}} envelope, returning the * unwrapped {@code data} value of the given type. */ - static T parseData(byte[] body, JavaType dataType) { + public static T parseData(byte[] body, JavaType dataType) { JavaType envelopeType = parametric(DataEnvelope.class, dataType); DataEnvelope envelope = parse(body, envelopeType); return envelope.data; } /** Parses a data-envelope whose {@code data} is of the given class. */ - static T parseData(byte[] body, Class dataClass) { + public static T parseData(byte[] body, Class dataClass) { return parseData(body, type(dataClass)); } } diff --git a/src/main/java/com/apify/client/PaginatedIterator.java b/src/main/java/com/apify/client/internal/PaginatedIterator.java similarity index 93% rename from src/main/java/com/apify/client/PaginatedIterator.java rename to src/main/java/com/apify/client/internal/PaginatedIterator.java index dd990ba..f9af796 100644 --- a/src/main/java/com/apify/client/PaginatedIterator.java +++ b/src/main/java/com/apify/client/internal/PaginatedIterator.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.internal; +import com.apify.client.PaginationList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -29,11 +30,11 @@ * signal would truncate iteration. Terminating on an empty page costs one extra request at the end * but yields the complete result in every case. */ -final class PaginatedIterator implements Iterator { +public final class PaginatedIterator implements Iterator { /** Fetches a single page starting at {@code offset}, requesting at most {@code limit} items. */ @FunctionalInterface - interface PageFetcher { + public interface PageFetcher { PaginationList fetch(long offset, Long limit); } @@ -47,7 +48,8 @@ interface PageFetcher { private long yielded; private boolean exhausted; - PaginatedIterator(Long totalLimit, Long chunkSize, Long startOffset, PageFetcher fetcher) { + public PaginatedIterator( + Long totalLimit, Long chunkSize, Long startOffset, PageFetcher fetcher) { this.totalLimit = totalLimit != null && totalLimit > 0 ? totalLimit : null; this.chunkSize = chunkSize != null && chunkSize > 0 ? chunkSize : null; this.offset = startOffset != null && startOffset > 0 ? startOffset : 0; diff --git a/src/main/java/com/apify/client/QueryParams.java b/src/main/java/com/apify/client/internal/QueryParams.java similarity index 79% rename from src/main/java/com/apify/client/QueryParams.java rename to src/main/java/com/apify/client/internal/QueryParams.java index 5abcafe..89a78bf 100644 --- a/src/main/java/com/apify/client/QueryParams.java +++ b/src/main/java/com/apify/client/internal/QueryParams.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.internal; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -9,14 +9,14 @@ * An ordered collection of query parameters that omits absent ({@code null}) values and encodes * booleans as {@code 1}/{@code 0}, matching the Apify API conventions. Internal to the client. */ -final class QueryParams { +public final class QueryParams { private final List pairs = new ArrayList<>(); - QueryParams() {} + public QueryParams() {} /** Adds a string parameter if the value is non-null. */ - QueryParams addString(String key, String value) { + public QueryParams addString(String key, String value) { if (value != null) { pairs.add(new String[] {key, value}); } @@ -24,7 +24,7 @@ QueryParams addString(String key, String value) { } /** Adds an integer parameter if the value is non-null. */ - QueryParams addLong(String key, Long value) { + public QueryParams addLong(String key, Long value) { if (value != null) { pairs.add(new String[] {key, Long.toString(value)}); } @@ -32,7 +32,7 @@ QueryParams addLong(String key, Long value) { } /** Adds a floating-point parameter if the value is non-null. */ - QueryParams addDouble(String key, Double value) { + public QueryParams addDouble(String key, Double value) { if (value != null) { pairs.add(new String[] {key, Double.toString(value)}); } @@ -40,7 +40,7 @@ QueryParams addDouble(String key, Double value) { } /** Adds a boolean parameter, encoded as {@code 1}/{@code 0}, if the value is non-null. */ - QueryParams addBool(String key, Boolean value) { + public QueryParams addBool(String key, Boolean value) { if (value != null) { pairs.add(new String[] {key, value ? "1" : "0"}); } @@ -48,7 +48,7 @@ QueryParams addBool(String key, Boolean value) { } /** Adds a comma-joined list parameter if the list is non-null and non-empty. */ - QueryParams addCsv(String key, List values) { + public QueryParams addCsv(String key, List values) { if (values != null && !values.isEmpty()) { pairs.add(new String[] {key, String.join(",", values)}); } @@ -56,24 +56,24 @@ QueryParams addCsv(String key, List values) { } /** Appends an already-stringified key/value pair unconditionally. */ - QueryParams addRaw(String key, String value) { + public QueryParams addRaw(String key, String value) { pairs.add(new String[] {key, value}); return this; } - boolean isEmpty() { + public boolean isEmpty() { return pairs.isEmpty(); } /** Returns a shallow copy of this instance. */ - QueryParams copy() { + public QueryParams copy() { QueryParams out = new QueryParams(); out.pairs.addAll(pairs); return out; } /** Appends all pairs from {@code other} to this instance. */ - QueryParams extend(QueryParams other) { + public QueryParams extend(QueryParams other) { if (other != null) { pairs.addAll(other.pairs); } @@ -81,7 +81,7 @@ QueryParams extend(QueryParams other) { } /** Appends the parameters to {@code rawUrl} as a URL-encoded query string. */ - String applyToUrl(String rawUrl) { + public String applyToUrl(String rawUrl) { if (pairs.isEmpty()) { return rawUrl; } diff --git a/src/main/java/com/apify/client/ResourceContext.java b/src/main/java/com/apify/client/internal/ResourceContext.java similarity index 85% rename from src/main/java/com/apify/client/ResourceContext.java rename to src/main/java/com/apify/client/internal/ResourceContext.java index 1c2e8d7..26eacfe 100644 --- a/src/main/java/com/apify/client/ResourceContext.java +++ b/src/main/java/com/apify/client/internal/ResourceContext.java @@ -1,5 +1,8 @@ -package com.apify.client; +package com.apify.client.internal; +import com.apify.client.ApifyApiException; +import com.apify.client.ApifyTransportException; +import com.apify.client.PaginationList; import com.fasterxml.jackson.databind.JavaType; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -14,10 +17,10 @@ * here implement the CRUD primitives once, so each resource client stays small and consistent * (DRY). Internal to the client. */ -final class ResourceContext { +public final class ResourceContext { - static final String CONTENT_TYPE_JSON = "application/json"; - static final String CONTENT_TYPE_JSON_CHARSET = "application/json; charset=utf-8"; + public static final String CONTENT_TYPE_JSON = "application/json"; + public static final String CONTENT_TYPE_JSON_CHARSET = "application/json; charset=utf-8"; /** How long to wait between polls while waiting for a run/build to finish. */ private static final Duration WAIT_POLL_INTERVAL = Duration.ofMillis(250); @@ -42,18 +45,18 @@ final class ResourceContext { private static final int NOT_FOUND = 404; - final HttpClientCore http; + public final HttpClientCore http; /** Fully-qualified base URL of the resource, e.g. {@code https://api.apify.com/v2/actors/ID}. */ - final String url; + public final String url; - final QueryParams baseParams; + public final QueryParams baseParams; /** Origin (scheme + host) the API is reached through. */ - final String apiOrigin; + public final String apiOrigin; /** Origin used to build public, shareable URLs (defaults to {@link #apiOrigin}). */ - String publicOrigin; + private String publicOrigin; private ResourceContext(HttpClientCore http, String url, String baseUrl) { this.http = http; @@ -64,31 +67,32 @@ private ResourceContext(HttpClientCore http, String url, String baseUrl) { } /** Creates a context for a collection endpoint: {@code {base}/{resourcePath}}. */ - static ResourceContext collection(HttpClientCore http, String baseUrl, String resourcePath) { + public static ResourceContext collection( + HttpClientCore http, String baseUrl, String resourcePath) { return new ResourceContext(http, baseUrl + "/" + resourcePath, baseUrl); } /** Creates a context for a single resource: {@code {base}/{resourcePath}/{safeId}}. */ - static ResourceContext single( + public static ResourceContext single( HttpClientCore http, String baseUrl, String resourcePath, String id) { return new ResourceContext(http, baseUrl + "/" + resourcePath + "/" + toSafeId(id), baseUrl); } /** Overrides the origin used when building public URLs. */ - ResourceContext withPublicOrigin(String publicBaseUrl) { + public ResourceContext withPublicOrigin(String publicBaseUrl) { this.publicOrigin = originOf(publicBaseUrl); return this; } /** This resource's URL with an optional extra path segment appended. */ - String subUrl(String subPath) { + public String subUrl(String subPath) { return (subPath == null || subPath.isEmpty()) ? url : url + "/" + subPath; } /** * The public (shareable) form of this resource's URL, swapping the API origin for the public one. */ - String publicUrl(String subPath) { + public String publicUrl(String subPath) { String apiUrl = subUrl(subPath); if (publicOrigin.equals(apiOrigin)) { return apiUrl; @@ -100,7 +104,7 @@ String publicUrl(String subPath) { } /** Merges the inherited base params with per-call params. */ - QueryParams mergedParams(QueryParams params) { + public QueryParams mergedParams(QueryParams params) { return baseParams.copy().extend(params); } @@ -109,7 +113,7 @@ QueryParams mergedParams(QueryParams params) { * {@code status}/{@code origin} filters carry into its nested storage/log accessors). No-op when * {@code inherited} is null or empty, so ordinary nested clients are unaffected. */ - ResourceContext seedParams(QueryParams inherited) { + public ResourceContext seedParams(QueryParams inherited) { if (inherited != null && !inherited.isEmpty()) { baseParams.extend(inherited); } @@ -118,7 +122,7 @@ ResourceContext seedParams(QueryParams inherited) { // ---- CRUD primitives ------------------------------------------------------ - Optional getResource(String subPath, QueryParams params, JavaType dataType) { + public Optional getResource(String subPath, QueryParams params, JavaType dataType) { try { // ofNullable, not of: an HTTP 200 with body {"data": null} unwraps to null, which is a valid // "no resource" answer rather than a programming error — never surface it as a raw NPE. @@ -131,21 +135,21 @@ Optional getResource(String subPath, QueryParams params, JavaType dataTyp } } - Optional getResource(String subPath, QueryParams params, Class dataClass) { + public Optional getResource(String subPath, QueryParams params, Class dataClass) { return getResource(subPath, params, Json.type(dataClass)); } - T getResourceRequired(String subPath, QueryParams params, JavaType dataType) { + public T getResourceRequired(String subPath, QueryParams params, JavaType dataType) { String u = mergedParams(params).applyToUrl(subUrl(subPath)); ApiResponse resp = http.call("GET", u, null, "", http.baseRequestTimeout()); return Json.parseData(resp.body, dataType); } - T getResourceRequired(String subPath, QueryParams params, Class dataClass) { + public T getResourceRequired(String subPath, QueryParams params, Class dataClass) { return getResourceRequired(subPath, params, Json.type(dataClass)); } - T updateResource(String subPath, Object body, Class dataClass) { + public T updateResource(String subPath, Object body, Class dataClass) { String u = mergedParams(new QueryParams()).applyToUrl(subUrl(subPath)); ApiResponse resp = http.call("PUT", u, Json.toBytes(body), CONTENT_TYPE_JSON, http.baseRequestTimeout()); @@ -153,7 +157,7 @@ T updateResource(String subPath, Object body, Class dataClass) { } /** Performs a DELETE; a not-found is treated as a successful no-op. */ - void deleteResource(String subPath) { + public void deleteResource(String subPath) { String u = mergedParams(new QueryParams()).applyToUrl(subUrl(subPath)); try { http.call("DELETE", u, null, "", http.baseRequestTimeout()); @@ -164,7 +168,8 @@ void deleteResource(String subPath) { } } - PaginationList listResource(String subPath, QueryParams params, Class itemClass) { + public PaginationList listResource( + String subPath, QueryParams params, Class itemClass) { JavaType listType = Json.parametric(PaginationList.class, Json.type(itemClass)); return getResourceRequired(subPath, params, listType); } @@ -175,7 +180,7 @@ PaginationList listResource(String subPath, QueryParams params, Class * drives per page). {@code totalLimit} caps the total items yielded; {@code chunkSize} is the * page size (both {@code null} meaning "unbounded" / "server default"). */ - Iterator iterateResource( + public Iterator iterateResource( String subPath, Long totalLimit, Long chunkSize, @@ -197,7 +202,7 @@ Iterator iterateResource( }); } - T createResource(QueryParams params, Object body, Class dataClass) { + public T createResource(QueryParams params, Object body, Class dataClass) { String u = mergedParams(params).applyToUrl(subUrl("")); ApiResponse resp = http.call("POST", u, Json.toBytes(body), CONTENT_TYPE_JSON, http.baseRequestTimeout()); @@ -205,7 +210,7 @@ T createResource(QueryParams params, Object body, Class dataClass) { } /** POST that gets-or-creates a named resource ({@code POST {collection}?name=...}). */ - T getOrCreateNamed(String name, Class dataClass) { + public T getOrCreateNamed(String name, Class dataClass) { return getOrCreateNamed(name, null, dataClass); } @@ -213,7 +218,7 @@ T getOrCreateNamed(String name, Class dataClass) { * POST that gets-or-creates a named resource, optionally sending a JSON request body (e.g. a * storage {@code schema}). A {@code null} body sends no body, matching the plain get-or-create. */ - T getOrCreateNamed(String name, Object body, Class dataClass) { + public T getOrCreateNamed(String name, Object body, Class dataClass) { QueryParams params = new QueryParams(); if (name != null && !name.isEmpty()) { params.addString("name", name); @@ -227,12 +232,12 @@ T getOrCreateNamed(String name, Object body, Class dataClass) { } /** POST with a raw body (optional) and content type, unwrapping the data envelope. */ - T postWithBody( + public T postWithBody( String subPath, QueryParams params, byte[] body, String contentType, Class dataClass) { return postWithBody(subPath, params, body, contentType, Json.type(dataClass)); } - T postWithBody( + public T postWithBody( String subPath, QueryParams params, byte[] body, String contentType, JavaType dataType) { String u = mergedParams(params).applyToUrl(subUrl(subPath)); ApiResponse resp = http.call("POST", u, body, contentType, http.baseRequestTimeout()); @@ -244,7 +249,7 @@ T postWithBody( * {"data": ...}} envelope. Used by endpoints (e.g. actor input validation) whose response is a * plain object rather than the standard data envelope. */ - T postWithBodyNoEnvelope( + public T postWithBodyNoEnvelope( String subPath, QueryParams params, byte[] body, String contentType, Class dataClass) { String u = mergedParams(params).applyToUrl(subUrl(subPath)); ApiResponse resp = http.call("POST", u, body, contentType, http.baseRequestTimeout()); @@ -252,7 +257,7 @@ T postWithBodyNoEnvelope( } /** DELETE with a JSON body (used for batch request deletion), unwrapping the data envelope. */ - T deleteWithBody(String subPath, QueryParams params, Object body, Class dataClass) { + public T deleteWithBody(String subPath, QueryParams params, Object body, Class dataClass) { String u = mergedParams(params).applyToUrl(subUrl(subPath)); ApiResponse resp = http.call("DELETE", u, Json.toBytes(body), CONTENT_TYPE_JSON, http.baseRequestTimeout()); @@ -260,7 +265,7 @@ T deleteWithBody(String subPath, QueryParams params, Object body, Class d } /** GET returning the raw response (no data envelope). Returns {@code null} on not-found. */ - ApiResponse getRaw(String subPath, QueryParams params) { + public ApiResponse getRaw(String subPath, QueryParams params) { String u = mergedParams(params).applyToUrl(subUrl(subPath)); try { return http.call("GET", u, null, "", http.baseRequestTimeout()); @@ -273,7 +278,7 @@ ApiResponse getRaw(String subPath, QueryParams params) { } /** HEAD request; returns whether the resource exists. */ - boolean headExists(String subPath, QueryParams params) { + public boolean headExists(String subPath, QueryParams params) { String u = mergedParams(params).applyToUrl(subUrl(subPath)); try { http.call("HEAD", u, null, "", http.baseRequestTimeout()); @@ -287,7 +292,7 @@ boolean headExists(String subPath, QueryParams params) { } /** PUT with raw bytes and a content type (used for key-value-store record uploads). */ - void putRaw(String subPath, QueryParams params, byte[] body, String contentType) { + public void putRaw(String subPath, QueryParams params, byte[] body, String contentType) { putRaw(subPath, params, body, contentType, http.baseRequestTimeout(), false); } @@ -296,7 +301,7 @@ void putRaw(String subPath, QueryParams params, byte[] body, String contentType) * over whether transport timeouts are retried. Used by key-value-store record uploads that expose * the reference client's {@code timeoutSecs}/{@code doNotRetryTimeouts} write options. */ - void putRaw( + public void putRaw( String subPath, QueryParams params, byte[] body, @@ -325,7 +330,7 @@ private long serverWaitCapSecs() { * connection longer than the client's own per-request timeout. Returns {@code null} for a {@code * null} input (no server-side wait requested). */ - Long clampServerWait(Long waitForFinishSecs) { + public Long clampServerWait(Long waitForFinishSecs) { if (waitForFinishSecs == null) { return null; } @@ -341,7 +346,7 @@ Long clampServerWait(Long waitForFinishSecs) { * currently present: a just-started run/build can transiently return 404 (database-replica lag), * which is treated as "not yet available". */ - T waitForFinish( + public T waitForFinish( Long waitSecs, String resourceName, JavaType dataType, Predicate isTerminal) { // Clamp to MAX_WAIT_FOR_FINISH_SECS so a pathological waitSecs near Long.MAX_VALUE cannot // overflow budgetMillis into a negative value (which would degrade the wait into a single @@ -402,14 +407,14 @@ private static void sleep(Duration d) { Thread.sleep(d.toMillis()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new HttpClientCore.TransportException(e); + throw new ApifyTransportException(e); } } // ---- URL / id helpers ----------------------------------------------------- /** Reports whether an exception represents a "resource not found" API error. */ - static boolean isNotFound(ApifyApiException e) { + public static boolean isNotFound(ApifyApiException e) { if (e.getStatusCode() != NOT_FOUND) { return false; } @@ -423,7 +428,7 @@ static boolean isNotFound(ApifyApiException e) { * Encodes a resource id so it is safe to embed in a URL path. Apify uses the {@code * username~resourcename} form, so the first {@code /} of an id is replaced with {@code ~}. */ - static String toSafeId(String id) { + public static String toSafeId(String id) { int slash = id.indexOf('/'); return slash < 0 ? id : id.substring(0, slash) + "~" + id.substring(slash + 1); } @@ -432,12 +437,12 @@ static String toSafeId(String id) { * Percent-encodes a single URL path segment, so that values interpolated into the path (record * keys, request IDs) cannot break out of the segment. */ - static String encodePathSegment(String input) { + public static String encodePathSegment(String input) { return URLEncoder.encode(input, StandardCharsets.UTF_8).replace("+", "%20"); } /** Extracts the origin ({@code scheme://host[:port]}) from a URL, dropping any path. */ - static String originOf(String rawUrl) { + public static String originOf(String rawUrl) { String rest = rawUrl; String scheme = ""; int i = rest.indexOf("://"); diff --git a/src/main/java/com/apify/client/internal/ResourcePaths.java b/src/main/java/com/apify/client/internal/ResourcePaths.java new file mode 100644 index 0000000..6b03ca2 --- /dev/null +++ b/src/main/java/com/apify/client/internal/ResourcePaths.java @@ -0,0 +1,28 @@ +package com.apify.client.internal; + +/** + * The Apify API's top-level resource collection path segments, in one place so the same literal is + * never duplicated between {@code ApifyClient} (which passes some of them to a resource client's + * constructor) and the resource clients that otherwise hardcode their own path. Internal to the + * client — not the URL paths of the OpenAPI spec verbatim, just the single path segment each + * resource client is rooted at (e.g. {@code https://api.apify.com/v2/}{@value #DATASETS}{@code + * /{id}}). + */ +public final class ResourcePaths { + + public static final String ACTORS = "actors"; + public static final String ACTOR_BUILDS = "actor-builds"; + public static final String ACTOR_RUNS = "actor-runs"; + public static final String ACTOR_TASKS = "actor-tasks"; + public static final String DATASETS = "datasets"; + public static final String KEY_VALUE_STORES = "key-value-stores"; + public static final String REQUEST_QUEUES = "request-queues"; + public static final String SCHEDULES = "schedules"; + public static final String STORE = "store"; + public static final String USERS = "users"; + public static final String WEBHOOKS = "webhooks"; + public static final String WEBHOOK_DISPATCHES = "webhook-dispatches"; + public static final String LOGS = "logs"; + + private ResourcePaths() {} +} diff --git a/src/main/java/com/apify/client/RetryConfig.java b/src/main/java/com/apify/client/internal/RetryConfig.java similarity index 92% rename from src/main/java/com/apify/client/RetryConfig.java rename to src/main/java/com/apify/client/internal/RetryConfig.java index 31c5ae2..2792e52 100644 --- a/src/main/java/com/apify/client/RetryConfig.java +++ b/src/main/java/com/apify/client/internal/RetryConfig.java @@ -1,9 +1,9 @@ -package com.apify.client; +package com.apify.client.internal; import java.time.Duration; /** Retry/timeout policy for the orchestrating HTTP client. Internal to the client. */ -final class RetryConfig { +public final class RetryConfig { /** Maximum number of retries (the request is attempted up to {@code maxRetries + 1} times). */ final int maxRetries; @@ -20,7 +20,7 @@ final class RetryConfig { */ final Duration timeout; - RetryConfig( + public RetryConfig( int maxRetries, Duration minDelayBetweenRetries, Duration maxDelayBetweenRetries, diff --git a/src/main/java/com/apify/client/Signatures.java b/src/main/java/com/apify/client/internal/Signatures.java similarity index 92% rename from src/main/java/com/apify/client/Signatures.java rename to src/main/java/com/apify/client/internal/Signatures.java index 1c45766..b220202 100644 --- a/src/main/java/com/apify/client/Signatures.java +++ b/src/main/java/com/apify/client/internal/Signatures.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.internal; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -11,7 +11,7 @@ * {@code @apify/utilities} implementation that the reference clients rely on. Internal to the * client. */ -final class Signatures { +public final class Signatures { /** Version tag embedded in storage-content signatures (upstream default). */ private static final String STORAGE_CONTENT_SIGNATURE_VERSION = "0"; @@ -32,7 +32,7 @@ private Signatures() {} * createHmacSignature}: HMAC-SHA256(secret, message) as lowercase hex, take the first 30 hex * characters, interpret them as a big integer, then base62-encode (alphabet {@code 0-9a-zA-Z}). */ - static String createHmacSignature(String secretKey, String message) { + public static String createHmacSignature(String secretKey, String message) { byte[] digest = hmacSha256(secretKey, message); StringBuilder hex = new StringBuilder(digest.length * 2); for (byte b : digest) { @@ -44,7 +44,7 @@ static String createHmacSignature(String secretKey, String message) { } /** Encodes a non-negative big integer in base62 using the {@code 0-9a-zA-Z} alphabet. */ - static String toBase62(BigInteger value) { + public static String toBase62(BigInteger value) { if (value.signum() == 0) { return "0"; } @@ -69,7 +69,7 @@ static String toBase62(BigInteger value) { * * @param expiresInSecs optional expiry in seconds ({@code null} for a non-expiring URL) */ - static String signStorageContent(String secretKey, String resourceId, Long expiresInSecs) { + public static String signStorageContent(String secretKey, String resourceId, Long expiresInSecs) { long expiresAtMillis = expiresInSecs != null ? System.currentTimeMillis() + expiresInSecs * 1000L : 0L; String version = STORAGE_CONTENT_SIGNATURE_VERSION; diff --git a/src/main/java/com/apify/client/Statuses.java b/src/main/java/com/apify/client/internal/Statuses.java similarity index 79% rename from src/main/java/com/apify/client/Statuses.java rename to src/main/java/com/apify/client/internal/Statuses.java index 71f74f3..cded735 100644 --- a/src/main/java/com/apify/client/Statuses.java +++ b/src/main/java/com/apify/client/internal/Statuses.java @@ -1,9 +1,9 @@ -package com.apify.client; +package com.apify.client.internal; import java.util.Set; /** Run/build status helpers. Internal to the client. */ -final class Statuses { +public final class Statuses { /** Terminal run/build statuses: a resource in any of these is finished and will not change. */ private static final Set TERMINAL = Set.of("SUCCEEDED", "FAILED", "ABORTED", "TIMED-OUT"); @@ -11,7 +11,7 @@ final class Statuses { private Statuses() {} /** Reports whether the status is a terminal (finished) run/build status. */ - static boolean isTerminal(String status) { + public static boolean isTerminal(String status) { return status != null && TERMINAL.contains(status); } } diff --git a/src/main/java/com/apify/client/internal/package-info.java b/src/main/java/com/apify/client/internal/package-info.java new file mode 100644 index 0000000..f8fe0e0 --- /dev/null +++ b/src/main/java/com/apify/client/internal/package-info.java @@ -0,0 +1,13 @@ +/** + * Internal implementation shared across the client's resource packages. + * + *

Not part of the public API. Classes here are {@code public} only because Java has no + * cross-package "friend" access below {@code public} without the module system (JPMS), which this + * project does not adopt (it would add module-path/{@code opens} complexity disproportionate to a + * single-package split). This is the same convention used by e.g. OkHttp's {@code okhttp3.internal} + * package: the name is the contract. Anything under {@code com.apify.client.internal} may change, + * move, or be removed in any release without notice, including patch releases. Application code + * must never import from this package; obtain functionality exclusively through {@link + * com.apify.client.ApifyClient} and the resource clients it returns. + */ +package com.apify.client.internal; diff --git a/src/main/java/com/apify/client/GetRecordOptions.java b/src/main/java/com/apify/client/keyvalue/GetRecordOptions.java similarity index 89% rename from src/main/java/com/apify/client/GetRecordOptions.java rename to src/main/java/com/apify/client/keyvalue/GetRecordOptions.java index 70e40fe..7e9586b 100644 --- a/src/main/java/com/apify/client/GetRecordOptions.java +++ b/src/main/java/com/apify/client/keyvalue/GetRecordOptions.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.keyvalue; + +import com.apify.client.internal.QueryParams; /** Configures {@link KeyValueStoreClient#getRecord(String, GetRecordOptions)}. */ public final class GetRecordOptions { diff --git a/src/main/java/com/apify/client/KeyValueStore.java b/src/main/java/com/apify/client/keyvalue/KeyValueStore.java similarity index 91% rename from src/main/java/com/apify/client/KeyValueStore.java rename to src/main/java/com/apify/client/keyvalue/KeyValueStore.java index d7ce8d4..e3202ea 100644 --- a/src/main/java/com/apify/client/KeyValueStore.java +++ b/src/main/java/com/apify/client/keyvalue/KeyValueStore.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.keyvalue; +import com.apify.client.ApifyResource; import java.time.Instant; /** A key-value store holds arbitrary data records. */ diff --git a/src/main/java/com/apify/client/KeyValueStoreClient.java b/src/main/java/com/apify/client/keyvalue/KeyValueStoreClient.java similarity index 87% rename from src/main/java/com/apify/client/KeyValueStoreClient.java rename to src/main/java/com/apify/client/keyvalue/KeyValueStoreClient.java index 85c22fa..0be5404 100644 --- a/src/main/java/com/apify/client/KeyValueStoreClient.java +++ b/src/main/java/com/apify/client/keyvalue/KeyValueStoreClient.java @@ -1,5 +1,12 @@ -package com.apify.client; +package com.apify.client.keyvalue; +import com.apify.client.dataset.DatasetClient; +import com.apify.client.internal.ApiResponse; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.Json; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.Signatures; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; @@ -7,33 +14,42 @@ /** A client for a specific key-value store (and run-nested variants). */ public final class KeyValueStoreClient { - private ResourceContext ctx; + private final ResourceContext ctx; - KeyValueStoreClient(HttpClientCore http, String baseUrl, String resourcePath, String id) { + public KeyValueStoreClient(HttpClientCore http, String baseUrl, String resourcePath, String id) { this.ctx = ResourceContext.single(http, baseUrl, resourcePath, id); } + /** + * As {@link #KeyValueStoreClient(HttpClientCore, String, String, String)}, additionally + * overriding the origin used for public, shareable URLs. Used by {@code + * ApifyClient.keyValueStore(String)} for top-level stores, which support a public-base-URL + * override; run-nested stores (via {@link #nested}) never do. Resolving the override here, before + * the field assignment, keeps {@code ctx} (and so this client) immutable from construction on + * rather than mutated after the fact. + */ + public KeyValueStoreClient( + HttpClientCore http, String baseUrl, String resourcePath, String id, String publicBaseUrl) { + ResourceContext base = ResourceContext.single(http, baseUrl, resourcePath, id); + this.ctx = publicBaseUrl != null ? base.withPublicOrigin(publicBaseUrl) : base; + } + private KeyValueStoreClient(ResourceContext ctx) { this.ctx = ctx; } /** Creates a client for a run's default key-value store (nested path only, no ID). */ - static KeyValueStoreClient nested(HttpClientCore http, String base, String subPath) { + public static KeyValueStoreClient nested(HttpClientCore http, String base, String subPath) { return nested(http, base, subPath, null); } /** As {@link #nested(HttpClientCore, String, String)} but inheriting parent query params. */ - static KeyValueStoreClient nested( + public static KeyValueStoreClient nested( HttpClientCore http, String base, String subPath, QueryParams inherited) { return new KeyValueStoreClient( ResourceContext.collection(http, base, subPath).seedParams(inherited)); } - KeyValueStoreClient withPublicBase(String publicBaseUrl) { - this.ctx = ctx.withPublicOrigin(publicBaseUrl); - return this; - } - /** Fetches the store metadata, or empty if it does not exist. */ public Optional get() { return ctx.getResource("", new QueryParams(), KeyValueStore.class); diff --git a/src/main/java/com/apify/client/KeyValueStoreCollectionClient.java b/src/main/java/com/apify/client/keyvalue/KeyValueStoreCollectionClient.java similarity index 80% rename from src/main/java/com/apify/client/KeyValueStoreCollectionClient.java rename to src/main/java/com/apify/client/keyvalue/KeyValueStoreCollectionClient.java index 7066cb0..7a18df0 100644 --- a/src/main/java/com/apify/client/KeyValueStoreCollectionClient.java +++ b/src/main/java/com/apify/client/keyvalue/KeyValueStoreCollectionClient.java @@ -1,13 +1,19 @@ -package com.apify.client; +package com.apify.client.keyvalue; +import com.apify.client.PaginationList; +import com.apify.client.StorageListOptions; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.util.Iterator; /** A client for the key-value store collection ({@code GET/POST /v2/key-value-stores}). */ public final class KeyValueStoreCollectionClient { private final ResourceContext ctx; - KeyValueStoreCollectionClient(HttpClientCore http, String baseUrl) { - this.ctx = ResourceContext.collection(http, baseUrl, "key-value-stores"); + public KeyValueStoreCollectionClient(HttpClientCore http, String baseUrl) { + this.ctx = ResourceContext.collection(http, baseUrl, ResourcePaths.KEY_VALUE_STORES); } /** Lists key-value stores. */ diff --git a/src/main/java/com/apify/client/KeyValueStoreKey.java b/src/main/java/com/apify/client/keyvalue/KeyValueStoreKey.java similarity index 80% rename from src/main/java/com/apify/client/KeyValueStoreKey.java rename to src/main/java/com/apify/client/keyvalue/KeyValueStoreKey.java index cf0aa7f..6b57461 100644 --- a/src/main/java/com/apify/client/KeyValueStoreKey.java +++ b/src/main/java/com/apify/client/keyvalue/KeyValueStoreKey.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.keyvalue; + +import com.apify.client.ApifyResource; /** A single key listed from a key-value store. */ public final class KeyValueStoreKey extends ApifyResource { diff --git a/src/main/java/com/apify/client/KeyValueStoreKeysPage.java b/src/main/java/com/apify/client/keyvalue/KeyValueStoreKeysPage.java similarity index 96% rename from src/main/java/com/apify/client/KeyValueStoreKeysPage.java rename to src/main/java/com/apify/client/keyvalue/KeyValueStoreKeysPage.java index 3a59e8f..7ed9ab1 100644 --- a/src/main/java/com/apify/client/KeyValueStoreKeysPage.java +++ b/src/main/java/com/apify/client/keyvalue/KeyValueStoreKeysPage.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.keyvalue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.Collections; diff --git a/src/main/java/com/apify/client/KeyValueStoreRecord.java b/src/main/java/com/apify/client/keyvalue/KeyValueStoreRecord.java similarity index 96% rename from src/main/java/com/apify/client/KeyValueStoreRecord.java rename to src/main/java/com/apify/client/keyvalue/KeyValueStoreRecord.java index a477227..f291f51 100644 --- a/src/main/java/com/apify/client/KeyValueStoreRecord.java +++ b/src/main/java/com/apify/client/keyvalue/KeyValueStoreRecord.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.keyvalue; /** * A single record retrieved from a key-value store. Its {@link #getValue() value} holds the raw diff --git a/src/main/java/com/apify/client/ListKeysOptions.java b/src/main/java/com/apify/client/keyvalue/ListKeysOptions.java similarity index 95% rename from src/main/java/com/apify/client/ListKeysOptions.java rename to src/main/java/com/apify/client/keyvalue/ListKeysOptions.java index ca2d1d7..550f64f 100644 --- a/src/main/java/com/apify/client/ListKeysOptions.java +++ b/src/main/java/com/apify/client/keyvalue/ListKeysOptions.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.keyvalue; + +import com.apify.client.internal.QueryParams; /** Configures {@link KeyValueStoreClient#listKeys(ListKeysOptions)}. */ public final class ListKeysOptions { diff --git a/src/main/java/com/apify/client/SetRecordOptions.java b/src/main/java/com/apify/client/keyvalue/SetRecordOptions.java similarity index 96% rename from src/main/java/com/apify/client/SetRecordOptions.java rename to src/main/java/com/apify/client/keyvalue/SetRecordOptions.java index 3820d43..8f3b493 100644 --- a/src/main/java/com/apify/client/SetRecordOptions.java +++ b/src/main/java/com/apify/client/keyvalue/SetRecordOptions.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.keyvalue; /** * Write options for {@link KeyValueStoreClient#setRecord(String, byte[], String, diff --git a/src/main/java/com/apify/client/LogClient.java b/src/main/java/com/apify/client/log/LogClient.java similarity index 85% rename from src/main/java/com/apify/client/LogClient.java rename to src/main/java/com/apify/client/log/LogClient.java index 2b2624f..a22bd25 100644 --- a/src/main/java/com/apify/client/LogClient.java +++ b/src/main/java/com/apify/client/log/LogClient.java @@ -1,5 +1,9 @@ -package com.apify.client; +package com.apify.client.log; +import com.apify.client.internal.ApiResponse; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import java.io.IOException; import java.io.InputStream; import java.net.http.HttpResponse; @@ -13,7 +17,7 @@ public final class LogClient { private final ResourceContext ctx; - LogClient(HttpClientCore http, String baseUrl, String resourcePath, String id) { + public LogClient(HttpClientCore http, String baseUrl, String resourcePath, String id) { this.ctx = ResourceContext.single(http, baseUrl, resourcePath, id); } @@ -22,12 +26,12 @@ private LogClient(ResourceContext ctx) { } /** Creates a log client for a run's or build's nested log endpoint (e.g. {@code .../log}). */ - static LogClient nested(HttpClientCore http, String base) { + public static LogClient nested(HttpClientCore http, String base) { return nested(http, base, null); } /** As {@link #nested(HttpClientCore, String)} but inheriting parent query params. */ - static LogClient nested(HttpClientCore http, String base, QueryParams inherited) { + public static LogClient nested(HttpClientCore http, String base, QueryParams inherited) { return new LogClient(ResourceContext.collection(http, base, "log").seedParams(inherited)); } diff --git a/src/main/java/com/apify/client/LogOptions.java b/src/main/java/com/apify/client/log/LogOptions.java similarity index 88% rename from src/main/java/com/apify/client/LogOptions.java rename to src/main/java/com/apify/client/log/LogOptions.java index f8762ff..1d4a94d 100644 --- a/src/main/java/com/apify/client/LogOptions.java +++ b/src/main/java/com/apify/client/log/LogOptions.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.log; + +import com.apify.client.internal.QueryParams; /** Configures log retrieval/streaming. */ public final class LogOptions { diff --git a/src/main/java/com/apify/client/StreamedLog.java b/src/main/java/com/apify/client/log/StreamedLog.java similarity index 98% rename from src/main/java/com/apify/client/StreamedLog.java rename to src/main/java/com/apify/client/log/StreamedLog.java index 4e23fba..9584a90 100644 --- a/src/main/java/com/apify/client/StreamedLog.java +++ b/src/main/java/com/apify/client/log/StreamedLog.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.log; +import com.apify.client.ApifyApiException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -69,7 +70,7 @@ public final class StreamedLog implements AutoCloseable { private volatile InputStream activeStream; private Thread streamingThread; - StreamedLog(LogClient logClient, Consumer destination, boolean fromStart) { + public StreamedLog(LogClient logClient, Consumer destination, boolean fromStart) { this.logClient = logClient; this.destination = destination; this.relevancyTimeLimit = fromStart ? null : Instant.now(); diff --git a/src/main/java/com/apify/client/StreamedLogOptions.java b/src/main/java/com/apify/client/log/StreamedLogOptions.java similarity index 89% rename from src/main/java/com/apify/client/StreamedLogOptions.java rename to src/main/java/com/apify/client/log/StreamedLogOptions.java index 8d1c8d0..16a02e1 100644 --- a/src/main/java/com/apify/client/StreamedLogOptions.java +++ b/src/main/java/com/apify/client/log/StreamedLogOptions.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.log; +import com.apify.client.run.RunClient; import java.util.function.Consumer; /** @@ -44,15 +45,15 @@ public StreamedLogOptions fromStart(boolean fromStart) { return this; } - Consumer destination() { + public Consumer destination() { return toLog; } - String prefixValue() { + public String prefixValue() { return prefix; } - boolean fromStartValue() { + public boolean fromStartValue() { return fromStart; } } diff --git a/src/main/java/com/apify/client/BatchAddRequestsOptions.java b/src/main/java/com/apify/client/requestqueue/BatchAddRequestsOptions.java similarity index 98% rename from src/main/java/com/apify/client/BatchAddRequestsOptions.java rename to src/main/java/com/apify/client/requestqueue/BatchAddRequestsOptions.java index f11c2a2..354ca33 100644 --- a/src/main/java/com/apify/client/BatchAddRequestsOptions.java +++ b/src/main/java/com/apify/client/requestqueue/BatchAddRequestsOptions.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.requestqueue; /** * Tuning options for {@link RequestQueueClient#batchAddRequests(java.util.List, boolean, diff --git a/src/main/java/com/apify/client/BatchAddResult.java b/src/main/java/com/apify/client/requestqueue/BatchAddResult.java similarity index 97% rename from src/main/java/com/apify/client/BatchAddResult.java rename to src/main/java/com/apify/client/requestqueue/BatchAddResult.java index 2e3de5b..98eafd4 100644 --- a/src/main/java/com/apify/client/BatchAddResult.java +++ b/src/main/java/com/apify/client/requestqueue/BatchAddResult.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.requestqueue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import java.util.ArrayList; diff --git a/src/main/java/com/apify/client/ListRequestsOptions.java b/src/main/java/com/apify/client/requestqueue/ListRequestsOptions.java similarity index 96% rename from src/main/java/com/apify/client/ListRequestsOptions.java rename to src/main/java/com/apify/client/requestqueue/ListRequestsOptions.java index 131d9fd..fa7dad8 100644 --- a/src/main/java/com/apify/client/ListRequestsOptions.java +++ b/src/main/java/com/apify/client/requestqueue/ListRequestsOptions.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.requestqueue; +import com.apify.client.internal.QueryParams; import java.util.List; /** Configures {@link RequestQueueClient#listRequests(ListRequestsOptions)}. */ diff --git a/src/main/java/com/apify/client/RequestQueue.java b/src/main/java/com/apify/client/requestqueue/RequestQueue.java similarity index 92% rename from src/main/java/com/apify/client/RequestQueue.java rename to src/main/java/com/apify/client/requestqueue/RequestQueue.java index 6d5a3f5..d52c1eb 100644 --- a/src/main/java/com/apify/client/RequestQueue.java +++ b/src/main/java/com/apify/client/requestqueue/RequestQueue.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.requestqueue; +import com.apify.client.ApifyResource; import java.time.Instant; /** A request queue stores URLs to be crawled. */ diff --git a/src/main/java/com/apify/client/RequestQueueClient.java b/src/main/java/com/apify/client/requestqueue/RequestQueueClient.java similarity index 96% rename from src/main/java/com/apify/client/RequestQueueClient.java rename to src/main/java/com/apify/client/requestqueue/RequestQueueClient.java index c2d4f83..7c5ae66 100644 --- a/src/main/java/com/apify/client/RequestQueueClient.java +++ b/src/main/java/com/apify/client/requestqueue/RequestQueueClient.java @@ -1,5 +1,12 @@ -package com.apify.client; - +package com.apify.client.requestqueue; + +import com.apify.client.ApifyApiException; +import com.apify.client.ApifyTransportException; +import com.apify.client.internal.ApiResponse; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.Json; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; @@ -39,7 +46,7 @@ public final class RequestQueueClient { private final ResourceContext ctx; private final String clientKey; - RequestQueueClient(HttpClientCore http, String baseUrl, String resourcePath, String id) { + public RequestQueueClient(HttpClientCore http, String baseUrl, String resourcePath, String id) { this(http, ResourceContext.single(http, baseUrl, resourcePath, id), null); } @@ -50,12 +57,12 @@ private RequestQueueClient(HttpClientCore http, ResourceContext ctx, String clie } /** Creates a client for a run's default request queue (nested path only, no ID). */ - static RequestQueueClient nested(HttpClientCore http, String base, String subPath) { + public static RequestQueueClient nested(HttpClientCore http, String base, String subPath) { return nested(http, base, subPath, null); } /** As {@link #nested(HttpClientCore, String, String)} but inheriting parent query params. */ - static RequestQueueClient nested( + public static RequestQueueClient nested( HttpClientCore http, String base, String subPath, QueryParams inherited) { return new RequestQueueClient( http, ResourceContext.collection(http, base, subPath).seedParams(inherited), null); @@ -243,7 +250,7 @@ private static BatchAddResult awaitResult(Future future) { throw new IllegalStateException("batch add request failed", cause); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new HttpClientCore.TransportException(e); + throw new ApifyTransportException(e); } } @@ -335,7 +342,7 @@ private static void sleepBackoff(int attempt, long minDelayMillis) { Thread.sleep(delayMillis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new HttpClientCore.TransportException(e); + throw new ApifyTransportException(e); } } diff --git a/src/main/java/com/apify/client/RequestQueueCollectionClient.java b/src/main/java/com/apify/client/requestqueue/RequestQueueCollectionClient.java similarity index 76% rename from src/main/java/com/apify/client/RequestQueueCollectionClient.java rename to src/main/java/com/apify/client/requestqueue/RequestQueueCollectionClient.java index b0ca489..391343b 100644 --- a/src/main/java/com/apify/client/RequestQueueCollectionClient.java +++ b/src/main/java/com/apify/client/requestqueue/RequestQueueCollectionClient.java @@ -1,13 +1,19 @@ -package com.apify.client; +package com.apify.client.requestqueue; +import com.apify.client.PaginationList; +import com.apify.client.StorageListOptions; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.util.Iterator; /** A client for the request queue collection ({@code GET/POST /v2/request-queues}). */ public final class RequestQueueCollectionClient { private final ResourceContext ctx; - RequestQueueCollectionClient(HttpClientCore http, String baseUrl) { - this.ctx = ResourceContext.collection(http, baseUrl, "request-queues"); + public RequestQueueCollectionClient(HttpClientCore http, String baseUrl) { + this.ctx = ResourceContext.collection(http, baseUrl, ResourcePaths.REQUEST_QUEUES); } /** Lists request queues. */ diff --git a/src/main/java/com/apify/client/RequestQueueHead.java b/src/main/java/com/apify/client/requestqueue/RequestQueueHead.java similarity index 89% rename from src/main/java/com/apify/client/RequestQueueHead.java rename to src/main/java/com/apify/client/requestqueue/RequestQueueHead.java index 80cb015..21e1977 100644 --- a/src/main/java/com/apify/client/RequestQueueHead.java +++ b/src/main/java/com/apify/client/requestqueue/RequestQueueHead.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.requestqueue; +import com.apify.client.ApifyResource; import java.util.Collections; import java.util.List; diff --git a/src/main/java/com/apify/client/RequestQueueOperationInfo.java b/src/main/java/com/apify/client/requestqueue/RequestQueueOperationInfo.java similarity index 95% rename from src/main/java/com/apify/client/RequestQueueOperationInfo.java rename to src/main/java/com/apify/client/requestqueue/RequestQueueOperationInfo.java index dc56c22..8901eab 100644 --- a/src/main/java/com/apify/client/RequestQueueOperationInfo.java +++ b/src/main/java/com/apify/client/requestqueue/RequestQueueOperationInfo.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.requestqueue; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; diff --git a/src/main/java/com/apify/client/RequestQueueRequest.java b/src/main/java/com/apify/client/requestqueue/RequestQueueRequest.java similarity index 95% rename from src/main/java/com/apify/client/RequestQueueRequest.java rename to src/main/java/com/apify/client/requestqueue/RequestQueueRequest.java index 6379bf2..b944272 100644 --- a/src/main/java/com/apify/client/RequestQueueRequest.java +++ b/src/main/java/com/apify/client/requestqueue/RequestQueueRequest.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.requestqueue; +import com.apify.client.ApifyResource; import com.fasterxml.jackson.databind.JsonNode; /** diff --git a/src/main/java/com/apify/client/ActorRun.java b/src/main/java/com/apify/client/run/ActorRun.java similarity index 95% rename from src/main/java/com/apify/client/ActorRun.java rename to src/main/java/com/apify/client/run/ActorRun.java index 31e635b..434ffb1 100644 --- a/src/main/java/com/apify/client/ActorRun.java +++ b/src/main/java/com/apify/client/run/ActorRun.java @@ -1,5 +1,7 @@ -package com.apify.client; +package com.apify.client.run; +import com.apify.client.ApifyResource; +import com.apify.client.internal.Statuses; import java.time.Instant; /** A single execution of an Actor. */ diff --git a/src/main/java/com/apify/client/LastRunOptions.java b/src/main/java/com/apify/client/run/LastRunOptions.java similarity index 90% rename from src/main/java/com/apify/client/LastRunOptions.java rename to src/main/java/com/apify/client/run/LastRunOptions.java index 0a582c1..2a1829e 100644 --- a/src/main/java/com/apify/client/LastRunOptions.java +++ b/src/main/java/com/apify/client/run/LastRunOptions.java @@ -1,4 +1,7 @@ -package com.apify.client; +package com.apify.client.run; + +import com.apify.client.actor.ActorClient; +import com.apify.client.task.TaskClient; /** * Filters which "last" run the {@link ActorClient#lastRun}/{@link TaskClient#lastRun} accessors diff --git a/src/main/java/com/apify/client/MetamorphOptions.java b/src/main/java/com/apify/client/run/MetamorphOptions.java similarity index 89% rename from src/main/java/com/apify/client/MetamorphOptions.java rename to src/main/java/com/apify/client/run/MetamorphOptions.java index 5e2230b..7b18bab 100644 --- a/src/main/java/com/apify/client/MetamorphOptions.java +++ b/src/main/java/com/apify/client/run/MetamorphOptions.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.run; + +import com.apify.client.internal.ResourceContext; /** Configures {@link RunClient#metamorph}. */ public final class MetamorphOptions { diff --git a/src/main/java/com/apify/client/RunChargeOptions.java b/src/main/java/com/apify/client/run/RunChargeOptions.java similarity index 97% rename from src/main/java/com/apify/client/RunChargeOptions.java rename to src/main/java/com/apify/client/run/RunChargeOptions.java index f22a198..7a3f64a 100644 --- a/src/main/java/com/apify/client/RunChargeOptions.java +++ b/src/main/java/com/apify/client/run/RunChargeOptions.java @@ -1,4 +1,4 @@ -package com.apify.client; +package com.apify.client.run; /** Configures {@link RunClient#charge(RunChargeOptions)}. */ public final class RunChargeOptions { diff --git a/src/main/java/com/apify/client/RunClient.java b/src/main/java/com/apify/client/run/RunClient.java similarity index 93% rename from src/main/java/com/apify/client/RunClient.java rename to src/main/java/com/apify/client/run/RunClient.java index 68b3bd2..c7e81fa 100644 --- a/src/main/java/com/apify/client/RunClient.java +++ b/src/main/java/com/apify/client/run/RunClient.java @@ -1,5 +1,17 @@ -package com.apify.client; +package com.apify.client.run; +import com.apify.client.ApifyClient; +import com.apify.client.actor.Actor; +import com.apify.client.dataset.DatasetClient; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.Json; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.keyvalue.KeyValueStoreClient; +import com.apify.client.log.LogClient; +import com.apify.client.log.StreamedLog; +import com.apify.client.log.StreamedLogOptions; +import com.apify.client.requestqueue.RequestQueueClient; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; @@ -22,7 +34,8 @@ public final class RunClient { private final ResourceContext ctx; private final String id; - RunClient(ApifyClient root, HttpClientCore http, String baseUrl, String resourcePath, String id) { + public RunClient( + ApifyClient root, HttpClientCore http, String baseUrl, String resourcePath, String id) { this.root = root; this.ctx = ResourceContext.single(http, baseUrl, resourcePath, id); this.id = id; @@ -32,7 +45,7 @@ public final class RunClient { * Pins the {@code status} and/or {@code origin} query parameters inherited by all calls on this * client (used by the last-run accessors). Empty values are skipped. */ - void setLastRunParams(LastRunOptions options) { + public void setLastRunParams(LastRunOptions options) { if (options.statusValue() != null && !options.statusValue().isEmpty()) { ctx.baseParams.addRaw("status", options.statusValue()); } @@ -184,7 +197,7 @@ public LogClient log() { } /** Logger name used by the default log-redirection destination. */ - private static final String REDIRECT_LOGGER_NAME = "com.apify.client.ActorRunLog"; + private static final String REDIRECT_LOGGER_NAME = "com.apify.client.run.ActorRunLog"; /** * Returns a {@link StreamedLog} that redirects this run's live log to a default per-run logger. diff --git a/src/main/java/com/apify/client/RunCollectionClient.java b/src/main/java/com/apify/client/run/RunCollectionClient.java similarity index 85% rename from src/main/java/com/apify/client/RunCollectionClient.java rename to src/main/java/com/apify/client/run/RunCollectionClient.java index 101a5a2..0093d4f 100644 --- a/src/main/java/com/apify/client/RunCollectionClient.java +++ b/src/main/java/com/apify/client/run/RunCollectionClient.java @@ -1,5 +1,10 @@ -package com.apify.client; +package com.apify.client.run; +import com.apify.client.ListOptions; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import java.util.Iterator; /** @@ -10,7 +15,7 @@ public final class RunCollectionClient { private final ResourceContext ctx; - RunCollectionClient(HttpClientCore http, String baseUrl, String resourcePath) { + public RunCollectionClient(HttpClientCore http, String baseUrl, String resourcePath) { this.ctx = ResourceContext.collection(http, baseUrl, resourcePath); } diff --git a/src/main/java/com/apify/client/RunListOptions.java b/src/main/java/com/apify/client/run/RunListOptions.java similarity index 92% rename from src/main/java/com/apify/client/RunListOptions.java rename to src/main/java/com/apify/client/run/RunListOptions.java index 1322548..f77dee4 100644 --- a/src/main/java/com/apify/client/RunListOptions.java +++ b/src/main/java/com/apify/client/run/RunListOptions.java @@ -1,5 +1,7 @@ -package com.apify.client; +package com.apify.client.run; +import com.apify.client.ListOptions; +import com.apify.client.internal.QueryParams; import java.util.List; /** diff --git a/src/main/java/com/apify/client/RunResurrectOptions.java b/src/main/java/com/apify/client/run/RunResurrectOptions.java similarity index 95% rename from src/main/java/com/apify/client/RunResurrectOptions.java rename to src/main/java/com/apify/client/run/RunResurrectOptions.java index a26555f..7138746 100644 --- a/src/main/java/com/apify/client/RunResurrectOptions.java +++ b/src/main/java/com/apify/client/run/RunResurrectOptions.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.run; + +import com.apify.client.internal.QueryParams; /** Configures {@link RunClient#resurrect(RunResurrectOptions)}. */ public final class RunResurrectOptions { diff --git a/src/main/java/com/apify/client/Schedule.java b/src/main/java/com/apify/client/schedule/Schedule.java similarity index 91% rename from src/main/java/com/apify/client/Schedule.java rename to src/main/java/com/apify/client/schedule/Schedule.java index c65f5e3..355ac02 100644 --- a/src/main/java/com/apify/client/Schedule.java +++ b/src/main/java/com/apify/client/schedule/Schedule.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.schedule; + +import com.apify.client.ApifyResource; /** A schedule automatically starts Actor or task runs at specified times. */ public final class Schedule extends ApifyResource { diff --git a/src/main/java/com/apify/client/ScheduleClient.java b/src/main/java/com/apify/client/schedule/ScheduleClient.java similarity index 69% rename from src/main/java/com/apify/client/ScheduleClient.java rename to src/main/java/com/apify/client/schedule/ScheduleClient.java index 38984f9..aeefed8 100644 --- a/src/main/java/com/apify/client/ScheduleClient.java +++ b/src/main/java/com/apify/client/schedule/ScheduleClient.java @@ -1,5 +1,10 @@ -package com.apify.client; +package com.apify.client.schedule; +import com.apify.client.internal.ApiResponse; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.nio.charset.StandardCharsets; import java.util.Optional; @@ -7,8 +12,8 @@ public final class ScheduleClient { private final ResourceContext ctx; - ScheduleClient(HttpClientCore http, String baseUrl, String id) { - this.ctx = ResourceContext.single(http, baseUrl, "schedules", id); + public ScheduleClient(HttpClientCore http, String baseUrl, String id) { + this.ctx = ResourceContext.single(http, baseUrl, ResourcePaths.SCHEDULES, id); } /** Fetches the schedule, or empty if it does not exist. */ diff --git a/src/main/java/com/apify/client/ScheduleCollectionClient.java b/src/main/java/com/apify/client/schedule/ScheduleCollectionClient.java similarity index 75% rename from src/main/java/com/apify/client/ScheduleCollectionClient.java rename to src/main/java/com/apify/client/schedule/ScheduleCollectionClient.java index bcd3d61..e6c85d0 100644 --- a/src/main/java/com/apify/client/ScheduleCollectionClient.java +++ b/src/main/java/com/apify/client/schedule/ScheduleCollectionClient.java @@ -1,13 +1,19 @@ -package com.apify.client; +package com.apify.client.schedule; +import com.apify.client.ListOptions; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.util.Iterator; /** A client for the schedule collection ({@code GET/POST /v2/schedules}). */ public final class ScheduleCollectionClient { private final ResourceContext ctx; - ScheduleCollectionClient(HttpClientCore http, String baseUrl) { - this.ctx = ResourceContext.collection(http, baseUrl, "schedules"); + public ScheduleCollectionClient(HttpClientCore http, String baseUrl) { + this.ctx = ResourceContext.collection(http, baseUrl, ResourcePaths.SCHEDULES); } /** Lists the account's schedules. */ diff --git a/src/main/java/com/apify/client/ActorStoreListItem.java b/src/main/java/com/apify/client/store/ActorStoreListItem.java similarity index 88% rename from src/main/java/com/apify/client/ActorStoreListItem.java rename to src/main/java/com/apify/client/store/ActorStoreListItem.java index d0891f0..8057c01 100644 --- a/src/main/java/com/apify/client/ActorStoreListItem.java +++ b/src/main/java/com/apify/client/store/ActorStoreListItem.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.store; + +import com.apify.client.ApifyResource; /** An Actor as listed in the Apify Store. */ public final class ActorStoreListItem extends ApifyResource { diff --git a/src/main/java/com/apify/client/StoreCollectionClient.java b/src/main/java/com/apify/client/store/StoreCollectionClient.java similarity index 76% rename from src/main/java/com/apify/client/StoreCollectionClient.java rename to src/main/java/com/apify/client/store/StoreCollectionClient.java index 56cb5d5..f1af86f 100644 --- a/src/main/java/com/apify/client/StoreCollectionClient.java +++ b/src/main/java/com/apify/client/store/StoreCollectionClient.java @@ -1,13 +1,18 @@ -package com.apify.client; +package com.apify.client.store; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.util.Iterator; /** A client for browsing the Apify Store ({@code GET /v2/store}). */ public final class StoreCollectionClient { private final ResourceContext ctx; - StoreCollectionClient(HttpClientCore http, String baseUrl) { - this.ctx = ResourceContext.collection(http, baseUrl, "store"); + public StoreCollectionClient(HttpClientCore http, String baseUrl) { + this.ctx = ResourceContext.collection(http, baseUrl, ResourcePaths.STORE); } /** Returns a single page of Store Actors matching the options. */ diff --git a/src/main/java/com/apify/client/StoreListOptions.java b/src/main/java/com/apify/client/store/StoreListOptions.java similarity index 93% rename from src/main/java/com/apify/client/StoreListOptions.java rename to src/main/java/com/apify/client/store/StoreListOptions.java index 9187464..3fb46f9 100644 --- a/src/main/java/com/apify/client/StoreListOptions.java +++ b/src/main/java/com/apify/client/store/StoreListOptions.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.store; + +import com.apify.client.internal.QueryParams; /** Options for listing/iterating the Apify Store ({@code GET /v2/store}). */ public final class StoreListOptions { @@ -79,15 +81,15 @@ public StoreListOptions responseFormat(String responseFormat) { return this; } - Long limitValue() { + public Long limitValue() { return limit; } - Long offsetValue() { + public Long offsetValue() { return offset; } - void apply(QueryParams q) { + public void apply(QueryParams q) { q.addLong("offset", offset).addLong("limit", limit); applyFilters(q); } @@ -95,7 +97,7 @@ void apply(QueryParams q) { /** * Applies every filter except {@code offset}/{@code limit}, which the iterator drives per page. */ - void applyFilters(QueryParams q) { + public void applyFilters(QueryParams q) { q.addString("search", search) .addString("sortBy", sortBy) .addString("category", category) diff --git a/src/main/java/com/apify/client/Task.java b/src/main/java/com/apify/client/task/Task.java similarity index 93% rename from src/main/java/com/apify/client/Task.java rename to src/main/java/com/apify/client/task/Task.java index a8618ee..d3d7e4b 100644 --- a/src/main/java/com/apify/client/Task.java +++ b/src/main/java/com/apify/client/task/Task.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.task; +import com.apify.client.ApifyResource; import java.time.Instant; /** A pre-configured Actor run (an Actor task). */ diff --git a/src/main/java/com/apify/client/TaskClient.java b/src/main/java/com/apify/client/task/TaskClient.java similarity index 82% rename from src/main/java/com/apify/client/TaskClient.java rename to src/main/java/com/apify/client/task/TaskClient.java index 5849be0..7bcbc1d 100644 --- a/src/main/java/com/apify/client/TaskClient.java +++ b/src/main/java/com/apify/client/task/TaskClient.java @@ -1,5 +1,17 @@ -package com.apify.client; +package com.apify.client.task; +import com.apify.client.ApifyClient; +import com.apify.client.internal.ApiResponse; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.Json; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; +import com.apify.client.run.ActorRun; +import com.apify.client.run.LastRunOptions; +import com.apify.client.run.RunClient; +import com.apify.client.run.RunCollectionClient; +import com.apify.client.webhook.NestedWebhookCollectionClient; import com.fasterxml.jackson.databind.JsonNode; import java.util.Optional; @@ -14,10 +26,10 @@ public final class TaskClient { private final HttpClientCore http; private final ResourceContext ctx; - TaskClient(ApifyClient root, HttpClientCore http, String baseUrl, String id) { + public TaskClient(ApifyClient root, HttpClientCore http, String baseUrl, String id) { this.root = root; this.http = http; - this.ctx = ResourceContext.single(http, baseUrl, "actor-tasks", id); + this.ctx = ResourceContext.single(http, baseUrl, ResourcePaths.ACTOR_TASKS, id); } /** Fetches the task object, or empty if it does not exist. */ diff --git a/src/main/java/com/apify/client/TaskCollectionClient.java b/src/main/java/com/apify/client/task/TaskCollectionClient.java similarity index 74% rename from src/main/java/com/apify/client/TaskCollectionClient.java rename to src/main/java/com/apify/client/task/TaskCollectionClient.java index 392b9c5..7de14b6 100644 --- a/src/main/java/com/apify/client/TaskCollectionClient.java +++ b/src/main/java/com/apify/client/task/TaskCollectionClient.java @@ -1,13 +1,19 @@ -package com.apify.client; +package com.apify.client.task; +import com.apify.client.ListOptions; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.util.Iterator; /** A client for the Actor task collection ({@code GET/POST /v2/actor-tasks}). */ public final class TaskCollectionClient { private final ResourceContext ctx; - TaskCollectionClient(HttpClientCore http, String baseUrl) { - this.ctx = ResourceContext.collection(http, baseUrl, "actor-tasks"); + public TaskCollectionClient(HttpClientCore http, String baseUrl) { + this.ctx = ResourceContext.collection(http, baseUrl, ResourcePaths.ACTOR_TASKS); } /** Lists the account's tasks. */ diff --git a/src/main/java/com/apify/client/TaskStartOptions.java b/src/main/java/com/apify/client/task/TaskStartOptions.java similarity index 95% rename from src/main/java/com/apify/client/TaskStartOptions.java rename to src/main/java/com/apify/client/task/TaskStartOptions.java index acc12d2..665f7e6 100644 --- a/src/main/java/com/apify/client/TaskStartOptions.java +++ b/src/main/java/com/apify/client/task/TaskStartOptions.java @@ -1,5 +1,7 @@ -package com.apify.client; +package com.apify.client.task; +import com.apify.client.actor.ActorStartOptions; +import com.apify.client.internal.QueryParams; import java.util.List; /** diff --git a/src/main/java/com/apify/client/User.java b/src/main/java/com/apify/client/user/User.java similarity index 84% rename from src/main/java/com/apify/client/User.java rename to src/main/java/com/apify/client/user/User.java index d5eb8ca..c429283 100644 --- a/src/main/java/com/apify/client/User.java +++ b/src/main/java/com/apify/client/user/User.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.user; + +import com.apify.client.ApifyResource; /** * An Apify user account. Private account details for {@code me} are available via {@link diff --git a/src/main/java/com/apify/client/UserClient.java b/src/main/java/com/apify/client/user/UserClient.java similarity index 86% rename from src/main/java/com/apify/client/UserClient.java rename to src/main/java/com/apify/client/user/UserClient.java index 9d5b090..80fe2c8 100644 --- a/src/main/java/com/apify/client/UserClient.java +++ b/src/main/java/com/apify/client/user/UserClient.java @@ -1,5 +1,10 @@ -package com.apify.client; +package com.apify.client.user; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.Json; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import com.fasterxml.jackson.databind.JsonNode; import java.util.Optional; @@ -17,9 +22,9 @@ public final class UserClient { private final ResourceContext ctx; private final boolean isMe; - UserClient(HttpClientCore http, String baseUrl, String id) { + public UserClient(HttpClientCore http, String baseUrl, String id) { this.http = http; - this.ctx = ResourceContext.single(http, baseUrl, "users", id); + this.ctx = ResourceContext.single(http, baseUrl, ResourcePaths.USERS, id); this.isMe = ME.equals(id); } diff --git a/src/main/java/com/apify/client/AbstractWebhookCollectionClient.java b/src/main/java/com/apify/client/webhook/AbstractWebhookCollectionClient.java similarity index 78% rename from src/main/java/com/apify/client/AbstractWebhookCollectionClient.java rename to src/main/java/com/apify/client/webhook/AbstractWebhookCollectionClient.java index 0c88298..62e2a3e 100644 --- a/src/main/java/com/apify/client/AbstractWebhookCollectionClient.java +++ b/src/main/java/com/apify/client/webhook/AbstractWebhookCollectionClient.java @@ -1,5 +1,11 @@ -package com.apify.client; +package com.apify.client.webhook; +import com.apify.client.ListOptions; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.util.Iterator; /** @@ -12,7 +18,7 @@ abstract class AbstractWebhookCollectionClient { final ResourceContext ctx; AbstractWebhookCollectionClient(HttpClientCore http, String baseUrl) { - this.ctx = ResourceContext.collection(http, baseUrl, "webhooks"); + this.ctx = ResourceContext.collection(http, baseUrl, ResourcePaths.WEBHOOKS); } /** Lists webhooks. */ diff --git a/src/main/java/com/apify/client/NestedWebhookCollectionClient.java b/src/main/java/com/apify/client/webhook/NestedWebhookCollectionClient.java similarity index 76% rename from src/main/java/com/apify/client/NestedWebhookCollectionClient.java rename to src/main/java/com/apify/client/webhook/NestedWebhookCollectionClient.java index e3f4100..19b699a 100644 --- a/src/main/java/com/apify/client/NestedWebhookCollectionClient.java +++ b/src/main/java/com/apify/client/webhook/NestedWebhookCollectionClient.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.webhook; + +import com.apify.client.internal.HttpClientCore; /** * A read-only client for the webhooks nested under an Actor ({@code GET /v2/actors/{id}/webhooks}) @@ -8,7 +10,7 @@ * exposed here. */ public final class NestedWebhookCollectionClient extends AbstractWebhookCollectionClient { - NestedWebhookCollectionClient(HttpClientCore http, String baseUrl) { + public NestedWebhookCollectionClient(HttpClientCore http, String baseUrl) { super(http, baseUrl); } } diff --git a/src/main/java/com/apify/client/Webhook.java b/src/main/java/com/apify/client/webhook/Webhook.java similarity index 90% rename from src/main/java/com/apify/client/Webhook.java rename to src/main/java/com/apify/client/webhook/Webhook.java index 0222c3e..fc61dd7 100644 --- a/src/main/java/com/apify/client/Webhook.java +++ b/src/main/java/com/apify/client/webhook/Webhook.java @@ -1,5 +1,6 @@ -package com.apify.client; +package com.apify.client.webhook; +import com.apify.client.ApifyResource; import java.util.Collections; import java.util.List; diff --git a/src/main/java/com/apify/client/WebhookClient.java b/src/main/java/com/apify/client/webhook/WebhookClient.java similarity index 74% rename from src/main/java/com/apify/client/WebhookClient.java rename to src/main/java/com/apify/client/webhook/WebhookClient.java index 1d5a37c..c6d3566 100644 --- a/src/main/java/com/apify/client/WebhookClient.java +++ b/src/main/java/com/apify/client/webhook/WebhookClient.java @@ -1,5 +1,9 @@ -package com.apify.client; +package com.apify.client.webhook; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; import java.util.Optional; /** A client for a specific webhook ({@code /v2/webhooks/{webhookId}}). */ @@ -7,9 +11,9 @@ public final class WebhookClient { private final HttpClientCore http; private final ResourceContext ctx; - WebhookClient(HttpClientCore http, String baseUrl, String id) { + public WebhookClient(HttpClientCore http, String baseUrl, String id) { this.http = http; - this.ctx = ResourceContext.single(http, baseUrl, "webhooks", id); + this.ctx = ResourceContext.single(http, baseUrl, ResourcePaths.WEBHOOKS, id); } /** Fetches the webhook, or empty if it does not exist. */ diff --git a/src/main/java/com/apify/client/WebhookCollectionClient.java b/src/main/java/com/apify/client/webhook/WebhookCollectionClient.java similarity index 74% rename from src/main/java/com/apify/client/WebhookCollectionClient.java rename to src/main/java/com/apify/client/webhook/WebhookCollectionClient.java index c5669e9..1d9f043 100644 --- a/src/main/java/com/apify/client/WebhookCollectionClient.java +++ b/src/main/java/com/apify/client/webhook/WebhookCollectionClient.java @@ -1,4 +1,7 @@ -package com.apify.client; +package com.apify.client.webhook; + +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; /** * A client for the account-wide webhook collection ({@code GET/POST /v2/webhooks}), supporting both @@ -6,7 +9,7 @@ * NestedWebhookCollectionClient} instead. */ public final class WebhookCollectionClient extends AbstractWebhookCollectionClient { - WebhookCollectionClient(HttpClientCore http, String baseUrl) { + public WebhookCollectionClient(HttpClientCore http, String baseUrl) { super(http, baseUrl); } diff --git a/src/main/java/com/apify/client/WebhookDispatch.java b/src/main/java/com/apify/client/webhook/WebhookDispatch.java similarity index 82% rename from src/main/java/com/apify/client/WebhookDispatch.java rename to src/main/java/com/apify/client/webhook/WebhookDispatch.java index 76ed263..e8bdf3b 100644 --- a/src/main/java/com/apify/client/WebhookDispatch.java +++ b/src/main/java/com/apify/client/webhook/WebhookDispatch.java @@ -1,4 +1,6 @@ -package com.apify.client; +package com.apify.client.webhook; + +import com.apify.client.ApifyResource; /** A single invocation of a webhook. */ public final class WebhookDispatch extends ApifyResource { diff --git a/src/main/java/com/apify/client/webhook/WebhookDispatchClient.java b/src/main/java/com/apify/client/webhook/WebhookDispatchClient.java new file mode 100644 index 0000000..9779e67 --- /dev/null +++ b/src/main/java/com/apify/client/webhook/WebhookDispatchClient.java @@ -0,0 +1,21 @@ +package com.apify.client.webhook; + +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; +import com.apify.client.internal.ResourcePaths; +import java.util.Optional; + +/** A client for a specific webhook dispatch ({@code /v2/webhook-dispatches/{dispatchId}}). */ +public final class WebhookDispatchClient { + private final ResourceContext ctx; + + public WebhookDispatchClient(HttpClientCore http, String baseUrl, String id) { + this.ctx = ResourceContext.single(http, baseUrl, ResourcePaths.WEBHOOK_DISPATCHES, id); + } + + /** Fetches the dispatch, or empty if it does not exist. */ + public Optional get() { + return ctx.getResource("", new QueryParams(), WebhookDispatch.class); + } +} diff --git a/src/main/java/com/apify/client/WebhookDispatchCollectionClient.java b/src/main/java/com/apify/client/webhook/WebhookDispatchCollectionClient.java similarity index 79% rename from src/main/java/com/apify/client/WebhookDispatchCollectionClient.java rename to src/main/java/com/apify/client/webhook/WebhookDispatchCollectionClient.java index 2aa3ede..c81e9b0 100644 --- a/src/main/java/com/apify/client/WebhookDispatchCollectionClient.java +++ b/src/main/java/com/apify/client/webhook/WebhookDispatchCollectionClient.java @@ -1,5 +1,10 @@ -package com.apify.client; +package com.apify.client.webhook; +import com.apify.client.ListOptions; +import com.apify.client.PaginationList; +import com.apify.client.internal.HttpClientCore; +import com.apify.client.internal.QueryParams; +import com.apify.client.internal.ResourceContext; import java.util.Iterator; /** @@ -9,7 +14,7 @@ public final class WebhookDispatchCollectionClient { private final ResourceContext ctx; - WebhookDispatchCollectionClient(HttpClientCore http, String baseUrl, String resourcePath) { + public WebhookDispatchCollectionClient(HttpClientCore http, String baseUrl, String resourcePath) { this.ctx = ResourceContext.collection(http, baseUrl, resourcePath); } diff --git a/src/test/java/com/apify/client/ClientBehaviourRegressionTest.java b/src/test/java/com/apify/client/ClientBehaviourRegressionTest.java index a8e5441..64da45b 100644 --- a/src/test/java/com/apify/client/ClientBehaviourRegressionTest.java +++ b/src/test/java/com/apify/client/ClientBehaviourRegressionTest.java @@ -5,6 +5,26 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.apify.client.actor.Actor; +import com.apify.client.actor.ActorListOptions; +import com.apify.client.actor.ActorVersion; +import com.apify.client.dataset.DatasetDownloadOptions; +import com.apify.client.dataset.DatasetListItemsOptions; +import com.apify.client.dataset.DownloadItemsFormat; +import com.apify.client.keyvalue.KeyValueStore; +import com.apify.client.keyvalue.KeyValueStoreRecord; +import com.apify.client.keyvalue.ListKeysOptions; +import com.apify.client.keyvalue.SetRecordOptions; +import com.apify.client.requestqueue.BatchAddRequestsOptions; +import com.apify.client.requestqueue.BatchAddResult; +import com.apify.client.requestqueue.RequestQueueRequest; +import com.apify.client.run.ActorRun; +import com.apify.client.run.LastRunOptions; +import com.apify.client.run.RunChargeOptions; +import com.apify.client.store.ActorStoreListItem; +import com.apify.client.store.StoreListOptions; +import com.apify.client.webhook.NestedWebhookCollectionClient; +import com.apify.client.webhook.Webhook; import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -115,7 +135,7 @@ void keyValueStoreRecordDefensivelyCopiesBytes() { void setRecordDoesNotRetryTimeoutsWhenOptedOut() { MockBackend timeouts = new MockBackend(List.of(MockBackend.timeoutError())); assertThrows( - HttpClientCore.TransportException.class, + ApifyTransportException.class, () -> client(timeouts, 3) .keyValueStore("s") @@ -131,7 +151,7 @@ void setRecordDoesNotRetryTimeoutsWhenOptedOut() { void setRecordRetriesTimeoutsByDefault() { MockBackend timeouts = new MockBackend(List.of(MockBackend.timeoutError())); assertThrows( - HttpClientCore.TransportException.class, + ApifyTransportException.class, () -> client(timeouts, 3).keyValueStore("s").setRecord("k", new byte[] {1}, "text/plain")); assertEquals(4, timeouts.calls, "timeouts should be retried (maxRetries + 1 attempts)"); } diff --git a/src/test/java/com/apify/client/CompressionTest.java b/src/test/java/com/apify/client/CompressionTest.java index f7c6c27..67ca00c 100644 --- a/src/test/java/com/apify/client/CompressionTest.java +++ b/src/test/java/com/apify/client/CompressionTest.java @@ -9,6 +9,7 @@ import com.aayushatharva.brotli4j.Brotli4jLoader; import com.aayushatharva.brotli4j.decoder.Decoder; import com.aayushatharva.brotli4j.decoder.DirectDecompress; +import com.apify.client.internal.HttpClientCore; import java.io.ByteArrayInputStream; import java.io.IOException; import java.nio.file.Files; @@ -54,21 +55,21 @@ private static byte[] payload(int size, byte fill) { } /** - * Whether a brotli native codec is expected to load here. brotli4j bundles glibc x86_64/aarch64 - * binaries (what CI's ubuntu-latest and typical server runtimes use); on musl (Alpine), 32-bit, - * or other CPU arches none ships and the client falls back to gzip. Tests that force the brotli - * codec are gated on this so they run where brotli is expected and skip (not error) where gzip - * applies. + * Whether a brotli native codec is expected to load here. The native codec is test-scoped only + * (see {@code pom.xml}: {@code native-linux-x86_64}, {@code test} scope) — the published artifact + * deliberately ships no native by default, so a consumer gets gzip unless they opt in by adding + * their platform's brotli4j native themselves. This test-only native targets glibc linux x86_64 + * (this project's CI and typical containers); on musl (Alpine), 32-bit, other CPU arches, or when + * the test native is absent, the client falls back to gzip. Tests that force the brotli codec are + * gated on this so they run where brotli is expected and skip (not error) where gzip applies. */ private static boolean nativeBrotliExpected() { String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT); String arch = System.getProperty("os.arch", "").toLowerCase(Locale.ROOT); boolean linux = os.contains("nux"); - boolean commonArch = - arch.equals("amd64") - || arch.equals("x86_64") - || arch.equals("aarch64") - || arch.equals("arm64"); + // Only x86_64/amd64: the test-scoped native dependency is native-linux-x86_64 only (see the + // class javadoc above), so aarch64/arm64 hosts have no test native and must not expect brotli. + boolean commonArch = arch.equals("amd64") || arch.equals("x86_64"); boolean musl = Files.exists(Path.of("/lib/ld-musl-x86_64.so.1")) || Files.exists(Path.of("/lib/ld-musl-aarch64.so.1")); @@ -99,14 +100,16 @@ void gzipPathEncodesAsGzipAndRoundTrips() throws IOException { assertArrayEquals(payload, gunzip(c.body), "server must recover the original body from gzip"); } - // --- Brotli native codec must load where a native is bundled (CI/runtime), so brotli runs. --- + // --- Brotli native codec must load where the test-scoped native applies, so brotli runs. --- @Test - void brotliNativeCodecLoadsWhereBundled() { - assumeTrue(nativeBrotliExpected(), "no brotli native bundled for this platform; gzip fallback"); + void brotliNativeCodecLoadsWhereTestNativeApplies() { + assumeTrue( + nativeBrotliExpected(), "no test-scoped brotli native for this platform; gzip fallback"); assertTrue( HttpClientCore.brotliAvailable(), - "brotli native codec must load on glibc x86_64/aarch64 so the preferred brotli path runs"); + "brotli native codec must load on glibc linux x86_64 (this project's test-scoped native) so" + + " the preferred brotli path runs"); } // --- Live client path: uses the preferred coding and round-trips through the backend. --- diff --git a/src/test/java/com/apify/client/DatasetItemsIteratorTest.java b/src/test/java/com/apify/client/DatasetItemsIteratorTest.java index d51f1b7..929557d 100644 --- a/src/test/java/com/apify/client/DatasetItemsIteratorTest.java +++ b/src/test/java/com/apify/client/DatasetItemsIteratorTest.java @@ -3,6 +3,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.apify.client.dataset.DatasetClient; +import com.apify.client.dataset.DatasetListItemsOptions; import com.fasterxml.jackson.databind.JsonNode; import java.time.Duration; import java.util.ArrayList; diff --git a/src/test/java/com/apify/client/DocSnippetsTest.java b/src/test/java/com/apify/client/DocSnippetsTest.java index 2db55d5..7e4e525 100644 --- a/src/test/java/com/apify/client/DocSnippetsTest.java +++ b/src/test/java/com/apify/client/DocSnippetsTest.java @@ -90,6 +90,19 @@ private static String wrap(String className, String snippet) { return snippet; } return "import com.apify.client.*;\n" + + "import com.apify.client.actor.*;\n" + + "import com.apify.client.build.*;\n" + + "import com.apify.client.dataset.*;\n" + + "import com.apify.client.http.*;\n" + + "import com.apify.client.keyvalue.*;\n" + + "import com.apify.client.log.*;\n" + + "import com.apify.client.requestqueue.*;\n" + + "import com.apify.client.run.*;\n" + + "import com.apify.client.schedule.*;\n" + + "import com.apify.client.store.*;\n" + + "import com.apify.client.task.*;\n" + + "import com.apify.client.user.*;\n" + + "import com.apify.client.webhook.*;\n" + "import com.fasterxml.jackson.databind.*;\n" + "import java.util.*;\n" + "import java.time.*;\n" diff --git a/src/test/java/com/apify/client/KeyIteratorTest.java b/src/test/java/com/apify/client/KeyIteratorTest.java index c92b7bb..df63644 100644 --- a/src/test/java/com/apify/client/KeyIteratorTest.java +++ b/src/test/java/com/apify/client/KeyIteratorTest.java @@ -4,6 +4,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.apify.client.keyvalue.KeyValueStoreClient; +import com.apify.client.keyvalue.KeyValueStoreKey; +import com.apify.client.keyvalue.ListKeysOptions; import java.time.Duration; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/test/java/com/apify/client/MockBackend.java b/src/test/java/com/apify/client/MockBackend.java index 1df7af6..bc89331 100644 --- a/src/test/java/com/apify/client/MockBackend.java +++ b/src/test/java/com/apify/client/MockBackend.java @@ -1,10 +1,10 @@ package com.apify.client; +import com.apify.client.http.HttpClient; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.net.http.HttpClient; import java.net.http.HttpHeaders; import java.net.http.HttpRequest; import java.net.http.HttpResponse; @@ -19,11 +19,15 @@ import javax.net.ssl.SSLSession; /** - * A deterministic {@link HttpBackend} for offline unit tests. It serves a queue of scripted + * A deterministic {@link HttpClient} for offline unit tests. It serves a queue of scripted * responses/errors, records how many times it was called, and captures the last request's headers, * URL and body. + * + *

The JDK's own {@code java.net.http.HttpClient} is referenced by its fully-qualified name below + * (never imported unqualified): an unqualified single-type import would clash with this package's + * own {@link HttpClient} interface of the same simple name. */ -final class MockBackend implements HttpBackend { +final class MockBackend implements HttpClient { /** One scripted response: an HTTP status + body, or a transport error. */ static final class Scripted { @@ -47,8 +51,8 @@ static final class Scripted { final List bodies = new ArrayList<>(); /** - * Optional scripted response for {@link #sendStreaming}; defaults to the first {@code send} - * entry. + * Optional scripted response for {@link #sendStreamingResponse}; defaults to the first {@code + * send} entry. */ private Scripted streamResponse; @@ -98,17 +102,17 @@ public synchronized HttpResponse send(HttpRequest request) throws IOExce return new FakeResponse(request.uri(), r.status, r.body); } - /** Scripts the next {@link #sendStreaming} call to return the given status and body. */ + /** Scripts the next {@link #sendStreamingResponse} call to return the given status and body. */ void scriptStream(int status, String body) { this.streamResponse = new Scripted(status, body, null); this.streamBody = null; } /** - * Scripts the next {@link #sendStreaming} call to return the given status and an arbitrary, - * possibly blocking, {@link InputStream}. Unlike {@link #scriptStream(int, String)}, the body is - * not a finite in-memory buffer, so {@code read()} does not naturally reach end-of-stream; this - * models a live log that only ends when the stream is closed. + * Scripts the next {@link #sendStreamingResponse} call to return the given status and an + * arbitrary, possibly blocking, {@link InputStream}. Unlike {@link #scriptStream(int, String)}, + * the body is not a finite in-memory buffer, so {@code read()} does not naturally reach + * end-of-stream; this models a live log that only ends when the stream is closed. */ void scriptStream(int status, InputStream body) { this.streamBodyStatus = status; @@ -117,7 +121,7 @@ void scriptStream(int status, InputStream body) { } @Override - public synchronized HttpResponse sendStreaming(HttpRequest request) { + public synchronized HttpResponse sendStreamingResponse(HttpRequest request) { calls++; lastUrl = request.uri().toString(); if (streamBody != null) { @@ -217,8 +221,8 @@ public URI uri() { } @Override - public HttpClient.Version version() { - return HttpClient.Version.HTTP_1_1; + public java.net.http.HttpClient.Version version() { + return java.net.http.HttpClient.Version.HTTP_1_1; } } @@ -270,8 +274,8 @@ public URI uri() { } @Override - public HttpClient.Version version() { - return HttpClient.Version.HTTP_1_1; + public java.net.http.HttpClient.Version version() { + return java.net.http.HttpClient.Version.HTTP_1_1; } } } diff --git a/src/test/java/com/apify/client/PaginatedIteratorTest.java b/src/test/java/com/apify/client/PaginatedIteratorTest.java index d77461d..e704b26 100644 --- a/src/test/java/com/apify/client/PaginatedIteratorTest.java +++ b/src/test/java/com/apify/client/PaginatedIteratorTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.apify.client.internal.PaginatedIterator; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; diff --git a/src/test/java/com/apify/client/SignatureTest.java b/src/test/java/com/apify/client/SignatureTest.java index e7ace13..6720ef3 100644 --- a/src/test/java/com/apify/client/SignatureTest.java +++ b/src/test/java/com/apify/client/SignatureTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import com.apify.client.internal.Signatures; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.util.Base64; diff --git a/src/test/java/com/apify/client/StreamedLogTest.java b/src/test/java/com/apify/client/StreamedLogTest.java index efaca90..90b5704 100644 --- a/src/test/java/com/apify/client/StreamedLogTest.java +++ b/src/test/java/com/apify/client/StreamedLogTest.java @@ -7,6 +7,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.apify.client.log.StreamedLog; +import com.apify.client.log.StreamedLogOptions; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; diff --git a/src/test/java/com/apify/client/UnitHttpTest.java b/src/test/java/com/apify/client/UnitHttpTest.java index a6d2334..b3e51e6 100644 --- a/src/test/java/com/apify/client/UnitHttpTest.java +++ b/src/test/java/com/apify/client/UnitHttpTest.java @@ -5,6 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.apify.client.actor.Actor; +import com.apify.client.user.User; import java.time.Duration; import java.util.List; import java.util.Optional; diff --git a/src/test/java/com/apify/client/examples/CreateBuildRunActor.java b/src/test/java/com/apify/client/examples/CreateBuildRunActor.java index a3b4ed4..45dcf94 100644 --- a/src/test/java/com/apify/client/examples/CreateBuildRunActor.java +++ b/src/test/java/com/apify/client/examples/CreateBuildRunActor.java @@ -1,12 +1,12 @@ package com.apify.client.examples; -import com.apify.client.Actor; -import com.apify.client.ActorBuildOptions; -import com.apify.client.ActorClient; -import com.apify.client.ActorRun; -import com.apify.client.ActorStartOptions; import com.apify.client.ApifyClient; -import com.apify.client.Build; +import com.apify.client.actor.Actor; +import com.apify.client.actor.ActorBuildOptions; +import com.apify.client.actor.ActorClient; +import com.apify.client.actor.ActorStartOptions; +import com.apify.client.build.Build; +import com.apify.client.run.ActorRun; import java.util.List; import java.util.Map; import java.util.Optional; diff --git a/src/test/java/com/apify/client/examples/GetAccount.java b/src/test/java/com/apify/client/examples/GetAccount.java index c8091b5..cf13e52 100644 --- a/src/test/java/com/apify/client/examples/GetAccount.java +++ b/src/test/java/com/apify/client/examples/GetAccount.java @@ -1,7 +1,7 @@ package com.apify.client.examples; import com.apify.client.ApifyClient; -import com.apify.client.User; +import com.apify.client.user.User; import java.util.Optional; /** diff --git a/src/test/java/com/apify/client/examples/IterateStore.java b/src/test/java/com/apify/client/examples/IterateStore.java index a733f02..5179179 100644 --- a/src/test/java/com/apify/client/examples/IterateStore.java +++ b/src/test/java/com/apify/client/examples/IterateStore.java @@ -1,8 +1,8 @@ package com.apify.client.examples; -import com.apify.client.ActorStoreListItem; import com.apify.client.ApifyClient; -import com.apify.client.StoreListOptions; +import com.apify.client.store.ActorStoreListItem; +import com.apify.client.store.StoreListOptions; import java.util.Iterator; /** diff --git a/src/test/java/com/apify/client/examples/LogRedirection.java b/src/test/java/com/apify/client/examples/LogRedirection.java index 0acfd1e..825f9b1 100644 --- a/src/test/java/com/apify/client/examples/LogRedirection.java +++ b/src/test/java/com/apify/client/examples/LogRedirection.java @@ -1,10 +1,10 @@ package com.apify.client.examples; -import com.apify.client.ActorRun; -import com.apify.client.ActorStartOptions; import com.apify.client.ApifyClient; -import com.apify.client.RunClient; -import com.apify.client.StreamedLog; +import com.apify.client.actor.ActorStartOptions; +import com.apify.client.log.StreamedLog; +import com.apify.client.run.ActorRun; +import com.apify.client.run.RunClient; /** * Starts an Actor without waiting, then redirects its live log to a logger in real time (log diff --git a/src/test/java/com/apify/client/examples/RunAndLastRunStorages.java b/src/test/java/com/apify/client/examples/RunAndLastRunStorages.java index bed3213..28e6500 100644 --- a/src/test/java/com/apify/client/examples/RunAndLastRunStorages.java +++ b/src/test/java/com/apify/client/examples/RunAndLastRunStorages.java @@ -1,9 +1,9 @@ package com.apify.client.examples; -import com.apify.client.ActorRun; -import com.apify.client.ActorStartOptions; import com.apify.client.ApifyClient; -import com.apify.client.DatasetListItemsOptions; +import com.apify.client.actor.ActorStartOptions; +import com.apify.client.dataset.DatasetListItemsOptions; +import com.apify.client.run.ActorRun; import java.util.Optional; /** diff --git a/src/test/java/com/apify/client/examples/RunStoreActor.java b/src/test/java/com/apify/client/examples/RunStoreActor.java index 561e255..12ba9a2 100644 --- a/src/test/java/com/apify/client/examples/RunStoreActor.java +++ b/src/test/java/com/apify/client/examples/RunStoreActor.java @@ -1,9 +1,9 @@ package com.apify.client.examples; -import com.apify.client.ActorRun; -import com.apify.client.ActorStartOptions; import com.apify.client.ApifyClient; -import com.apify.client.DatasetListItemsOptions; +import com.apify.client.actor.ActorStartOptions; +import com.apify.client.dataset.DatasetListItemsOptions; +import com.apify.client.run.ActorRun; /** * Runs an existing store Actor ({@code apify/hello-world}), waits for it to finish, and reads its diff --git a/src/test/java/com/apify/client/examples/Storages.java b/src/test/java/com/apify/client/examples/Storages.java index f1bdec9..8195abc 100644 --- a/src/test/java/com/apify/client/examples/Storages.java +++ b/src/test/java/com/apify/client/examples/Storages.java @@ -1,12 +1,12 @@ package com.apify.client.examples; import com.apify.client.ApifyClient; -import com.apify.client.Dataset; -import com.apify.client.DatasetListItemsOptions; -import com.apify.client.KeyValueStore; -import com.apify.client.KeyValueStoreRecord; -import com.apify.client.RequestQueue; -import com.apify.client.RequestQueueRequest; +import com.apify.client.dataset.Dataset; +import com.apify.client.dataset.DatasetListItemsOptions; +import com.apify.client.keyvalue.KeyValueStore; +import com.apify.client.keyvalue.KeyValueStoreRecord; +import com.apify.client.requestqueue.RequestQueue; +import com.apify.client.requestqueue.RequestQueueRequest; import java.util.List; import java.util.Map; import java.util.Optional; diff --git a/src/test/java/com/apify/client/integration/ActorIntegrationTest.java b/src/test/java/com/apify/client/integration/ActorIntegrationTest.java index a6f93af..bc52b9f 100644 --- a/src/test/java/com/apify/client/integration/ActorIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/ActorIntegrationTest.java @@ -3,14 +3,14 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.apify.client.Actor; -import com.apify.client.ActorClient; -import com.apify.client.ActorEnvVar; -import com.apify.client.ActorListOptions; -import com.apify.client.ActorVersion; import com.apify.client.ApifyClient; import com.apify.client.ListOptions; import com.apify.client.PaginationList; +import com.apify.client.actor.Actor; +import com.apify.client.actor.ActorClient; +import com.apify.client.actor.ActorEnvVar; +import com.apify.client.actor.ActorListOptions; +import com.apify.client.actor.ActorVersion; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/apify/client/integration/ActorRunIntegrationTest.java b/src/test/java/com/apify/client/integration/ActorRunIntegrationTest.java index b7a8f3d..b1e68b9 100644 --- a/src/test/java/com/apify/client/integration/ActorRunIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/ActorRunIntegrationTest.java @@ -3,12 +3,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.apify.client.ActorRun; -import com.apify.client.ActorStartOptions; import com.apify.client.ApifyClient; -import com.apify.client.LastRunOptions; import com.apify.client.ListOptions; -import com.apify.client.RunListOptions; +import com.apify.client.actor.ActorStartOptions; +import com.apify.client.run.ActorRun; +import com.apify.client.run.LastRunOptions; +import com.apify.client.run.RunListOptions; import java.util.Optional; import org.junit.jupiter.api.Test; @@ -32,7 +32,10 @@ void runActorAndReadOutputs() { Optional log = client.run(run.getId()).log().get(); assertTrue(log.isPresent() && !log.get().isEmpty()); - client.run(run.getId()).dataset().listItems(new com.apify.client.DatasetListItemsOptions()); + client + .run(run.getId()) + .dataset() + .listItems(new com.apify.client.dataset.DatasetListItemsOptions()); client.run(run.getId()).keyValueStore().getRecord("OUTPUT"); } @@ -58,11 +61,12 @@ void lastRunAccess() { void streamedLogRedirection() { ApifyClient client = requireClient(); ActorRun run = client.actor("apify/hello-world").start(null, new ActorStartOptions()); - com.apify.client.RunClient runClient = client.run(run.getId()); + com.apify.client.run.RunClient runClient = client.run(run.getId()); java.util.List collected = new java.util.concurrent.CopyOnWriteArrayList<>(); - try (com.apify.client.StreamedLog streamedLog = - runClient.getStreamedLog(new com.apify.client.StreamedLogOptions().toLog(collected::add))) { + try (com.apify.client.log.StreamedLog streamedLog = + runClient.getStreamedLog( + new com.apify.client.log.StreamedLogOptions().toLog(collected::add))) { streamedLog.start(); runClient.waitForFinish(120L); } diff --git a/src/test/java/com/apify/client/integration/BuildIntegrationTest.java b/src/test/java/com/apify/client/integration/BuildIntegrationTest.java index 1642a64..c01d2d6 100644 --- a/src/test/java/com/apify/client/integration/BuildIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/BuildIntegrationTest.java @@ -3,11 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.apify.client.Actor; -import com.apify.client.ActorBuildOptions; import com.apify.client.ApifyClient; -import com.apify.client.Build; import com.apify.client.ListOptions; +import com.apify.client.actor.Actor; +import com.apify.client.actor.ActorBuildOptions; +import com.apify.client.build.Build; import org.junit.jupiter.api.Test; class BuildIntegrationTest extends IntegrationBase { diff --git a/src/test/java/com/apify/client/integration/DatasetIntegrationTest.java b/src/test/java/com/apify/client/integration/DatasetIntegrationTest.java index 7cde9ca..ebea377 100644 --- a/src/test/java/com/apify/client/integration/DatasetIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/DatasetIntegrationTest.java @@ -4,13 +4,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.apify.client.ApifyClient; -import com.apify.client.Dataset; -import com.apify.client.DatasetClient; -import com.apify.client.DatasetDownloadOptions; -import com.apify.client.DatasetListItemsOptions; -import com.apify.client.DownloadItemsFormat; import com.apify.client.PaginationList; import com.apify.client.StorageListOptions; +import com.apify.client.dataset.Dataset; +import com.apify.client.dataset.DatasetClient; +import com.apify.client.dataset.DatasetDownloadOptions; +import com.apify.client.dataset.DatasetListItemsOptions; +import com.apify.client.dataset.DownloadItemsFormat; import com.fasterxml.jackson.databind.JsonNode; import java.nio.charset.StandardCharsets; import java.util.List; diff --git a/src/test/java/com/apify/client/integration/IterationIntegrationTest.java b/src/test/java/com/apify/client/integration/IterationIntegrationTest.java index 1772e2d..5822f8d 100644 --- a/src/test/java/com/apify/client/integration/IterationIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/IterationIntegrationTest.java @@ -3,26 +3,26 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.apify.client.Actor; -import com.apify.client.ActorEnvVar; -import com.apify.client.ActorListOptions; import com.apify.client.ApifyClient; -import com.apify.client.Build; -import com.apify.client.Dataset; -import com.apify.client.DatasetClient; -import com.apify.client.DatasetListItemsOptions; -import com.apify.client.KeyValueStore; -import com.apify.client.KeyValueStoreClient; -import com.apify.client.KeyValueStoreKey; -import com.apify.client.ListKeysOptions; import com.apify.client.ListOptions; -import com.apify.client.RequestQueue; -import com.apify.client.RunListOptions; -import com.apify.client.Schedule; import com.apify.client.StorageListOptions; -import com.apify.client.Task; -import com.apify.client.Webhook; -import com.apify.client.WebhookDispatch; +import com.apify.client.actor.Actor; +import com.apify.client.actor.ActorEnvVar; +import com.apify.client.actor.ActorListOptions; +import com.apify.client.build.Build; +import com.apify.client.dataset.Dataset; +import com.apify.client.dataset.DatasetClient; +import com.apify.client.dataset.DatasetListItemsOptions; +import com.apify.client.keyvalue.KeyValueStore; +import com.apify.client.keyvalue.KeyValueStoreClient; +import com.apify.client.keyvalue.KeyValueStoreKey; +import com.apify.client.keyvalue.ListKeysOptions; +import com.apify.client.requestqueue.RequestQueue; +import com.apify.client.run.RunListOptions; +import com.apify.client.schedule.Schedule; +import com.apify.client.task.Task; +import com.apify.client.webhook.Webhook; +import com.apify.client.webhook.WebhookDispatch; import com.fasterxml.jackson.databind.JsonNode; import java.util.ArrayList; import java.util.HashSet; @@ -322,7 +322,7 @@ void iterateActorVersionsAndEnvVars() { // version 0.0, so iteration yields at least one version, each exactly once. Set versionNumbers = new HashSet<>(); int versionCount = 0; - Iterator versions = + Iterator versions = actorClient.versions().iterate(new ListOptions()); while (versions.hasNext()) { versionCount++; @@ -363,7 +363,7 @@ void iterateBuildsBounded() { @Test void iterateRunsBounded() { ApifyClient client = requireClient(); - Iterator it = + Iterator it = client.runs().iterate(new ListOptions().limit(5L), new RunListOptions(), 2L); int count = 0; while (it.hasNext()) { diff --git a/src/test/java/com/apify/client/integration/KeyValueStoreIntegrationTest.java b/src/test/java/com/apify/client/integration/KeyValueStoreIntegrationTest.java index f01bf7f..1f9a423 100644 --- a/src/test/java/com/apify/client/integration/KeyValueStoreIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/KeyValueStoreIntegrationTest.java @@ -4,12 +4,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.apify.client.ApifyClient; -import com.apify.client.GetRecordOptions; -import com.apify.client.KeyValueStore; -import com.apify.client.KeyValueStoreClient; -import com.apify.client.KeyValueStoreRecord; -import com.apify.client.ListKeysOptions; import com.apify.client.StorageListOptions; +import com.apify.client.keyvalue.GetRecordOptions; +import com.apify.client.keyvalue.KeyValueStore; +import com.apify.client.keyvalue.KeyValueStoreClient; +import com.apify.client.keyvalue.KeyValueStoreRecord; +import com.apify.client.keyvalue.ListKeysOptions; import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; diff --git a/src/test/java/com/apify/client/integration/RequestQueueIntegrationTest.java b/src/test/java/com/apify/client/integration/RequestQueueIntegrationTest.java index 69277f2..27d9590 100644 --- a/src/test/java/com/apify/client/integration/RequestQueueIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/RequestQueueIntegrationTest.java @@ -4,13 +4,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import com.apify.client.ApifyClient; -import com.apify.client.BatchAddResult; -import com.apify.client.ListRequestsOptions; -import com.apify.client.RequestQueue; -import com.apify.client.RequestQueueClient; -import com.apify.client.RequestQueueOperationInfo; -import com.apify.client.RequestQueueRequest; import com.apify.client.StorageListOptions; +import com.apify.client.requestqueue.BatchAddResult; +import com.apify.client.requestqueue.ListRequestsOptions; +import com.apify.client.requestqueue.RequestQueue; +import com.apify.client.requestqueue.RequestQueueClient; +import com.apify.client.requestqueue.RequestQueueOperationInfo; +import com.apify.client.requestqueue.RequestQueueRequest; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; diff --git a/src/test/java/com/apify/client/integration/ScheduleIntegrationTest.java b/src/test/java/com/apify/client/integration/ScheduleIntegrationTest.java index fb5e09c..0455c48 100644 --- a/src/test/java/com/apify/client/integration/ScheduleIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/ScheduleIntegrationTest.java @@ -5,8 +5,8 @@ import com.apify.client.ApifyClient; import com.apify.client.ListOptions; -import com.apify.client.Schedule; -import com.apify.client.ScheduleClient; +import com.apify.client.schedule.Schedule; +import com.apify.client.schedule.ScheduleClient; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/apify/client/integration/StoreIntegrationTest.java b/src/test/java/com/apify/client/integration/StoreIntegrationTest.java index 66a19ce..ea0863a 100644 --- a/src/test/java/com/apify/client/integration/StoreIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/StoreIntegrationTest.java @@ -2,9 +2,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import com.apify.client.ActorStoreListItem; import com.apify.client.ApifyClient; -import com.apify.client.StoreListOptions; +import com.apify.client.store.ActorStoreListItem; +import com.apify.client.store.StoreListOptions; import java.util.Iterator; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/apify/client/integration/TaskIntegrationTest.java b/src/test/java/com/apify/client/integration/TaskIntegrationTest.java index f1ea420..4c16d77 100644 --- a/src/test/java/com/apify/client/integration/TaskIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/TaskIntegrationTest.java @@ -5,9 +5,9 @@ import com.apify.client.ApifyClient; import com.apify.client.ListOptions; -import com.apify.client.RunListOptions; -import com.apify.client.Task; -import com.apify.client.TaskClient; +import com.apify.client.run.RunListOptions; +import com.apify.client.task.Task; +import com.apify.client.task.TaskClient; import java.util.Map; import org.junit.jupiter.api.Test; diff --git a/src/test/java/com/apify/client/integration/WebhookIntegrationTest.java b/src/test/java/com/apify/client/integration/WebhookIntegrationTest.java index eb7e9b9..58f0de4 100644 --- a/src/test/java/com/apify/client/integration/WebhookIntegrationTest.java +++ b/src/test/java/com/apify/client/integration/WebhookIntegrationTest.java @@ -5,9 +5,9 @@ import com.apify.client.ApifyClient; import com.apify.client.ListOptions; -import com.apify.client.Webhook; -import com.apify.client.WebhookClient; -import com.apify.client.WebhookDispatch; +import com.apify.client.webhook.Webhook; +import com.apify.client.webhook.WebhookClient; +import com.apify.client.webhook.WebhookDispatch; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test;