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