Skip to content

Commit 1b05113

Browse files
committed
[simplestreams] Use Boost.JSON for manifest
1 parent 9a90122 commit 1b05113

File tree

1 file changed

+58
-64
lines changed

1 file changed

+58
-64
lines changed

src/simplestreams/simple_streams_manifest.cpp

Lines changed: 58 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919

2020
#include <QFileInfo>
2121
#include <QHash>
22-
#include <QJsonDocument>
23-
#include <QJsonObject>
2422
#include <QSysInfo>
2523

24+
#include <boost/json.hpp>
25+
2626
#include <multipass/constants.h>
2727
#include <multipass/exceptions/manifest_exceptions.h>
2828
#include <multipass/settings/settings.h>
@@ -39,24 +39,22 @@ const QHash<QString, QString> arch_to_manifest{{"x86_64", "amd64"},
3939
{"power64", "ppc64el"},
4040
{"power64le", "ppc64el"}};
4141

42-
QJsonObject parse_manifest(const QByteArray& json)
42+
const boost::json::object* if_contains_object(const boost::json::value& data, std::string_view key)
4343
{
44-
QJsonParseError parse_error;
45-
const auto doc = QJsonDocument::fromJson(json, &parse_error);
46-
if (doc.isNull())
47-
throw mp::GenericManifestException(parse_error.errorString().toStdString());
48-
49-
if (!doc.isObject())
50-
throw mp::GenericManifestException("invalid manifest object");
51-
return doc.object();
44+
if (auto elem = data.as_object().if_contains(key))
45+
{
46+
if (auto obj = elem->if_object())
47+
return obj;
48+
}
49+
return nullptr;
5250
}
5351

