Skip to content

Phase 6: Project API#30

Open
alxgsv wants to merge 19 commits intomasterfrom
phase6-project-api
Open

Phase 6: Project API#30
alxgsv wants to merge 19 commits intomasterfrom
phase6-project-api

Conversation

@alxgsv
Copy link
Copy Markdown
Contributor

@alxgsv alxgsv commented Mar 26, 2026

  • Add projectapi/ package for the Uploadcare Project API
  • Add ucare.NewBearerClient(token, conf) for bearer token authentication required by the Project API
  • Add ucare.ProjectAPIError type for Project API error responses ({"message": "...", "code": "..."})
  • Integration tests against the live API (PROJECT_API_TOKEN env var)
  • Updated README, CHANGELOG, and ucare/doc.go with Project API examples

Summary by CodeRabbit

  • New Features

    • Project API client (bearer auth) for projects, secrets and usage
    • File metadata CRUD and Addon execution (e.g., ClamAV, remove.bg)
    • Unified upload API with metadata passthrough and smart direct/multipart selection
    • Conversion path builders and expanded include options for file info
    • Per-project CDN base URL derivation
  • API Updates

    • File info call accepts optional include params; list supports appdata
    • Webhook deletion now by ID; new webhook event constants
    • Request retries disabled by default and opt-in via config; new bearer client
  • Error Handling

    • Exported structured API error types for reliable matching

alxgsv added 15 commits March 13, 2026 16:19
Previously, any status not explicitly handled (e.g. 409, 403, 500, 502)
fell through to the JSON decode path, silently producing zero-value
results. Now all >= 400 statuses return an error.

Also fix Decode(&resdata) -> Decode(resdata) to avoid unnecessary
*interface{} indirection.
Extract handleResponse method with defer resp.Body.Close() to
guarantee the response body is closed on all paths including nil
resdata, error decoding, and retry. Replace goto with for loop.
Derive CDN base URL from the public key (SHA256 → base36 → first 10 chars).
Config.CDNBase can be set explicitly to override the default. Shallow-copy Config in resolveConfig to avoid mutating a shared template when creating clients for multiple projects.
New projectapi/ package covering projects CRUD, secret keys management,
and usage metrics. Adds ucare.NewBearerClient() for bearer token auth
used by the Project API.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ef18bbbe-f473-43bb-9bc3-a66ebf907077

📥 Commits

Reviewing files that changed from the base of the PR and between 236f3b0 and 2ba0666.

📒 Files selected for processing (2)
  • ucare/projectapi.go
  • ucare/restapi.go
✅ Files skipped from review due to trivial changes (1)
  • ucare/projectapi.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • ucare/restapi.go

Walkthrough

Adds new packages (addon, metadata, projectapi), expands public APIs (file.Info accepts params; webhook.Delete by ID), introduces bearer-token Project API client with opt-in retry, exported structured API errors, CDN helpers, conversion path builders, unified Upload with metadata support, and various tests and docs updates.

Changes

