userver: /data/code/userver/libraries/s3api/src/s3api/models/multipart_upload/responses.cpp Source File
Loading...
Searching...
No Matches
responses.cpp
1#include <userver/s3api/models/multipart_upload/responses.hpp>
2
3#include <fmt/format.h>
4#include <pugixml.hpp>
5
6#include <userver/s3api/models/errors.hpp>
7#include <userver/utils/datetime.hpp>
8#include <userver/utils/from_string.hpp>
9
10USERVER_NAMESPACE_BEGIN
11
12namespace s3api::multipart_upload {
13namespace {
14
15pugi::xml_node GetRequiredChildNode(const pugi::xml_document& doc, const char* child_name) {
16 auto child = doc.child(child_name);
17 if (!child) throw ResponseParsingError(fmt::format("document is missing root child node '{}'", child_name));
18 return child;
19}
20
21std::optional<std::string_view>
22ExtractChildValue(const pugi::xml_node& node, const char* child_name, bool is_empty_allowed = true) {
23 const auto child = node.child(child_name);
24 auto result = (!child.empty() ? std::make_optional<std::string_view>(child.child_value()) : std::nullopt);
25 if (!is_empty_allowed && result && result->empty()) {
26 throw ResponseParsingError(fmt::format("node '{}.{}' is not expected to be empty", node.name(), child_name));
27 }
28 return result;
29}
30
31std::string_view ExtractRequiredChildValue(const pugi::xml_node& node, const char* child_name) {
32 const auto result = ExtractChildValue(node, child_name, false);
33 if (!result) {
35 fmt::format("node '{}' is missing the required child node '{}'", node.name(), child_name)
36 );
37 }
38 return *result;
39}
40
41bool ToBoolean(const std::optional<std::string_view>& maybe_str) {
42 if (maybe_str.value_or("false") != "true") return false;
43 return true;
44}
45
46template <typename T>
47std::enable_if_t<std::is_integral_v<T>, std::optional<T>>
48ExtractChildValueAsIntegral(const pugi::xml_node& node, const char* child_name) try {
49 const auto maybe_str = ExtractChildValue(node, child_name, false);
50 if (!maybe_str) return std::nullopt;
51
52 return USERVER_NAMESPACE::utils::FromString<T>(*maybe_str);
53
54} catch (const std::exception& exc) {
56 fmt::format("failed to parse node '{}.{}' value as integral type - {}", node.name(), child_name, exc.what())
57 );
58}
59
60template <typename T>
61std::enable_if_t<std::is_integral_v<T>, T>
62ExtractRequiredChildValueAsIntegral(const pugi::xml_node& node, const char* child_name) {
63 const auto maybe_value = ExtractChildValueAsIntegral<T>(node, child_name);
64 if (!maybe_value) {
66 fmt::format("node '{}' is missing the required child node '{}'", node.name(), child_name)
67 );
68 }
69 return *maybe_value;
70}
71
72constexpr auto ExtractRequiredChildValueAsUInt = ExtractRequiredChildValueAsIntegral<unsigned>;
73constexpr auto ExtractChildValueAsUInt = ExtractChildValueAsIntegral<unsigned>;
74constexpr auto ExtractChildValueAsULong = ExtractChildValueAsIntegral<unsigned long>;
75
76} // namespace
77
80 pugi::xml_document xml;
81 const pugi::xml_parse_result parse_result =
82 xml.load_string(http_s3_respose_body.c_str(), pugi::parse_default | pugi::parse_escapes);
83 if (parse_result.status != pugi::status_ok) {
84 throw ResponseParsingError(parse_result.description());
85 }
86 const auto xml_root_item = GetRequiredChildNode(xml, "InitiateMultipartUploadResult");
87 result.bucket = ExtractRequiredChildValue(xml_root_item, "Bucket");
88 result.upload_id = ExtractRequiredChildValue(xml_root_item, "UploadId");
89 result.key = ExtractRequiredChildValue(xml_root_item, "Key");
90 return result;
91}
92
95 pugi::xml_document xml;
96 const pugi::xml_parse_result parse_result =
97 xml.load_string(http_s3_respose_body.c_str(), pugi::parse_default | pugi::parse_escapes);
98 if (parse_result.status != pugi::status_ok) {
99 throw ResponseParsingError(parse_result.description());
100 }
101 const auto xml_root_item = GetRequiredChildNode(xml, "CompleteMultipartUploadResult");
102 result.location = ExtractRequiredChildValue(xml_root_item, "Location");
103 result.bucket = ExtractRequiredChildValue(xml_root_item, "Bucket");
104 result.key = ExtractRequiredChildValue(xml_root_item, "Key");
105 return result;
106}
107
110 pugi::xml_document xml;
111 const pugi::xml_parse_result parse_result =
112 xml.load_string(http_s3_respose_body.c_str(), pugi::parse_default | pugi::parse_escapes);
113 if (parse_result.status != pugi::status_ok) {
114 throw ResponseParsingError(parse_result.description());
115 }
116
117 const auto xml_root_item = GetRequiredChildNode(xml, "ListMultipartUploadsResult");
118 result.bucket = ExtractRequiredChildValue(xml_root_item, "Bucket");
119 result.key_marker = ExtractChildValue(xml_root_item, "KeyMarker");
120 result.upload_id_marker = ExtractChildValue(xml_root_item, "UploadIdMarker");
121 result.next_key_marker = ExtractChildValue(xml_root_item, "NextKeyMarker");
122 result.next_upload_id_marker = ExtractChildValue(xml_root_item, "NextUploadIdMarker");
123 result.delimiter = ExtractChildValue(xml_root_item, "Delimiter");
124 result.is_truncated = ToBoolean(ExtractChildValue(xml_root_item, "IsTruncated"));
125 result.max_uploads = ExtractChildValueAsUInt(xml_root_item, "MaxUploads");
126
127 for (auto node = xml_root_item.child("Upload"); node; node = node.next_sibling("Upload")) {
129 multipart_upload.key = ExtractRequiredChildValue(node, "Key");
130 multipart_upload.upload_id = ExtractRequiredChildValue(node, "UploadId");
131 if (const auto maybe_initiated_str = ExtractChildValue(node, "Initiated")) {
132 // See S3 client aws-sdk-cpp implementaton references:
133 // https://github.com/aws/aws-sdk-cpp/blob/6762e8220ae37a35c7a8f762f9b97c5d2eda7455/generated/src/aws-cpp-sdk-s3/source/model/MultipartUpload.cpp#L49
134 multipart_upload.initiated_ts =
135 USERVER_NAMESPACE::utils::datetime::GuessStringtime(std::string{*maybe_initiated_str}, "UTC");
136 }
137 result.uploads.push_back(std::move(multipart_upload));
138 }
139
140 for (auto node = xml_root_item.child("CommonPrefixes"); node; node = node.next_sibling("CommonPrefixes")) {
141 result.common_prefixes.emplace_back(ExtractRequiredChildValue(node, "Prefix"));
142 }
143
144 return result;
145}
146
147ListPartsResult ListPartsResult::Parse(utils::zstring_view http_s3_respose_body) {
148 ListPartsResult result;
149 pugi::xml_document xml;
150 const pugi::xml_parse_result parse_result =
151 xml.load_string(http_s3_respose_body.c_str(), pugi::parse_default | pugi::parse_escapes);
152 if (parse_result.status != pugi::status_ok) {
153 throw ResponseParsingError(parse_result.description());
154 }
155
156 const auto xml_root_item = GetRequiredChildNode(xml, "ListPartsResult");
157 result.bucket = ExtractRequiredChildValue(xml_root_item, "Bucket");
158 result.upload_id = ExtractRequiredChildValue(xml_root_item, "UploadId");
159 result.max_parts = ExtractChildValueAsUInt(xml_root_item, "MaxParts");
160 result.part_number_marker = ExtractChildValueAsUInt(xml_root_item, "PartNumberMarker");
161 result.next_part_number_marker = ExtractChildValueAsUInt(xml_root_item, "NextPartNumberMarker");
162 result.key = ExtractRequiredChildValue(xml_root_item, "Key");
163 result.is_truncated = ToBoolean(ExtractChildValue(xml_root_item, "IsTruncated"));
164
165 for (auto part_node = xml_root_item.child("Part"); part_node; part_node = part_node.next_sibling("Part")) {
166 ListPartsResult::Part part;
167 part.etag = ExtractRequiredChildValue(part_node, "ETag");
168 part.part_number = ExtractRequiredChildValueAsUInt(part_node, "PartNumber");
169 part.byte_size = ExtractChildValueAsULong(part_node, "Size");
170
171 if (const auto maybe_last_modified_ts_str = ExtractChildValue(part_node, "LastModified")) {
172 // See S3 client aws-sdk-cpp implementaton references:
173 // https://github.com/aws/aws-sdk-cpp/blob/6762e8220ae37a35c7a8f762f9b97c5d2eda7455/generated/src/aws-cpp-sdk-s3/source/model/MultipartUpload.cpp#L49
174 part.last_modified_ts =
175 USERVER_NAMESPACE::utils::datetime::GuessStringtime(std::string{*maybe_last_modified_ts_str}, "UTC");
176 }
177
178 result.parts.push_back(std::move(part));
179 }
180
181 return result;
182}
183
184} // namespace s3api::multipart_upload
185
186USERVER_NAMESPACE_END