Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions google/cloud/storage/google_cloud_cpp_storage.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ google_cloud_cpp_storage_hdrs = [
"oauth2/refreshing_credentials_wrapper.h",
"oauth2/service_account_credentials.h",
"object_access_control.h",
"object_contexts.h",
"object_metadata.h",
"object_read_stream.h",
"object_retention.h",
Expand Down Expand Up @@ -251,6 +252,7 @@ google_cloud_cpp_storage_srcs = [
"oauth2/refreshing_credentials_wrapper.cc",
"oauth2/service_account_credentials.cc",
"object_access_control.cc",
"object_contexts.cc",
"object_metadata.cc",
"object_read_stream.cc",
"object_retention.cc",
Expand Down
2 changes: 2 additions & 0 deletions google/cloud/storage/google_cloud_cpp_storage.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ add_library(
oauth2/service_account_credentials.h
object_access_control.cc
object_access_control.h
object_contexts.cc
object_contexts.h
object_metadata.cc
object_metadata.h
object_read_stream.cc
Expand Down
55 changes: 55 additions & 0 deletions google/cloud/storage/internal/object_metadata_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,29 @@ void SetIfNotEmpty(nlohmann::json& json, char const* key,
json[key] = value;
}

/**
* Populates the "contexts" field in the JSON object from the given metadata.
*/
void SetJsonContextsIfNotEmpty(nlohmann::json& json,
ObjectMetadata const& meta) {
if (!meta.has_contexts()) {
return;
}

nlohmann::json custom_json;
for (auto const& kv : meta.contexts().custom) {
custom_json[kv.first] = nlohmann::json{
{"value", kv.second.value},
{"createTime",
google::cloud::internal::FormatRfc3339(kv.second.create_time)},
{"updateTime",
google::cloud::internal::FormatRfc3339(kv.second.update_time)},
};
}

json["contexts"] = nlohmann::json{{"custom", std::move(custom_json)}};
}

Status ParseAcl(ObjectMetadata& meta, nlohmann::json const& json) {
auto i = json.find("acl");
if (i == json.end()) return Status{};
Expand Down Expand Up @@ -160,6 +183,33 @@ Status ParseRetention(ObjectMetadata& meta, nlohmann::json const& json) {
return Status{};
}

Status ParseContexts(ObjectMetadata& meta, nlohmann::json const& json) {
auto f_contexts = json.find("contexts");
if (f_contexts == json.end()) return Status{};

auto f_custom = f_contexts->find("custom");
if (f_custom == f_contexts->end()) return Status{};

ObjectContexts contexts;
for (auto const& kv : f_custom->items()) {
ObjectCustomContextPayload payload;
auto value = kv.value().value("value", "");
payload.value = value;

auto create_time = internal::ParseTimestampField(kv.value(), "createTime");
if (!create_time) return std::move(create_time).status();
payload.create_time = *create_time;

auto update_time = internal::ParseTimestampField(kv.value(), "updateTime");
if (!update_time) return std::move(update_time).status();
payload.update_time = *update_time;

contexts.custom.emplace(kv.key(), std::move(payload));
}
meta.set_contexts(std::move(contexts));
return Status{};
}

Status ParseSize(ObjectMetadata& meta, nlohmann::json const& json) {
auto v = internal::ParseUnsignedLongField(json, "size");
if (!v) return std::move(v).status();
Expand Down Expand Up @@ -296,6 +346,7 @@ StatusOr<ObjectMetadata> ObjectMetadataParser::FromJson(
ParseOwner,
ParseRetentionExpirationTime,
ParseRetention,
ParseContexts,
[](ObjectMetadata& meta, nlohmann::json const& json) {
return SetStringField(meta, json, "selfLink",
&ObjectMetadata::set_self_link);
Expand Down Expand Up @@ -372,6 +423,8 @@ nlohmann::json ObjectMetadataJsonForCompose(ObjectMetadata const& meta) {
meta.retention().retain_until_time)}};
}

SetJsonContextsIfNotEmpty(metadata_as_json, meta);

return metadata_as_json;
}

Expand Down Expand Up @@ -430,6 +483,8 @@ nlohmann::json ObjectMetadataJsonForUpdate(ObjectMetadata const& meta) {
meta.retention().retain_until_time)}};
}

