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