Repository navigation
feat!: sync .NET client with latest spec and apify-client-js v3 behavior fixes - #15
Merged
Merged
Conversation
…ior fixes Bumps the OpenAPI spec baseline to v2-2026-10-01T153946Z (adds Build.ImageDigest; drops a stale "contact support" sentence from task-publish docs) and ports the behavior-relevant fixes from apify-client-js's v2->v3 rewrite (PRs #985-#1104): - Per-status ApifyApiException subclasses (InvalidRequestException, UnauthorizedException, ForbiddenException, NotFoundException, ConflictException, RateLimitException, ServerException). - A 404 reached through a chained client with no id of its own (run.Dataset()/.KeyValueStore()/ .RequestQueue()/.Log(), build.Log()) now throws instead of resolving null/no-op, since the ambiguity (parent vs. nested resource) made the old null misleading; id-addressed lookups are unaffected. DatasetClient.GetStatisticsAsync/ScheduleClient.GetLogAsync/TaskClient.GetInputAsync now throw rather than resolve null for a missing parent. - Dataset item iteration now paginates by the API's scanned-row count instead of the returned item count, fixing early stops/re-yields on filtered pages and skips on Unwind-expanded ones. - CreateItemsPublicUrlAsync gained a `format` parameter. - Request-body compression now skips already-compressed content types (images, audio, video, archives, office/ZIP formats, web fonts). - BaseUrl/PublicBaseUrl accept a URL that already ends with /v2 without doubling it. - Actor/task start, Actor input validation and run metamorph accept byte[] input sent as raw bytes. - ActorVersionCollectionClient drops its non-functional ListOptions parameter (the endpoint never supported pagination); ActorClient.Version/ActorVersionClient.EnvVar/ApifyClient.Build reject an empty id. Verified path traversal (#1011), custom HTTP client (#1058), configurable compression (#1065) and CancellationToken-as-AbortSignal (#1081) were already correctly implemented; URL-field normalization (#1045) and the full timeout-tier redesign (#1046) are deliberately out of scope for this PR (see notes.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STUGToGEaEFoBGRbWTvF4m
…tion Addresses the three open review items from PR #15's first review round: - Added the reference client's timeout-tier system: Short/Medium/Long request tiers with constructor-configurable durations (ApifyClientOptions.TimeoutShortSecs default 5, TimeoutMediumSecs default 30, TimeoutSecs default 360 now also the Long tier and overall clamp). Every call site across every resource client now picks its tier explicitly, classified from the actual apify-client-js source per method. A per-call literal `timeoutSecs` parameter on every method was not added: CancellationToken + CancelAfter() is .NET's idiomatic per-call deadline mechanism, and the existing per-attempt-doubling-capped-at-overall algorithm already matches the reference's own timeout semantics. - Reverted TaskClient.StartAsync to JSON-only input: the reference's TaskStartOptions has no contentType to pair raw bytes with, so the earlier byte[] support there was an over-port that mislabeled raw bytes as JSON. - Added URL normalization (RFC 3986, via System.Uri) to the client's two typed properties among the reference's 17 normalized fields (ActorRun.ContainerUrl, Webhook.RequestUrl); RequestQueueRequest.Url is correctly left unnormalized, matching the reference's explicit exclusion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STUGToGEaEFoBGRbWTvF4m
Addresses the two open items from PR #15's second review round: - Fixed 5 tier-classification mismatches found against the reference source: ActorCollectionClient/TaskCollectionClient.CreateAsync (Short -> Medium, matching the reference's non-idempotent-create tier, avoiding a duplicate Actor/Task on a retried timeout), RunClient.ChargeAsync (was silently defaulting to Long, now explicit Short), BuildClient.GetOpenApiDefinitionAsync (Short -> Medium), BuildClient.AbortAsync (Medium -> Short). - Added WithTimeout(TimeSpan) to every resource client, a general per-call(-instance) timeout override matching the reference client's per-call `timeoutSecs` option (TimeSpan.Zero for no timeout, matching 'noTimeout'). Implementing this surfaced and fixed a latent bug: an explicit override longer than the configured overall budget was silently clamped back down to it on the very first attempt, so asking for more time than the default had no effect. The retry-growth cap for a call now also rises to cover an explicit override that exceeds it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STUGToGEaEFoBGRbWTvF4m
…call sites Addresses the two new defects from PR #15's third review round: - Reverted the Iteration-3 change that let an explicit WithTimeout override exceed ApifyClientOptions.TimeoutSecs: the reference client always caps a per-call timeout at timeoutMaxSecs (logging a warning when exceeded) rather than letting it win, and the client's own RetryConfig.TimeoutSecs doc already promised an overall cap. WithTimeout now behaves like the reference: a value above TimeoutSecs is capped at it; raise TimeoutSecs itself for a longer per-call timeout. TimeSpan.Zero (no timeout) is unaffected, as before. - RunClient.ChargeAsync, TaskClient.UpdateInputAsync and UserClient.UpdateLimitsAsync call HttpClientCore directly rather than through a ResourceContext CRUD primitive, and each omitted the context's timeout override, so WithTimeout silently had no effect on them. Fixed all three to pass it through. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STUGToGEaEFoBGRbWTvF4m
Addresses the two open items from PR #15's fourth review round: - docs/README.md's "Timeout tiers" section still described the reverted Iteration-3 behavior ("asking for more time is honored rather than silently clamped"). Corrected to state a WithTimeout override above TimeoutSecs is capped at it, matching the code, the XML docs and the CHANGELOG. - The empty-id guard added to ApifyClient.Build was never applied to its 11 sibling id-addressed accessors (Actor, Run, Dataset, KeyValueStore, RequestQueue, Task, Schedule, Webhook, WebhookDispatch, Log, User), so an empty id on any of them silently mis-addressed a collection-shaped URL instead of throwing. Extracted a shared RequireNonEmptyId helper and applied it to all 12 accessors, matching the reference client's uniform id.min(1) validation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STUGToGEaEFoBGRbWTvF4m
Addresses the single open item from PR #15's fifth review round: the AttemptTimeout XML doc still described the reverted Iteration-3 behavior (an explicit override raising the overall cap). The code already caps an override at the overall budget (fixed in efebae2); only the comment was stale. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STUGToGEaEFoBGRbWTvF4m
… happens The previous reword (95141d1) claimed an explicit WithTimeout override is "capped to by the caller before reaching" AttemptTimeout, but the caller (CallAsync) does no capping -- baseTimeout is passed through uncapped and the cap is applied inside AttemptTimeout itself (including on the first, unscaled attempt). Corrected the comment to describe the actual control flow precisely. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STUGToGEaEFoBGRbWTvF4m
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Syncs the .NET client to OpenAPI spec
v2-2026-10-01T153946Z(apify/apify-docs#3024, apify/apify-docs#3037) and ports the behavior-relevant changes from the apify-client-js v2 → v3 rewrite (apify/apify-client-js#985 … #1104). The version is bumped to0.4.0, andCHANGELOG.mdcalls out each breaking change.Spec
Build.ImageDigest.docs/tasks.mdnow match the corrected spec wording.Reference-client parity
ApifyApiExceptionsubclasses (InvalidRequestException,UnauthorizedException,ForbiddenException,NotFoundException,ConflictException,RateLimitException,ServerException). All of them are still caught bycatch (ApifyApiException).RunClient.Dataset(),.KeyValueStore(),.RequestQueue(),.Log(),BuildClient.Log()) now throws instead of returningnull.DatasetClient.GetStatisticsAsync,ScheduleClient.GetLogAsyncandTaskClient.GetInputAsyncnow throw when the parent resource is missing.LogClient.StreamAsyncreturnsStream?.Short5s /Medium30s /Long360s) are assigned per method.ApifyClientOptions.TimeoutShortSecsandTimeoutMediumSecsare new, andTimeoutSecsstays the overall cap. Every resource client gets aWithTimeout(TimeSpan)override.ApifyClient, plusActorClient.VersionandActorVersionClient.EnvVar, reject an empty ID.ActorVersionCollectionClient.ListAsync/IterateAsyncdrop the unusedListOptionsparameter.DatasetClient.IterateItemsAsyncpaginates byX-Apify-Pagination-Count. Before, a filtered page could stall or re-yield items, and anUnwindpage could skip rows.CreateItemsPublicUrlAsyncgets aformatparameter.BaseUrl/PublicBaseUrlno longer double/v2.ActorClient.StartAsync/ValidateInputAsyncandRunClient.MetamorphAsyncaccept rawbyte[]input. Task input stays JSON-only, as in the reference client.format: uriresponse fields (ActorRun.ContainerUrl,Webhook.RequestUrl) are normalized to their RFC 3986 form.Review
It took seven
/review-clientiterations to get here. The rejections caught:WithTimeoutIteration 7 was Approved with no open items.
Test plan
dotnet format --verify-no-changesdotnet build -c Release(0 warnings/errors)dotnet test -c Releasewith liveAPIFY_TOKEN: 221/221 pass (unit, integration, examples)