SetJsonContextsIfNotEmpty(metadata_as_json, meta);

return metadata_as_json;
}

Expand Down
46 changes: 46 additions & 0 deletions google/cloud/storage/object_contexts.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "google/cloud/storage/object_contexts.h"
#include "google/cloud/internal/format_time_point.h"
#include <iostream>

namespace google {
namespace cloud {
namespace storage {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

std::ostream& operator<<(std::ostream& os,
ObjectCustomContextPayload const& rhs) {
return os << "ObjectCustomContextPayload={value=" << rhs.value
<< ", create_time="
<< google::cloud::internal::FormatRfc3339(rhs.create_time)
<< ", update_time="
<< google::cloud::internal::FormatRfc3339(rhs.update_time) << "}";
}

std::ostream& operator<<(std::ostream& os, ObjectContexts const& rhs) {
os << "ObjectContexts={custom={";
char const* sep = "";
for (auto const& kv : rhs.custom) {
os << sep << kv.first << "=" << kv.second;
sep = ",\n";
}
return os << "}}";
}

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace storage
} // namespace cloud
} // namespace google
94 changes: 94 additions & 0 deletions google/cloud/storage/object_contexts.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_OBJECT_CONTEXTS_H
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_OBJECT_CONTEXTS_H

#include "google/cloud/storage/version.h"
#include <chrono>
#include <map>
#include <string>

namespace google {
namespace cloud {
namespace storage {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN

/**
* Represents the payload of a user-defined object context.
*/
struct ObjectCustomContextPayload {
std::string value;

std::chrono::system_clock::time_point create_time;

std::chrono::system_clock::time_point update_time;
};

inline bool operator==(ObjectCustomContextPayload const& lhs,
ObjectCustomContextPayload const& rhs) {
return std::tie(lhs.value, lhs.create_time, lhs.update_time) ==
std::tie(rhs.value, rhs.create_time, rhs.update_time);
};

inline bool operator!=(ObjectCustomContextPayload const& lhs,
ObjectCustomContextPayload const& rhs) {
return !(lhs == rhs);
}

std::ostream& operator<<(std::ostream& os,
ObjectCustomContextPayload const& rhs);

/**
* Specifies the custom contexts of an object.
*/
struct ObjectContexts {
/**
* Represents the map of user-defined object contexts, keyed by a string
* value.
*/
std::map<std::string, ObjectCustomContextPayload> custom;

/**
* A set of helper functions to handle the custom.
*/
bool has_custom(std::string const& key) const {
return custom.end() != custom.find(key);
}
ObjectCustomContextPayload const& get_custom(std::string const& key) const {
return custom.at(key);
}
void upsert_custom(std::string const& key,
ObjectCustomContextPayload const& value) {
custom[key] = value;
}
void delete_custom(std::string const& key) { custom.erase(key); }
};

inline bool operator==(ObjectContexts const& lhs, ObjectContexts const& rhs) {
return lhs.custom == rhs.custom;
};

inline bool operator!=(ObjectContexts const& lhs, ObjectContexts const& rhs) {
return !(lhs == rhs);
}

std::ostream& operator<<(std::ostream& os, ObjectContexts const& rhs);

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace storage
} // namespace cloud
} // namespace google

#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_STORAGE_OBJECT_CONTEXTS_H
31 changes: 31 additions & 0 deletions google/cloud/storage/object_metadata.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ bool operator==(ObjectMetadata const& lhs, ObjectMetadata const& rhs) {
&& lhs.updated_ == rhs.updated_ //
&& lhs.soft_delete_time_ == rhs.soft_delete_time_ //
&& lhs.hard_delete_time_ == rhs.hard_delete_time_ //
&& lhs.contexts_ == rhs.contexts_ //
;
}

Expand Down Expand Up @@ -133,6 +134,11 @@ std::ostream& operator<<(std::ostream& os, ObjectMetadata const& rhs) {
if (rhs.has_hard_delete_time()) {
os << ", hard_delete_time=" << FormatRfc3339(rhs.hard_delete_time());
}

if (rhs.has_contexts()) {
os << ", contexts=" << rhs.contexts();
}

return os << "}";
}

Expand Down Expand Up @@ -271,6 +277,31 @@ ObjectMetadataPatchBuilder& ObjectMetadataPatchBuilder::ResetMetadata() {
return *this;
}

ObjectMetadataPatchBuilder& ObjectMetadataPatchBuilder::SetContexts(
ObjectContexts const& tp) {
internal::PatchBuilder custom_subpatch;
for (auto const& pair : tp.custom) {
custom_subpatch.AddSubPatch(
pair.first.c_str(),
internal::PatchBuilder()
.SetStringField("value", pair.second.value)
.SetStringField(
"createTime",
google::cloud::internal::FormatRfc3339(pair.second.create_time))
.SetStringField("updateTime",
google::cloud::internal::FormatRfc3339(
pair.second.update_time)));
}
impl_.AddSubPatch("contexts", internal::PatchBuilder().AddSubPatch(
"custom", custom_subpatch));
return *this;
}

ObjectMetadataPatchBuilder& ObjectMetadataPatchBuilder::ResetContexts() {
impl_.RemoveField("contexts");
return *this;
}

ObjectMetadataPatchBuilder& ObjectMetadataPatchBuilder::SetTemporaryHold(
bool v) {
impl_.SetBoolField("temporaryHold", v);
Expand Down
28 changes: 28 additions & 0 deletions google/cloud/storage/object_metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "google/cloud/storage/internal/complex_option.h"
#include "google/cloud/storage/object_access_control.h"
#include "google/cloud/storage/object_contexts.h"
#include "google/cloud/storage/object_retention.h"
#include "google/cloud/storage/owner.h"
#include "google/cloud/storage/version.h"
Expand Down Expand Up @@ -450,6 +451,29 @@ class ObjectMetadata {
return *this;
}

/// Returns `true` if the object has user-defined contexts.
bool has_contexts() const { return contexts_.has_value(); }

/**
* The object's user custom contexts.
*
* It is undefined behavior to call this member function if
* `has_contexts() == false`.
*/
ObjectContexts const& contexts() const { return *contexts_; }

/// Change or set the object's custom contexts.
ObjectMetadata& set_contexts(ObjectContexts v) {
contexts_ = std::move(v);
return *this;
}

/// Reset the object's custom contexts.
ObjectMetadata& reset_contexts() {
contexts_.reset();
return *this;
}

/// An HTTPS link to the object metadata.
std::string const& self_link() const { return self_link_; }

Expand Down Expand Up @@ -612,6 +636,7 @@ class ObjectMetadata {
std::string md5_hash_;
std::string media_link_;
std::map<std::string, std::string> metadata_;
absl::optional<ObjectContexts> contexts_;
std::string name_;
absl::optional<Owner> owner_;
std::chrono::system_clock::time_point retention_expiration_time_;
Expand Down Expand Up @@ -675,6 +700,9 @@ class ObjectMetadataPatchBuilder {
ObjectMetadataPatchBuilder& ResetMetadata(std::string const& key);
ObjectMetadataPatchBuilder& ResetMetadata();

ObjectMetadataPatchBuilder& SetContexts(ObjectContexts const& tp);
ObjectMetadataPatchBuilder& ResetContexts();

ObjectMetadataPatchBuilder& SetTemporaryHold(bool v);
ObjectMetadataPatchBuilder& ResetTemporaryHold();

Expand Down
Loading