Cohort / File(s) Summary
API Signatures & Events
file/file.go, file/service.go, file/list.go, webhook/webhook.go, webhook/service.go
file.Service.Info now takes *file.InfoParams (adds include support); file.ListParams.Include added; webhook.Service.Delete switched to delete by id int64; webhook event constants expanded.
Addon package
addon/addon.go, addon/log.go, addon/service.go, addon/service_test.go
New addon package: constants/types for addons, logging enable/disable, Service with Execute/Status, and unit tests covering request encoding and endpoints.
Metadata package
metadata/metadata.go, metadata/log.go, metadata/service.go, metadata/validation.go, metadata/service_test.go
New metadata package: key validation (ErrInvalidKey), logging control, Service exposing List/Get/Set/Delete, path-escaping and JSON-string body encoder for Set, and comprehensive tests (including invalid-key and dot-segment handling).
Project API package
projectapi/projectapi.go, projectapi/log.go, projectapi/params.go, projectapi/projects.go, projectapi/secrets.go, projectapi/usage.go, projectapi/service.go, projectapi/service_test.go
New projectapi package: models, param encoders, paginated iterators, Service for projects/secrets/usage, logging helpers, and tests validating routing, encoding, decoding, and iteration.
Bearer client & core client changes
ucare/client.go, ucare/projectapi.go, ucare/projectapi_test.go, ucare/doc.go
Added ucare.NewBearerClient; ucare.Config gains Retry and CDNBase; config resolution updated to accept creds; new project-API HTTP client with retry/Retry-After handling; tests for bearer client and request/response behavior.
Errors
ucare/error.go, ucare/error_test.go
Replaced unexported error types with exported types: APIError, AuthError, ThrottleError, ValidationError, ForbiddenError, plus ProjectAPIError; updated formatting and tests for errors.As semantics.
Retry & backoff
ucare/retry.go, ucare/retry_test.go, ucare/restapi.go, ucare/restapi_test.go, ucare/uploadapi.go, ucare/uploadapi_test.go
Introduced RetryConfig and expBackoff; refactored REST/Upload clients to use configurable, opt-in retries for 429, respect Retry-After and context cancellation, centralized response handling, and added throttle/retry tests.
Upload unified + metadata passthrough
upload/upload.go, upload/upload_test.go, upload/direct.go, upload/multipart.go, upload/fromurl.go, upload/service.go
New unified Upload() selecting direct vs multipart by threshold; UploadParams includes Metadata map[string]string; direct/multipart/from-url params carry metadata; tests validate flow selection and metadata propagation.
Conversion helpers
conversion/path.go, conversion/conversion.go, conversion/path_test.go, conversion/conversion_test.go
Added BuildDocumentPath and BuildVideoPath helpers and conversion.Params.SaveInGroup; tests for path building and params encoding.
Codec/form & query encoding
internal/codec/codec.go, internal/codec/codec_test.go
Query encoder now skips fields without form tag and supports map[string]string encoded as formKey[k]=v for query and multipart form; added/updated tests for metadata encoding.
Group operations
group/group.go, group/service.go, group/group_test.go
Added Group.Delete(ctx, id) and updated Service interface; tests validate DELETE path and 204 handling.
Webhook tests & constants
webhook/service_test.go, webhook/webhook_test.go
Tests for ID-based webhook deletion; unit test validating file lifecycle event constant values.
CDN helpers
ucare/cdn.go, ucare/cdn_test.go
Added CnamePrefix and CDNBaseURL helpers to derive per-project CDN base from public key and unit tests.
Internal config cleanup
internal/config/config.go
Removed exported MaxThrottleRetries constant.
Test infra & integration
test/*, test/testenv/runner.go, test/integration_test.go
Extended test helpers and Runner to include Metadata and Addon services; added integration tests for Project API, metadata, addon flows, group deletion, and updated test call sites to new signatures.
Docs & changelog
CHANGELOG.md, README.md, ucare/doc.go
README and docs updated with Project API bearer-client examples; file.Info examples updated to pass nil params; CHANGELOG updated.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Caller
participant ProjectClient as ProjectAPIClient
participant Remote as ProjectAPI
participant Backoff as BackoffTimer

Caller->>ProjectClient: Do(ctx, req)
ProjectClient->>Remote: HTTP Request (Authorization: Bearer ...)
Remote-->>ProjectClient: 429 + Retry-After?
ProjectClient->>Backoff: compute wait (Retry-After or expBackoff)
Backoff-->>ProjectClient: wait (respect ctx cancellation)
ProjectClient->>Remote: Retry HTTP Request
Remote-->>ProjectClient: 200 OK + JSON
ProjectClient-->>Caller: decoded response

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Phase 6: Project API' directly relates to the main change—adding a new projectapi package and Project API support via NewBearerClient, as evidenced by the extensive projectapi/ files, ucare/client.go, and integration tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase6-project-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (9)
conversion/path.go (2)

56-58: Consider validating required Format field.

Unlike BuildDocumentPath which defaults Format to "pdf", BuildVideoPath uses the format directly. An empty Format would produce a malformed path like "uuid/video/-/format//".

📝 Option: Add validation for empty Format
 func BuildVideoPath(opts VideoPathOptions) string {
+	if opts.Format == "" {
+		// Consider returning an error or setting a default
+	}
 	var b strings.Builder
 	fmt.Fprintf(&b, "%s/video/-/format/%s/", opts.UUID, opts.Format)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@conversion/path.go` around lines 56 - 58, BuildVideoPath currently uses
opts.Format directly and can produce a malformed path when Format is empty;
update the function that consumes VideoPathOptions (BuildVideoPath) to mirror
BuildDocumentPath behavior by defaulting opts.Format to a sensible value (e.g.,
"mp4") when it is empty or validate and return an error upstream; specifically,
inside BuildVideoPath check if opts.Format == "" and set opts.Format = "mp4" (or
alternatively perform validation and fail fast), then continue building the path
so the "%s/video/-/format/%s/" segment never contains an empty format string.

71-73: Silent failure when only one cut parameter is provided.

If a caller provides only CutStart or only CutLength, both are silently ignored. This could lead to unexpected behavior where the user expects a cut operation but gets the full video.

Consider either documenting this behavior clearly, or returning an error when the parameters are partially specified.

📝 Option: Add validation or document the behavior

If validation is preferred:

+// ErrPartialCutParams is returned when only one of CutStart or CutLength is provided.
+var ErrPartialCutParams = errors.New("both CutStart and CutLength must be provided for cut operation")
+
 // BuildVideoPath constructs a video conversion path.
-func BuildVideoPath(opts VideoPathOptions) string {
+func BuildVideoPath(opts VideoPathOptions) (string, error) {
+	if (opts.CutStart != "" && opts.CutLength == "") || (opts.CutStart == "" && opts.CutLength != "") {
+		return "", ErrPartialCutParams
+	}

Alternatively, add a doc comment on VideoPathOptions explaining that both CutStart and CutLength must be set together.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@conversion/path.go` around lines 71 - 73, The code that builds the cut
segment ignores CutStart/CutLength when only one is set; add validation in the
path-construction function that consumes VideoPathOptions (the function that
currently checks opts.CutStart and opts.CutLength) to detect partial
specification: if one of opts.CutStart or opts.CutLength is non-empty while the
other is empty, return an error (or a well-defined sentinel) instead of silently
omitting the cut, and update callers to handle that error; alternatively (if you
prefer non-failing behavior) add a doc comment to the VideoPathOptions type
clearly stating that CutStart and CutLength must be provided together.
README.md (1)

180-181: Minor: Inconsistent line break formatting.

Line 180 ends without the double-space line break ( ) that other links in this section use. This may cause the "Changelog" link to render on the same line as "REST API reference" in some Markdown renderers.

📝 Fix formatting
-[REST API reference](https://uploadcare.com/api-refs/rest-api/?utm_source=github&utm_medium=referral&utm_campaign=uploadcare-go)
+[REST API reference](https://uploadcare.com/api-refs/rest-api/?utm_source=github&utm_medium=referral&utm_campaign=uploadcare-go)  

(add two trailing spaces)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 180 - 181, The markdown links "REST API reference"
and "Changelog" are missing a trailing double-space line break between them;
update the README.md so the line containing the "REST API reference" link ends
with two spaces (add "  " after the URL) so "Changelog" renders on the next
line; locate the lines containing the "REST API reference" and "Changelog" link
texts to apply the fix.
ucare/cdn.go (1)

16-22: Consider checking SetString return value for defensive coding.

While hexStr from hex.EncodeToString is always valid hex, ignoring the boolean return from SetString could mask bugs if this code is refactored later.

🛡️ Proposed defensive fix
 func CnamePrefix(pubkey string) string {
 	hash := sha256.Sum256([]byte(pubkey))
 	hexStr := hex.EncodeToString(hash[:])
 	n := new(big.Int)
-	n.SetString(hexStr, 16)
+	if _, ok := n.SetString(hexStr, 16); !ok {
+		panic("invalid hex from sha256 hash")
+	}
 	return n.Text(36)[:10]
 }

Note: Uploadcare's actual CDN subdomain generation algorithm is not publicly documented, so this implementation cannot be verified against their official specification. The algorithm appears to be a custom implementation specific to this codebase.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ucare/cdn.go` around lines 16 - 22, The CnamePrefix function currently
ignores the boolean return of n.SetString so add defensive handling: capture the
bool result from n.SetString(hexStr, 16) and if it returns false, handle the
error path (e.g., return a safe fallback such as the first 10 chars of hexStr or
log/return an error) instead of proceeding; update the code around CnamePrefix
and the n.SetString call to check the return value and return the chosen
fallback when parsing fails.
test/integration_test.go (1)

110-113: Include root-cause details when project discovery fails.

At Line 112, the fatal message collapses two distinct failures (request error vs empty results), which makes debugging CI failures harder.

🔧 Suggested improvement
 	list, err := svc.List(context.Background(), nil)
-	if err != nil || len(list.Results) == 0 {
-		t.Fatal("cannot list projects to get pub_key for further tests")
-	}
+	if err != nil {
+		t.Fatalf("cannot list projects to get pub_key for further tests: %v", err)
+	}
+	if len(list.Results) == 0 {
+		t.Fatal("cannot list projects to get pub_key for further tests: no projects returned")
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/integration_test.go` around lines 110 - 113, The current test collapses
request errors and empty responses into one generic t.Fatal; update the svc.List
call handling so you first check and fail on a non-nil err (use t.Fatalf and
include the err value and any response metadata), and only after that assert
len(list.Results) > 0 and fail with a clear message that includes the actual
list or its length; target the svc.List call and the t.Fatal/t.Fatalf invocation
so the failure distinguishes request error vs empty results.
projectapi/usage.go (1)

30-46: Consider validating the metric parameter.

The metric parameter is interpolated directly into the URL path. While the comment documents valid values ("traffic", "storage", "operations"), there's no validation to prevent misuse. Invalid metrics would be rejected server-side, but client-side validation provides faster feedback.

Optional: Add metric validation
// Metric constants for GetUsageMetric
const (
	MetricTraffic    = "traffic"
	MetricStorage    = "storage"
	MetricOperations = "operations"
)

Then optionally validate in GetUsageMetric or document that validation is deferred to the server.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projectapi/usage.go` around lines 30 - 46, GetUsageMetric interpolates the
metric into the path without validating it; add constants (MetricTraffic,
MetricStorage, MetricOperations) and validate the metric at the start of
GetUsageMetric (or expose a helper isValidMetric) and return an error (e.g.,
fmt.Errorf("invalid metric: %q", metric)) if it is not one of the allowed values
before calling s.svc.ResourceOp to avoid constructing invalid URLs.
test/addon.go (1)

18-18: Use assert.NoError for error assertions.

Using assert.Equal(t, nil, err) works but is not idiomatic. assert.NoError(t, err) provides clearer error messages when assertions fail and is the recommended pattern in testify.

Suggested fix
-	assert.Equal(t, nil, err)
+	assert.NoError(t, err)

Apply similarly at line 27.

Also applies to: 27-27

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/addon.go` at line 18, Replace the non-idiomatic error assertions using
assert.Equal(t, nil, err) with assert.NoError(t, err) in the test file so
failures report clearer error messages; update the two occurrences (the one at
line with the original diff and the similar occurrence around line 27) to use
assert.NoError(t, err).
test/metadata.go (1)

14-14: Prefer assert.NoError over assert.Equal(t, nil, err).

The assert.NoError(t, err) idiom provides better failure messages and is more idiomatic with testify. It's also consistent with test/projectapi.go which uses assert.NoError.

♻️ Suggested refactor
 func metadataSet(t *testing.T, r *testenv.Runner) {
 	ctx := context.Background()
 	val, err := r.Metadata.Set(ctx, r.Artifacts.Files[0].ID, "test_key", "test_value")
-	assert.Equal(t, nil, err)
+	assert.NoError(t, err)
 	assert.Equal(t, "test_value", val)
 }

 func metadataGet(t *testing.T, r *testenv.Runner) {
 	ctx := context.Background()
 	val, err := r.Metadata.Get(ctx, r.Artifacts.Files[0].ID, "test_key")
-	assert.Equal(t, nil, err)
+	assert.NoError(t, err)
 	assert.Equal(t, "test_value", val)
 }

 func metadataList(t *testing.T, r *testenv.Runner) {
 	ctx := context.Background()
 	data, err := r.Metadata.List(ctx, r.Artifacts.Files[0].ID)
-	assert.Equal(t, nil, err)
+	assert.NoError(t, err)
 	assert.Equal(t, "test_value", data["test_key"])
 }

 func metadataDelete(t *testing.T, r *testenv.Runner) {
 	ctx := context.Background()
 	err := r.Metadata.Delete(ctx, r.Artifacts.Files[0].ID, "test_key")
-	assert.Equal(t, nil, err)
+	assert.NoError(t, err)

Also applies to: 21-21, 28-28, 35-35

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/metadata.go` at line 14, Replace occurrences of the non-idiomatic
assertion assert.Equal(t, nil, err) with assert.NoError(t, err); locate all uses
of the exact call assert.Equal(t, nil, err) (including the ones referenced at
lines showing assert.Equal) and change them to assert.NoError to get better
failure messages and match the existing test style (e.g., as used in
test/projectapi.go).
ucare/restapi.go (1)

58-63: Retry replay shouldn't mutate the live request.

req.GetBody() currently re-runs data.EncodeReq(req) against the same *http.Request. Any encoder that appends query params or headers will accumulate state on attempt 2+, so the retried request may not match the original one byte-for-byte. Prefer replaying from a cloned request or a snapshotted encoded payload.

Also applies to: 80-85, 101-108

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ucare/restapi.go` around lines 58 - 63, getBodyBuilder currently calls
data.EncodeReq(req) on each replay which mutates the original *http.Request;
change it so EncodeReq is only run once and replay uses an immutable snapshot:
call data.EncodeReq(req) a single time into a buffer (or clone the request
first), capture the encoded payload and any headers/query state, then return a
closure that produces a fresh io.ReadCloser each attempt (e.g., a new
bytes.Reader/io.NopCloser over a copy of the buffer) and reapply any
non-idempotent headers/query additions from the snapshot rather than re-running
EncodeReq; apply the same pattern to the other request-replay helpers referenced
(the functions around the other diff regions).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@file/service.go`:
- Line 21: The file.Service interface change is breaking; restore the original
method signature Info(ctx context.Context, id string) (Info, error) and add a
new non-breaking method such as InfoWithParams(ctx context.Context, id string,
params *InfoParams) (Info, error) (or InfoWithOptions) to support the optional
params. Update the file.Service interface to include both Info and
InfoWithParams, then update any concrete implementations (types that implement
file.Service) to implement both methods (have Info call InfoWithParams(ctx, id,
nil) or vice versa), and update callers to use the new InfoWithParams when they
need params while leaving existing calls to Info unchanged.

In `@projectapi/projects.go`:
- Around line 53-67: The Update method on service (func (s service) Update(...))
calls a non-existent Uploadcare Project API endpoint (using projectPathFmt and
UpdateProjectParams); remove this method from the service API or refactor it to
return an explicit unsupported-operation error (e.g., ErrUnsupportedOperation)
and update any callers to handle that error; ensure references to projectPathFmt
and UpdateProjectParams are cleaned up if you delete the method, or replace the
implementation with a clear error-returning stub and documentation comment
stating project updates must be done via the Uploadcare Dashboard.

In `@ucare/restapi.go`:
- Around line 101-110: The debug log currently uses log.Debugf("making %d
request: %+v", tries, req) which prints the full http.Request (including Headers
and Authorization) and can leak credentials; change this to log only
non-sensitive request attributes (e.g., method, URL, ContentLength, and
optionally Host) and do NOT include req.Header or %+v; update the retry loop
around req/GetBody/Body (the variables in this block) to use the new safer log
call so retries no longer emit headers or token data.

---

Nitpick comments:
In `@conversion/path.go`:
- Around line 56-58: BuildVideoPath currently uses opts.Format directly and can
produce a malformed path when Format is empty; update the function that consumes
VideoPathOptions (BuildVideoPath) to mirror BuildDocumentPath behavior by
defaulting opts.Format to a sensible value (e.g., "mp4") when it is empty or
validate and return an error upstream; specifically, inside BuildVideoPath check
if opts.Format == "" and set opts.Format = "mp4" (or alternatively perform
validation and fail fast), then continue building the path so the
"%s/video/-/format/%s/" segment never contains an empty format string.
- Around line 71-73: The code that builds the cut segment ignores
CutStart/CutLength when only one is set; add validation in the path-construction
function that consumes VideoPathOptions (the function that currently checks
opts.CutStart and opts.CutLength) to detect partial specification: if one of
opts.CutStart or opts.CutLength is non-empty while the other is empty, return an
error (or a well-defined sentinel) instead of silently omitting the cut, and
update callers to handle that error; alternatively (if you prefer non-failing
behavior) add a doc comment to the VideoPathOptions type clearly stating that
CutStart and CutLength must be provided together.

In `@projectapi/usage.go`:
- Around line 30-46: GetUsageMetric interpolates the metric into the path
without validating it; add constants (MetricTraffic, MetricStorage,
MetricOperations) and validate the metric at the start of GetUsageMetric (or
expose a helper isValidMetric) and return an error (e.g., fmt.Errorf("invalid
metric: %q", metric)) if it is not one of the allowed values before calling
s.svc.ResourceOp to avoid constructing invalid URLs.

In `@README.md`:
- Around line 180-181: The markdown links "REST API reference" and "Changelog"
are missing a trailing double-space line break between them; update the
README.md so the line containing the "REST API reference" link ends with two
spaces (add "  " after the URL) so "Changelog" renders on the next line; locate
the lines containing the "REST API reference" and "Changelog" link texts to
apply the fix.

In `@test/addon.go`:
- Line 18: Replace the non-idiomatic error assertions using assert.Equal(t, nil,
err) with assert.NoError(t, err) in the test file so failures report clearer
error messages; update the two occurrences (the one at line with the original
diff and the similar occurrence around line 27) to use assert.NoError(t, err).

In `@test/integration_test.go`:
- Around line 110-113: The current test collapses request errors and empty
responses into one generic t.Fatal; update the svc.List call handling so you
first check and fail on a non-nil err (use t.Fatalf and include the err value
and any response metadata), and only after that assert len(list.Results) > 0 and
fail with a clear message that includes the actual list or its length; target
the svc.List call and the t.Fatal/t.Fatalf invocation so the failure
distinguishes request error vs empty results.

In `@test/metadata.go`:
- Line 14: Replace occurrences of the non-idiomatic assertion assert.Equal(t,
nil, err) with assert.NoError(t, err); locate all uses of the exact call
assert.Equal(t, nil, err) (including the ones referenced at lines showing
assert.Equal) and change them to assert.NoError to get better failure messages
and match the existing test style (e.g., as used in test/projectapi.go).

In `@ucare/cdn.go`:
- Around line 16-22: The CnamePrefix function currently ignores the boolean
return of n.SetString so add defensive handling: capture the bool result from
n.SetString(hexStr, 16) and if it returns false, handle the error path (e.g.,
return a safe fallback such as the first 10 chars of hexStr or log/return an
error) instead of proceeding; update the code around CnamePrefix and the
n.SetString call to check the return value and return the chosen fallback when
parsing fails.

In `@ucare/restapi.go`:
- Around line 58-63: getBodyBuilder currently calls data.EncodeReq(req) on each
replay which mutates the original *http.Request; change it so EncodeReq is only
run once and replay uses an immutable snapshot: call data.EncodeReq(req) a
single time into a buffer (or clone the request first), capture the encoded
payload and any headers/query state, then return a closure that produces a fresh
io.ReadCloser each attempt (e.g., a new bytes.Reader/io.NopCloser over a copy of
the buffer) and reapply any non-idempotent headers/query additions from the
snapshot rather than re-running EncodeReq; apply the same pattern to the other
request-replay helpers referenced (the functions around the other diff regions).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2f9f5796-ffc0-47c3-8818-40c7cacaff53

📥 Commits

Reviewing files that changed from the base of the PR and between 766afd6 and 5617b05.

📒 Files selected for processing (65)
  • CHANGELOG.md
  • README.md
  • addon/addon.go
  • addon/log.go
  • addon/service.go
  • addon/service_test.go
  • conversion/conversion.go
  • conversion/conversion_test.go
  • conversion/path.go
  • conversion/path_test.go
  • file/file.go
  • file/list.go
  • file/service.go
  • file/service_test.go
  • group/group.go
  • group/group_test.go
  • group/service.go
  • internal/codec/codec.go
  • internal/codec/codec_test.go
  • internal/config/config.go
  • metadata/log.go
  • metadata/metadata.go
  • metadata/service.go
  • metadata/service_test.go
  • metadata/validation.go
  • projectapi/log.go
  • projectapi/params.go
  • projectapi/projectapi.go
  • projectapi/projects.go
  • projectapi/secrets.go
  • projectapi/service.go
  • projectapi/service_test.go
  • projectapi/usage.go
  • test/addon.go
  • test/file.go
  • test/group.go
  • test/integration_test.go
  • test/metadata.go
  • test/projectapi.go
  • test/testenv/runner.go
  • test/webhook.go
  • ucare/cdn.go
  • ucare/cdn_test.go
  • ucare/client.go
  • ucare/doc.go
  • ucare/error.go
  • ucare/error_test.go
  • ucare/projectapi.go
  • ucare/projectapi_test.go
  • ucare/restapi.go
  • ucare/restapi_test.go
  • ucare/retry.go
  • ucare/retry_test.go
  • ucare/uploadapi.go
  • ucare/uploadapi_test.go
  • upload/direct.go
  • upload/fromurl.go
  • upload/multipart.go
  • upload/service.go
  • upload/upload.go
  • upload/upload_test.go
  • webhook/service.go
  • webhook/service_test.go
  • webhook/webhook.go
  • webhook/webhook_test.go
💤 Files with no reviewable changes (1)
  • internal/config/config.go

alxgsv added 2 commits March 26, 2026 14:41
Pagination next URLs point to a different host (app.uploadcare.com),
so List and ListSecrets now use codec.ResultBuf iterators that
automatically follow next links. Bearer client fallback routes
unknown hosts through the same auth backend.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
projectapi/projects.go (1)

53-67: ⚠️ Potential issue | 🔴 Critical

Update appears to target a non-existent Project API operation.

Please verify that POST /projects/{pubKey}/ is actually supported. If not, this method should be removed or replaced with an explicit unsupported-operation error, and references in the service interface/docs should be aligned.

Does Uploadcare Project API currently support updating project settings via API?
If yes, provide the exact HTTP method and endpoint path for updating a project by public key.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projectapi/projects.go` around lines 53 - 67, The Update method appears to
call a non-existent API operation (using http.MethodPost to
fmt.Sprintf(projectPathFmt, pubKey)); verify whether the Uploadcare Project API
supports updating project settings and if so confirm the exact HTTP method and
endpoint and update projectPathFmt/HTTP verb accordingly; if the API does not
support this operation remove the Update method (and any UpdateProjectParams
type and references) or change it to return a clear unsupported-operation error,
and update the service interface/docs to match; check symbols: Update,
UpdateProjectParams, projectPathFmt, and svc.ResourceOp to locate and fix or
remove the implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ucare/client.go`:
- Around line 125-134: NewRequest currently returns errNoClient when endpoint
(u.Host) isn't in client.backends, so fallbackDo is never used for cross-host
pagination; update NewRequest to detect a missing backend and, if
client.fallbackDo is set, use the fallback client to build and execute the
request instead of returning errNoClient (e.g., delegate creation/execution to
the same client used by fallbackDo), and ensure NewBearerClient assigns
fallbackDo on the constructed client so cross-host next/previous URLs are routed
through the bearer-auth fallback path; key symbols: client struct, NewRequest,
fallbackDo, NewBearerClient, errNoClient.

---

Duplicate comments:
In `@projectapi/projects.go`:
- Around line 53-67: The Update method appears to call a non-existent API
operation (using http.MethodPost to fmt.Sprintf(projectPathFmt, pubKey)); verify
whether the Uploadcare Project API supports updating project settings and if so
confirm the exact HTTP method and endpoint and update projectPathFmt/HTTP verb
accordingly; if the API does not support this operation remove the Update method
(and any UpdateProjectParams type and references) or change it to return a clear
unsupported-operation error, and update the service interface/docs to match;
check symbols: Update, UpdateProjectParams, projectPathFmt, and svc.ResourceOp
to locate and fix or remove the implementation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54f7a96a-f0d3-4a57-a410-aab15fde2ba3

📥 Commits

Reviewing files that changed from the base of the PR and between d0c6dc8 and 6590292.

📒 Files selected for processing (8)
  • projectapi/projectapi.go
  • projectapi/projects.go
  • projectapi/secrets.go
  • projectapi/service.go
  • projectapi/service_test.go
  • test/integration_test.go
  • test/projectapi.go
  • ucare/client.go
✅ Files skipped from review due to trivial changes (2)
  • projectapi/service_test.go
  • test/projectapi.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • projectapi/secrets.go

Pagination next URLs point to a different host (app.uploadcare.com).
NewRequest was failing with errNoClient before Do/fallbackDo could
run. Add fallbackNewReq to route unknown endpoints through the bearer
auth client for both request construction and execution.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
ucare/projectapi_test.go (1)

12-14: Consider using standard import alias for require package.

The alias assert for github.com/stretchr/testify/require is technically valid but may cause confusion since testify also has an assert package with different semantics (require stops on first failure, assert continues). The common convention is to use the package name directly or alias it as require.

♻️ Suggested change
-	assert "github.com/stretchr/testify/require"
+	"github.com/stretchr/testify/require"

Then update all assert. calls to require. throughout the file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ucare/projectapi_test.go` around lines 12 - 14, Replace the import alias
`assert` for "github.com/stretchr/testify/require" with the standard `require`
name and update all call sites in this test file (e.g., any `assert.` usages) to
`require.` so the semantics match the package (functions like require.NoError,
require.Equal) and avoid confusion with the separate testify `assert` package.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ucare/projectapi_test.go`:
- Around line 12-14: Replace the import alias `assert` for
"github.com/stretchr/testify/require" with the standard `require` name and
update all call sites in this test file (e.g., any `assert.` usages) to
`require.` so the semantics match the package (functions like require.NoError,
require.Equal) and avoid confusion with the separate testify `assert` package.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ed30fa52-6bca-4a82-bcc4-a9335e93fd1a

📥 Commits

Reviewing files that changed from the base of the PR and between 6590292 and 236f3b0.

📒 Files selected for processing (2)
  • ucare/client.go
  • ucare/projectapi_test.go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant