Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
c2a16fb
insert and get contexts perform
salilg-eng Mar 20, 2026
b941e81
Unit Test Cases
salilg-eng Mar 24, 2026
fa985a8
Unit Test Cases
salilg-eng Mar 24, 2026
409e321
Resolve comments
salilg-eng Mar 24, 2026
8f5051f
Follow the checklist and resolved comments
salilg-eng Mar 25, 2026
49d60ad
Follow the checklist and resolved comments
salilg-eng Mar 25, 2026
59732ed
Follow the checklist and resolved comments
salilg-eng Mar 25, 2026
ffc8b00
Fixed gemini related comments
salilg-eng Mar 25, 2026
fbf594e
Fixed gemini related comments
salilg-eng Mar 25, 2026
37b7051
Style code
salilg-eng Mar 25, 2026
e94a00d
Style code
salilg-eng Mar 25, 2026
a563bb4
Personal Review code
salilg-eng Mar 26, 2026
d258bd9
Style set
salilg-eng Mar 26, 2026
711c118
Style set
salilg-eng Mar 26, 2026
d70d66a
Required Changes
salilg-eng Mar 26, 2026
41aec81
Required Changes
salilg-eng Mar 26, 2026
1e86369
Required Changes
salilg-eng Mar 26, 2026
3520f8b
As per new comments changes
salilg-eng Mar 26, 2026
7e433b8
Changes
salilg-eng Mar 27, 2026
248ffe8
Remove old test method
salilg-eng Mar 27, 2026
fdd1e97
Handled System Test cases
salilg-eng Mar 27, 2026
902eb86
Gemini review and style check
salilg-eng Mar 27, 2026
e87efaf
Style Check
salilg-eng Mar 27, 2026
e78441d
Style Check
salilg-eng Mar 27, 2026
5bb84fb
Pending scenario covers
salilg-eng Mar 27, 2026
f31760e
Pending scenario covers
salilg-eng Mar 27, 2026
d1fb39c
Pending scenario covers
salilg-eng Mar 27, 2026
eda39e8
Pending scenario covers
salilg-eng Mar 27, 2026
be06d29
Pending scenario covers
salilg-eng Mar 27, 2026
64b6c01
Remove unwanted delete code
salilg-eng Mar 30, 2026
1241ae6
Remove unwanted delete code
salilg-eng Mar 30, 2026
500b9f2
Add FIle const
salilg-eng Mar 30, 2026
0ed4e1e
Handle more scenario and resolved comments
salilg-eng Apr 1, 2026
3996210
Add more scenarios and also recheck with document
salilg-eng Apr 2, 2026
0e84582
Make a trait for validateContext and use in both bucket and storageOb…
salilg-eng Apr 2, 2026
cfd37d1
Changed code as per gemini review
salilg-eng Apr 2, 2026
54e9eac
Changed code as per gemini review
salilg-eng Apr 2, 2026
b7afe81
Changed code as per gemini review
salilg-eng Apr 2, 2026
b966e67
Changes code as per comments
salilg-eng Apr 7, 2026
c5f7aa8
Style check issue
salilg-eng Apr 7, 2026
6956993
Style check issue
salilg-eng Apr 7, 2026
fe2f2aa
Style check issue
salilg-eng Apr 7, 2026
08fb2de
Review Manage Test
salilg-eng Apr 7, 2026
5d7f458
Overall Completed the Test case only filter in system test is pending
salilg-eng Apr 10, 2026
f8a845d
Add New cases and scenarios
salilg-eng Apr 10, 2026
eac4fd3
Updated code
salilg-eng Apr 13, 2026
b919376
Updated code
salilg-eng Apr 13, 2026
b65b013
Updated code
salilg-eng Apr 13, 2026
6ae7138
Updated code
salilg-eng Apr 13, 2026
60fab43
Updated code
salilg-eng Apr 13, 2026
7a90dad
Updated code
salilg-eng Apr 13, 2026
e4ec9bf
Updated code
salilg-eng Apr 13, 2026
f37252c
Updated Code
salilg-eng Apr 14, 2026
e1a4699
Fixer
salilg-eng Apr 14, 2026
1270cf2
CHanges as per gemini review
salilg-eng Apr 14, 2026
af178b1
CS Fixer
salilg-eng Apr 14, 2026
c4021cb
gemini review
salilg-eng Apr 14, 2026
8eaf4de
gemini review
salilg-eng Apr 14, 2026
51d3e09
Changed according to feedback
salilg-eng Apr 17, 2026
a8a67ff
Final changes code
salilg-eng Apr 17, 2026
fa23edb
Final changes code
salilg-eng Apr 17, 2026
99065d2
Final changes code
salilg-eng Apr 17, 2026
b30316a
Final change and push
salilg-eng Apr 17, 2026
bd9c720
Final change and push
salilg-eng Apr 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 65 additions & 1 deletion Storage/src/Bucket.php
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,18 @@ public function exists(array $options = [])
* @type array $metadata The full list of available options are outlined
* at the [JSON API docs](https://cloud.google.com/storage/docs/json_api/v1/objects/insert#request-body).
* @type array $metadata.metadata User-provided metadata, in key/value pairs.
* @type array $contexts User-defined or system-defined object contexts.
* Each object context is a key-payload pair, where the key provides the
* identification and the payload holds the associated value and additional metadata.
* @type array $contexts.custom Custom user-defined contexts. Keys must start
* with an alphanumeric character and cannot contain double quotes (`"`).
* @type string $contexts.custom.{key}.value The value associated with the context.
* If not empty, must start with an alphanumeric character and cannot contain double quotes (`"`)
* or forward slashes (`/`).
Comment on lines +282 to +284
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The documentation states that the context value "Must start with an alphanumeric character", but the implementation in validateContexts (line 367) explicitly allows an empty string ($val !== ''). If empty strings are permitted, the documentation should be updated to reflect this (e.g., "If not empty, must start with...").

* @type string $contexts.custom.{key}.createTime The time the context
* was created in RFC 3339 format. **(read only)**
* @type string $contexts.custom.{key}.updateTime The time the context
* was last updated in RFC 3339 format. **(read only)**
* @type string $encryptionKey A base64 encoded AES-256 customer-supplied
* encryption key. If you would prefer to manage encryption
* utilizing the Cloud Key Management Service (KMS) please use the
Expand All @@ -294,6 +306,13 @@ public function upload($data, array $options = [])
throw new \InvalidArgumentException('A name is required when data is of type string or null.');
}

