Skip to content

fix(error-handling): improve error messages for missing S3 objects and instruction files#149

Merged
texastony merged 5 commits into
stagingfrom
tonyknap/feat-handle-no-object
Apr 6, 2026
Merged

fix(error-handling): improve error messages for missing S3 objects and instruction files#149
texastony merged 5 commits into
stagingfrom
tonyknap/feat-handle-no-object

Conversation

@texastony

Copy link
Copy Markdown

Match Java S3EC behavior when S3 objects or instruction files do not exist:

  • Add early return in event handler when Body is None (failed S3 response)
  • Catch ClientError separately in get_object with "Unable to retrieve object" message
  • Catch ClientError in fetch_instruction_file with instruction-file-specific message
  • Check for None body in process_instruction_file before reading

Tests:

  • Unit: NoSuchKey, AccessDenied, and missing instruction file error wrapping
  • Integration: non-existent object and plain object with missing instruction file

…d instruction files

Match Java S3EC behavior when S3 objects or instruction files do not exist:

- Add early return in event handler when Body is None (failed S3 response)
- Catch ClientError separately in get_object with "Unable to retrieve object" message
- Catch ClientError in fetch_instruction_file with instruction-file-specific message
- Check for None body in process_instruction_file before reading

Tests:
- Unit: NoSuchKey, AccessDenied, and missing instruction file error wrapping
- Integration: non-existent object and plain object with missing instruction file
@texastony texastony requested a review from kessplas March 6, 2026 16:34

@texastony texastony left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Blocking

Comment on lines +192 to +198
plain_s3.put_object(Bucket=bucket, Key="plain-object-no-instruction-file", Body=b"hello")

try:
with pytest.raises(S3EncryptionClientError, match="fetching Instruction File"):
s3ec.get_object(Bucket=bucket, Key="plain-object-no-instruction-file")
finally:
plain_s3.delete_object(Bucket=bucket, Key="plain-object-no-instruction-file")

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Needs a UUID.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done — test key now uses uuid.uuid4() to avoid collisions. (028de0b)

Comment thread src/s3_encryption/__init__.py Outdated
raise S3EncryptionClientError(
"Exception encountered while fetching Instruction File."
" Ensure the object you are attempting to decrypt has been encrypted"
" using the S3 Encryption Client and instruction files are enabled."

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.

I don't know if I agree with this change;
if I'm reading the code correctly,
an instruction file which exists but has no body
would be considered invalid JSON during the parsing stage.
That is more specific and feels more descriptive than this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I looked at this again and decided it was best to handle the none case here, while the request handler is dealing with the instruction file as the context, as compared to in the JSON parsing, where the context has moved back to the actual object.

Comment thread src/s3_encryption/__init__.py
Comment thread src/s3_encryption/__init__.py Outdated
raise
except ClientError as e:
# Wrap S3 service errors (e.g., NoSuchKey) with context
raise S3EncryptionClientError(f"Unable to retrieve object: {str(e)}") from e

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.

nit/suggestion: on line 300 below, maybe we should say:

            raise S3EncryptionClientError(f"Failed to retrieve and/or decrypt object: {str(e)}") from e

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in 99a707f

Comment thread src/s3_encryption/instruction_file.py Outdated
texastony added a commit that referenced this pull request Mar 17, 2026
@texastony texastony force-pushed the tonyknap/feat-handle-no-object branch from 028de0b to 99a707f Compare March 17, 2026 20:23

@kessplas kessplas 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.

Error text is blocking.

# If Body is None, the S3 request failed (e.g., NoSuchKey).
# Return early and let boto3 raise the original error.
if parsed.get("Body") is None:
if parsed.get("Body", None) is None:

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.

I don't care to enough to quibble about this more than in this comment, so leave it if you want but, it's redundant given get() will return None by default anyway. Or is this a linter thing?

instruction_key = getattr(self._context, _CTX_KEY, None)

body = parsed.get("Body")
body = parsed.get("Body", None)

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.

ditto, redundant None

Comment thread src/s3_encryption/instruction_file.py Outdated
Comment on lines -103 to -104
" Ensure the object you are attempting to decrypt has been encrypted"
" using the S3 Encryption Client and instruction files are enabled."

@kessplas kessplas Mar 23, 2026

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.

Ah, sorry, we absolutely must keep this part:

Ensure the object you are attempting to decrypt has been encrypted using the S3 Encryption Client. 

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Addressed in ea46039

@kessplas kessplas 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.

LGTM but there are merge conflicts

@texastony texastony merged commit 52d05f3 into staging Apr 6, 2026
5 checks passed
@texastony texastony deleted the tonyknap/feat-handle-no-object branch April 6, 2026 21:53
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.

2 participants