Skip to content

feat(gax): include upload-status header in resumable upload command response objects - #14420

Draft
whowes wants to merge 1 commit into
whowes/resumable-upload-error-classificationfrom
whowes/resumable-upload-status-header
Draft

whowes wants to merge 1 commit into
whowes/resumable-upload-error-classificationfrom
whowes/resumable-upload-status-header

Conversation

@whowes

@whowes whowes commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

This is needed to meet the requirement that the protocol implementation inspect responses for the presence of this header, responding differently in its absence depending on the command (e.g. treat as transient error for start commands, recoverable error for upload, fatal error for query)

@whowes
whowes force-pushed the whowes/resumable-upload-status-header branch from c150a47 to 755a631 Compare September 17, 2026 22:08
gemini-code-assist[bot]

This comment was marked as outdated.

@whowes
whowes added this pull request to stack #14429 September 17, 2026 22:16
@whowes
whowes force-pushed the whowes/resumable-upload-status-header branch from 755a631 to aed4f4b Compare September 18, 2026 02:23
@whowes
whowes force-pushed the whowes/resumable-upload-status-header branch from aed4f4b to 2f4434b Compare September 18, 2026 03:21
@whowes
whowes force-pushed the whowes/resumable-upload-status-header branch from 2f4434b to 445db61 Compare September 18, 2026 15:04
@whowes
whowes force-pushed the whowes/resumable-upload-status-header branch from 445db61 to 86a18dd Compare September 19, 2026 01:36
@whowes
whowes force-pushed the whowes/resumable-upload-status-header branch 2 times, most recently from 429933c to 628d442 Compare September 19, 2026 22:57
@whowes whowes changed the title feat(gax): add upload-status header plumbing feat(gax): propagate upload-status header via resumable upload command responses Sep 20, 2026
@whowes whowes changed the title feat(gax): propagate upload-status header via resumable upload command responses feat(gax): include upload-status header in resumable upload command response objects Sep 20, 2026
@whowes
whowes removed this pull request from stack #14429 September 20, 2026 07:20
@whowes
whowes added this pull request to stack #14454 September 20, 2026 07:21
Add nullable getUploadStatus() accessors to ChunkUploadResponse,
QueryStatusResponse, and ResumableUploadSession, and plumb the
X-Goog-Upload-Status response header through the HTTP/JSON callables.

On HTTP 200 chunk responses where X-Goog-Upload-Status is absent,
return a ChunkUploadResponse with a null uploadStatus rather than
throwing a wire-level exception, allowing higher-level upload
coordinators to classify the missing header and trigger protocol
recovery.

Existing test uploadChunk_missingUploadStatusHeader_throwsInternalException
was updated to uploadChunk_missingUploadStatusHeader_returnsNullUploadStatusOnHttp200
to reflect that the missing status header on HTTP 200 is now surfaced via
a null status property on ChunkUploadResponse instead of throwing an
InternalException at the transport layer.

Note: No end-to-end integration test is included because the test
server always returns the X-Goog-Upload-Status header on success,
making header absence uninjectable end-to-end.
@whowes
whowes force-pushed the whowes/resumable-upload-status-header branch from 628d442 to 4ee20f2 Compare September 20, 2026 07:45
@whowes

whowes commented Sep 20, 2026

Copy link
Copy Markdown
Contributor Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request replaces the boolean 'isComplete' flag in 'ChunkUploadResponse' and 'QueryStatusResponse' with a new 'ResumableUploadStatus' enum to represent the upload session status ('ACTIVE', 'FINAL', 'CANCELLED', 'UNKNOWN'). It updates the chunk and query status callables, improves error handling for cancelled or rejected uploads, and updates the corresponding tests. The review feedback suggests simplifying the creation of 'FAILED_PRECONDITION_STATUS_CODE' by using the existing 'HttpJsonStatusCode' class instead of an anonymous class, and combining the high-level context message with the cause's message in 'createServerRejectionException' to preserve debugging details.

Comment on lines +223 to +234
private static final StatusCode FAILED_PRECONDITION_STATUS_CODE =
new StatusCode() {
@Override
public Code getCode() {
return Code.FAILED_PRECONDITION;
}

@Override
public @Nullable Integer getTransportCode() {
return null;
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Instead of creating a verbose anonymous class implementing StatusCode, you can use the existing HttpJsonStatusCode class which is already available in this package. This simplifies the code and improves maintainability.

  private static final StatusCode FAILED_PRECONDITION_STATUS_CODE =
      HttpJsonStatusCode.of(StatusCode.Code.FAILED_PRECONDITION);

Comment on lines +236 to +245
static Throwable createServerRejectionException(
int statusCode, ResumableUploadStatus uploadStatus, HttpJsonMetadata trailers) {
Throwable cause = trailers.getException();
String message = "Upload " + uploadStatus + " by server with status code: " + statusCode;
if (cause != null && cause.getMessage() != null) {
message = cause.getMessage();
}
return ApiExceptionFactory.createException(
message, cause, FAILED_PRECONDITION_STATUS_CODE, /* retryable= */ false);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

When creating the server rejection exception, if a cause with a message is present, the current implementation completely overwrites the high-level context message (which contains the uploadStatus and statusCode). It is better to append or combine the cause's message to preserve both the high-level context and the low-level error details for better debuggability.

Suggested change
static Throwable createServerRejectionException(
int statusCode, ResumableUploadStatus uploadStatus, HttpJsonMetadata trailers) {
Throwable cause = trailers.getException();
String message = "Upload " + uploadStatus + " by server with status code: " + statusCode;
if (cause != null && cause.getMessage() != null) {
message = cause.getMessage();
}
return ApiExceptionFactory.createException(
message, cause, FAILED_PRECONDITION_STATUS_CODE, /* retryable= */ false);
}
static Throwable createServerRejectionException(
int statusCode, ResumableUploadStatus uploadStatus, HttpJsonMetadata trailers) {
Throwable cause = trailers.getException();
String message = "Upload " + uploadStatus + " by server with status code: " + statusCode;
if (cause != null && cause.getMessage() != null) {
message += ": " + cause.getMessage();
}
return ApiExceptionFactory.createException(
message, cause, FAILED_PRECONDITION_STATUS_CODE, /* retryable= */ false);
}

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed for 'gapic-generator-java-root'

Failed conditions
70.8% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed for 'gapic-generator-java-root'

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

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