if (isset($options['contexts'])) {
if (!is_array($options['contexts'])) {
throw new \InvalidArgumentException('Object contexts must be an array.');
}
$this->validateContexts($options['contexts']);
}
Comment on lines +309 to +314
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The PR description mentions that validateContexts() should throw an InvalidArgumentException if contexts are not arrays. However, because the validateContexts method uses a ?array type hint, passing a non-array value for contexts in the $options array will trigger a TypeError before the method body is even executed. To provide a more descriptive error message and adhere to the intended 'Fail Fast' logic, you should explicitly check if the value is an array before calling the validation method.

        if (isset($options['contexts'])) {
            if (!is_array($options['contexts'])) {
                throw new \InvalidArgumentException('Object contexts must be an array.');
            }
            $this->validateContexts($options['contexts']);
        }

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Okay, That means before throwing an error we need to throw clean message.


$encryptionKey = $options['encryptionKey'] ?? null;
$encryptionKeySHA256 = $options['encryptionKeySHA256'] ?? null;

Expand All @@ -314,6 +333,49 @@ public function upload($data, array $options = [])
);
}

private function validateContexts(?array $contexts)
{
if (!isset($contexts['custom'])) {
return;
}
if (!is_array($contexts['custom'])) {
throw new \InvalidArgumentException('Object contexts custom field must be an array.');
}
foreach ($contexts['custom'] as $key => $data) {
if (!preg_match('/^[a-zA-Z0-9]/', (string) $key)) {
throw new \InvalidArgumentException('Object context key must start with an alphanumeric.');
}
if (strpos((string) $key, '"') !== false) {
throw new \InvalidArgumentException('Object context key cannot contain double quotes.');
}
if ($data === null) {
continue;
}
if (!is_array($data)) {
throw new \InvalidArgumentException(sprintf(
'Context data for key "%s" must be an array.',
$key
));
}
Comment on lines +354 to +359
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The validation logic currently prevents deleting individual context keys by setting them to null. In Cloud Storage, setting a metadata or context key to null in a patch request is the standard way to remove it. While upload is primarily for creation, this validation method should support null values to be reusable for update operations and to avoid blocking valid inputs.

            if ($data === null) {
                continue;
            }
            if (!is_array($data)) {
                throw new \InvalidArgumentException(sprintf(
                    'Context data for key "%s" must be an array.',
                    $key
                ));
            }

if (!isset($data['value'])) {
throw new \InvalidArgumentException(sprintf(
'Context for key "%s" must have a \'value\' property.',
$key
));
}
if (!is_scalar($data['value'])) {
throw new \InvalidArgumentException(sprintf(
'Context value for key "%s" must be a scalar type.',
$key
));
}
$val = (string) $data['value'];
if ($val !== '' && !preg_match('/^[a-zA-Z0-9][^"\/]*$/', $val)) {
throw new \InvalidArgumentException('Object context value must start with an alphanumeric.');
}
Comment on lines +373 to +375
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For consistency with the key validation logic and to provide more specific error messages, consider splitting the value validation into separate checks. The current error message is also incomplete as it doesn't mention the restrictions on double quotes and forward slashes.

            if ($val !== '') {
                if (!preg_match('/^[a-zA-Z0-9]/', $val)) {
                    throw new \InvalidArgumentException('Object context value must start with an alphanumeric.');
                }
                if (preg_match('/["\/]/', $val)) {
                    throw new \InvalidArgumentException('Object context value cannot contain double quotes or forward slashes.');
                }
            }

Comment on lines +373 to +375
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The error message is misleading. If the value starts with an alphanumeric character but contains a forward slash or a double quote, the regex will fail, but the exception message will only mention the alphanumeric requirement. It should be updated to reflect all constraints.

            if ($val !== '' && !preg_match('/^[a-zA-Z0-9][^"\/]*$/', $val)) {
                throw new \InvalidArgumentException('Object context value must start with an alphanumeric and cannot contain double quotes or forward slashes.');
            }

}
}