54-
QString latest_version_in(const QJsonObject& versions)
52+
QString latest_version_in(const boost::json::object& versions)
5553
{
5654
QString max_version;
57-
for (auto it = versions.constBegin(); it != versions.constEnd(); ++it)
55+
for (const auto& [key, _] : versions)
5856
{
59-
const auto version = it.key();
57+
const auto version = QString::fromStdString(key);
6058
if (version < max_version)
6159
continue;
6260
max_version = version;
@@ -96,102 +94,95 @@ std::unique_ptr<mp::SimpleStreamsManifest> mp::SimpleStreamsManifest::fromJson(
9694
const std::optional<QByteArray>& json_from_mirror,
9795
const QString& host_url,
9896
std::function<bool(VMImageInfo&)> mutator)
97+
try
9998
{
10099
// Get the official manifest products
101-
const auto manifest_from_official = parse_manifest(json_from_official);
102-
const auto updated = manifest_from_official["updated"].toString();
103-
104-
const auto manifest_products_from_official = manifest_from_official["products"].toObject();
105-
if (manifest_products_from_official.isEmpty())
100+
const auto manifest_from_official = boost::json::parse(std::string_view(json_from_official));
101+
const auto updated = lookup_or<QString>(manifest_from_official, "updated", "");
102+
const auto& manifest_products_from_official = manifest_from_official.at("products").as_object();
103+
if (manifest_products_from_official.empty())
106104
throw mp::GenericManifestException("No products found");
107105

108106
auto arch = QSysInfo::currentCpuArchitecture();
109107
auto mapped_arch = arch_to_manifest.value(arch, arch);
110108

111109
// Get the mirror manifest products, if any
112-
std::optional<QJsonObject> manifest_products_from_mirror = std::nullopt;
110+
std::optional<boost::json::object> manifest_products_from_mirror = std::nullopt;
113111
if (json_from_mirror)
114112
{
115-
const auto manifest_from_mirror = parse_manifest(json_from_mirror.value());
116-
const auto products_from_mirror = manifest_from_mirror["products"].toObject();
117-
manifest_products_from_mirror = std::make_optional(products_from_mirror);
113+
const auto manifest_from_mirror = boost::json::parse(std::string_view(*json_from_mirror));
114+
manifest_products_from_mirror = manifest_from_mirror.at("products").as_object();
118115
}
119-
120-
const QJsonObject manifest_products =
116+
const auto& manifest_products =
121117
manifest_products_from_mirror.value_or(manifest_products_from_official);
122118

123119
std::vector<VMImageInfo> products;
124-
for (auto it = manifest_products.constBegin(); it != manifest_products.constEnd(); ++it)
120+
for (const auto& [product_key, product] : manifest_products)
125121
{
126-
const auto product_key = it.key();
127-
const QJsonValue product = it.value();
128-
129-
if (product["arch"].toString() != mapped_arch)
122+
if (lookup_or<QString>(product, "arch", "") != mapped_arch)
130123
continue;
131124

132-
const auto official_product = manifest_products_from_official[product_key].toObject();
133-
if (official_product.isEmpty())
125+
const auto* official_product = manifest_products_from_official.if_contains(product_key);
126+
if (!official_product)
134127
continue;
135128

136-
auto product_aliases = product["aliases"].toString().split(",");
129+
auto product_aliases = lookup_or<QString>(product, "aliases", "").split(",");
137130

138-
const auto image_type = product["image_type"].toString();
139-
const auto os = product["os"].toString();
140-
const auto release = product["release"].toString();
141-
const auto release_title = product["release_title"].toString();
142-
const auto release_codename = product["release_codename"].toString();
143-
const auto supported = product["supported"].toBool() || product_aliases.contains("devel") ||
131+
const auto image_type = lookup_or<QString>(product, "image_type", "");
132+
const auto os = lookup_or<QString>(product, "os", "");
133+
const auto release = lookup_or<QString>(product, "release", "");
134+
const auto release_title = lookup_or<QString>(product, "release_title", "");
135+
const auto release_codename = lookup_or<QString>(product, "release_codename", "");
136+
const auto supported = lookup_or<bool>(product, "supported", false) ||
137+
product_aliases.contains("devel") ||
144138
(os == "ubuntu-core" && image_type == "stable");
145139

146-
const auto versions = product["versions"].toObject();
147-
if (versions.isEmpty())
140+
const auto* versions = if_contains_object(product, "versions");
141+
if (!versions || versions->empty())
148142
continue;
149-
const auto official_versions = official_product["versions"].toObject();
150-
if (official_versions.isEmpty())
143+
const auto* official_versions = if_contains_object(*official_product, "versions");
144+
if (!official_versions || official_versions->empty())
151145
continue;
152146

153-
const auto latest_version = latest_version_in(versions);
154-
155-
for (auto it = versions.constBegin(); it != versions.constEnd(); ++it)
147+
const auto latest_version = latest_version_in(*versions);
148+
for (const auto& [version_string, version] : *versions)
156149
{
157-
const auto version_string = it.key();
158-
const auto version = versions[version_string].toObject();
159-
const auto official_version = official_versions[version_string].toObject();
160-
if (version != official_version)
150+
const auto* official_version = official_versions->if_contains(version_string);
151+
if (!official_version || version != *official_version)
161152
continue;
162153

163-
const auto items = version["items"].toObject();
164-
if (items.isEmpty())
154+
const auto* items = if_contains_object(version, "items");
155+
if (!items || items->empty())
165156
continue;
166157

167158
const auto& driver = MP_SETTINGS.get(mp::driver_key);
168159

169-
QJsonObject image;
170160
QString sha256, image_location;
171161
int size = -1;
172162

173-
QString image_key;
163+
std::string image_key;
174164
// Prioritize UEFI images
175-
if (items.contains("uefi1.img"))
165+
if (items->contains("uefi1.img"))
176166
image_key = "uefi1.img";
177167
// For Ubuntu Core images
178-
else if (items.contains("img.xz") && os == "ubuntu-core")
168+
else if (items->contains("img.xz") && os == "ubuntu-core")
179169
image_key = "img.xz";
180170
// Last resort, use img
181-
else if (items.contains("disk1.img"))
171+
else if (items->contains("disk1.img"))
182172
image_key = "disk1.img";
183173
// Otherwise, give up
184174
else
185175
continue;
186176

187-
image = items[image_key].toObject();
188-
image_location = host_url + image["path"].toString();
189-
sha256 = image["sha256"].toString();
190-
size = image["size"].toInt(-1);
177+
const auto& image = items->at(image_key);
178+
image_location = host_url + value_to<QString>(image.at("path"));
179+
sha256 = lookup_or<QString>(image, "sha256", "");
180+
size = lookup_or<int>(image, "size", -1);
191181

182+
const auto version_qstring = QString::fromStdString(version_string);
192183
// Aliases always alias to the latest version
193184
const QStringList& aliases =
194-
version_string == latest_version ? product_aliases : QStringList();
185+
version_qstring == latest_version ? product_aliases : QStringList();
195186

196187
VMImageInfo info{aliases,
197188
"Ubuntu",
@@ -202,7 +193,7 @@ std::unique_ptr<mp::SimpleStreamsManifest> mp::SimpleStreamsManifest::fromJson(
202193
image_location,
203194
sha256,
204195
host_url,
205-
version_string,
196+
version_qstring,
206197
size,
207198
true};
208199

@@ -212,9 +203,12 @@ std::unique_ptr<mp::SimpleStreamsManifest> mp::SimpleStreamsManifest::fromJson(
212203
}
213204
}
214205
}
215-
216206
if (products.empty())
217207
throw mp::EmptyManifestException("No supported products found.");
218208

219209
return std::make_unique<SimpleStreamsManifest>(updated, std::move(products));
220210
}
211+
catch (const boost::system::system_error& e)
212+
{
213+
throw GenericManifestException(e.what());
214+
}

0 commit comments

Comments
 (0)