/**
* Asynchronously uploads an object.
*
Expand Down Expand Up @@ -703,6 +765,9 @@ public function restore($name, $generation, array $options = [])
* distinct results. **Defaults to** `false`.
* @type string $fields Selector which will cause the response to only
* return the specified fields.
* @type string $filter Filter results to include only objects to which the
* specified context is attached. You can filter by the presence,
* absence, or specific value of context keys.
* @type string $matchGlob A glob pattern to filter results. The string
* value must be UTF-8 encoded. See:
* https://cloud.google.com/storage/docs/json_api/v1/objects/list#list-object-glob
Expand All @@ -712,7 +777,6 @@ public function restore($name, $generation, array $options = [])
public function objects(array $options = [])
{
$resultLimit = $this->pluck('resultLimit', $options, false);

return new ObjectIterator(
new ObjectPageIterator(
function (array $object) {
Expand Down
6 changes: 6 additions & 0 deletions Storage/src/Connection/Rest.php
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,12 @@ private function resolveUploadOptions(array $args)
$args['metadata']['retention'] = $args['retention'];
unset($args['retention']);
}
if (isset($args['contexts'])) {
// during object creation context properties are part of the object resource
// and should be included in the request body.
$args['metadata']['contexts'] = $args['contexts'];
unset($args['contexts']);
}
unset($args['name']);
$args['contentType'] = $args['metadata']['contentType']
?? MimeType::fromFilename($args['metadata']['name']);
Expand Down
36 changes: 36 additions & 0 deletions Storage/src/Connection/ServiceDefinition/storage-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,37 @@
"type": "string",
"description": "The modification time of the object metadata in RFC 3339 format. Set initially to object creation time and then updated whenever any metadata of the object changes. This includes changes made by a requester, such as modifying custom metadata, as well as changes made by Cloud Storage on behalf of a requester, such as changing the storage class based on an Object Lifecycle Configuration.",
"format": "date-time"
},
"contexts" : {
"type": "object",
"description": "User-defined or system-defined object contexts. Represented as key-payload pairs, where the key identifies the context and the payload contains the associated value and additional metadata.",
"properties" : {
"custom" : {
"type": "object",
"description": "User-provided object contexts where each entry consists of a unique key and a corresponding payload.",
"additionalProperties": {
"type": "object",
"description": "The payload associated with a user-defined context key.",
"properties": {
"value": {
"type": "string",
"description": "The value of the object contexts.",
"required": true
},
"createTime": {
"type": "string",
"format": "date-time",
"description": "The time at which the object contexts was created in RFC 3339 format."
},
"updateTime": {
"type": "string",
"format": "date-time",
"description": "The time at which the object context was last updated in RFC 3339 format."
}
}
}
}
}
}
}
},
Expand Down Expand Up @@ -4793,6 +4824,11 @@
"required": true,
"location": "path"
},
"filter": {
"type": "string",
"description": "Filter results to include only objects to which the specified context is attached. You can filter by the presence, absence, or specific value of context keys.",
"location": "query"
},
"delimiter": {
"type": "string",
"description": "Returns results in a directory-like mode. items will contain only objects whose names, aside from the prefix, do not contain delimiter. Objects whose names, aside from the prefix, contain delimiter will have their name, truncated after the delimiter, returned in prefixes. Duplicate prefixes are omitted.",
Expand Down
12 changes: 12 additions & 0 deletions Storage/src/StorageObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,18 @@ public function delete(array $options = [])
* This is the retention configuration set for this object.
* @type string $retention.mode The mode of the retention configuration,
* which can be either `"Unlocked"` or `"Locked"`.
* @type array $contexts User-defined or system-defined object contexts.
* Each object context is a key-payload pair, where the key provides the
* identification and the payload holds the associated value and additional metadata.
* @type array $contexts.custom Custom user-defined contexts. Keys must start
* with an alphanumeric character and cannot contain double quotes (`"`).
* @type string $contexts.custom.{key}.value The value associated with the context.
* If not empty, must start with an alphanumeric character and cannot contain double quotes (`"`)
* or forward slashes (`/`).
Comment on lines +239 to +241
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The documentation for the context value here is inconsistent with the one in Bucket.php. It is missing the 'If not empty' qualifier, which is important because the validation logic allows empty strings.

     *     @type string $contexts.custom.{key}.value The value associated with the context.
     *           If not empty, must start with an alphanumeric character and cannot contain double quotes (`"`)
     *           or forward slashes (`/`).

* @type string $contexts.custom.{key}.createTime The time the context
* was created in RFC 3339 format. **(read only)**
* @type string $contexts.custom.{key}.updateTime The time the context
* was last updated in RFC 3339 format. **(read only)**
* @type bool $overrideUnlockedRetention Applicable for objects that
* have an unlocked retention configuration. Required to be set to
* `true` if the operation includes a retention property that
Expand Down
Loading
Loading