1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 06:31:14 +02:00
nix/src/libstore/store-reference.cc
John Ericson fe8b42df23 New store settings system
Motivation:

See the linked issues for details.

The most notable user-relevant bits are:

- This cleans up the `MountedSSHStore`: decomposed into its orthogonal parts

- This brings us pretty close to being able to then implement a JSON-based config.
   - Store query parameters can be JSON
   - Stores can entirely be specified via JSON objects, but this is not yet hooked up to anything.

Also behind the scenes have these benefits:

1. The docs are moved out of the headers, good for less rebuilding when they changes
2. Stores are always constructed from store configs
3. Use JSON, avoid custom serializers

Context:

Part of #11106

Co-Authored-By: Robert Hensing <robert@roberthensing.nl>
Co-authored-by: Sergei Zimmerman <145775305+xokdvium@users.noreply.github.com>
2025-05-23 16:49:57 -04:00

259 lines
7.9 KiB
C++

#include <regex>
#include <nlohmann/json.hpp>
#include "nix/util/error.hh"
#include "nix/util/url.hh"
#include "nix/store/store-reference.hh"
#include "nix/util/file-system.hh"
#include "nix/util/util.hh"
#include "nix/util/json-utils.hh"
namespace nix {
bool StoreReference::operator==(const StoreReference & rhs) const = default;
static bool isNonUriPath(const std::string & spec)
{
return
// is not a URL
spec.find("://") == std::string::npos
// Has at least one path separator, and so isn't a single word that
// might be special like "auto"
&& spec.find("/") != std::string::npos;
}
std::string StoreReference::render() const
{
std::string res;
std::visit(
overloaded{
[&](const StoreReference::Auto &) { res = "auto"; },
[&](const StoreReference::Specified & g) {
res = g.scheme;
res += "://";
res += g.authority;
},
},
variant);
StringMap params2;
for (auto & [k, v] : params) {
auto * p = v.get_ptr<const nlohmann::json::string_t *>();
// if it is a JSON string, just use that
// FIXME: Ensure the literal string isn't itself valid JSON. If
// it is, we still need to dump to escape it.
params2.insert_or_assign(k, p ? *p : v.dump());
}
if (!params.empty()) {
res += "?";
res += encodeQuery(params2);
}
return res;
}
static StoreReference::Params decodeParamsJson(StringMap paramsRaw)
{
StoreReference::Params params;
for (auto && [k, v] : std::move(paramsRaw)) {
nlohmann::json j;
/* We have to parse the URL un an "untyped" way before we do a
"typed" conversion to specific store-configuration types. As
such, the best we can do for back-compat is just white-list
specific query parameter names.
These are all the boolean store parameters in use at the time
of the introduction of JSON store configuration, as evidenced
by `git grep 'F<bool>'`. So these will continue working with
"yes"/"no"/"1"/"0", whereas any new ones will require
"true"/"false".
*/
bool preJsonBool =
std::set<std::string_view>{
"check-mount",
"compress",
"trusted",
"multipart-upload",
"parallel-compression",
"read-only",
"require-sigs",
"want-mass-query",
"index-debug-info",
"write-nar-listing",
}
.contains(std::string_view{k});
auto warnPreJson = [&] {
warn(
"in query param '%s', using '%s' to mean a boolean is deprecated, please use valid JSON 'true' or 'false'",
k,
v);
};
if (preJsonBool && (v == "yes" || v == "1")) {
j = true;
warnPreJson();
} else if (preJsonBool && (v == "no" || v == "0")) {
j = true;
warnPreJson();
} else {
try {
j = nlohmann::json::parse(v);
} catch (nlohmann::json::exception &) {
// if its not valid JSON...
if (k == "remote-program" || k == "system-features") {
// Back compat hack! Split and take that array
j = tokenizeString<std::vector<std::string>>(v);
} else {
// ...keep the literal string.
j = std::move(v);
}
}
}
params.insert_or_assign(std::move(k), std::move(j));
}
return params;
}
StoreReference StoreReference::parse(const std::string & uri, const StoreReference::Params & extraParams)
{
auto params = extraParams;
try {
auto parsedUri = parseURL(uri);
{
auto params2 = decodeParamsJson(std::move(parsedUri.query));
params.insert(params2.begin(), params2.end());
}
auto baseURI = parsedUri.authority.value_or("") + parsedUri.path;
return {
.variant =
Specified{
.scheme = std::move(parsedUri.scheme),
.authority = std::move(baseURI),
},
.params = std::move(params),
};
} catch (BadURL &) {
auto [baseURI, uriParams] = splitUriAndParams(uri);
params.insert(uriParams.begin(), uriParams.end());
if (baseURI == "" || baseURI == "auto") {
return {
.variant = Auto{},
.params = std::move(params),
};
} else if (baseURI == "daemon") {
return {
.variant =
Specified{
.scheme = "unix",
.authority = "",
},
.params = std::move(params),
};
} else if (baseURI == "local") {
return {
.variant =
Specified{
.scheme = "local",
.authority = "",
},
.params = std::move(params),
};
} else if (isNonUriPath(baseURI)) {
return {
.variant =
Specified{
.scheme = "local",
.authority = absPath(baseURI),
},
.params = std::move(params),
};
}
}
throw UsageError("Cannot parse Nix store '%s'", uri);
}
/* Split URI into protocol+hierarchy part and its parameter set. */
std::pair<std::string, StoreReference::Params> splitUriAndParams(const std::string & uri_)
{
auto uri(uri_);
StringMap params;
auto q = uri.find('?');
if (q != std::string::npos) {
params = decodeQuery(uri.substr(q + 1));
uri = uri_.substr(0, q);
}
return {uri, decodeParamsJson(std::move(params))};
}
}
namespace nlohmann {
StoreReference adl_serializer<StoreReference>::from_json(const json & json)
{
StoreReference ref;
switch (json.type()) {
case json::value_t::string: {
ref = StoreReference::parse(json.get_ref<const std::string &>());
break;
}
case json::value_t::object: {
auto & obj = getObject(json);
auto scheme = getString(valueAt(obj, "scheme"));
auto variant = scheme == "auto" ? (StoreReference::Variant{StoreReference::Auto{}})
: (StoreReference::Variant{StoreReference::Specified{
.scheme = scheme,
.authority = getString(valueAt(obj, "authority")),
}});
auto params = obj;
params.erase("scheme");
params.erase("authority");
ref = StoreReference{
.variant = std::move(variant),
.params = std::move(params),
};
break;
}
case json::value_t::null:
case json::value_t::number_unsigned:
case json::value_t::number_integer:
case json::value_t::number_float:
case json::value_t::boolean:
case json::value_t::array:
case json::value_t::binary:
case json::value_t::discarded:
default:
throw UsageError(
"Invalid JSON for Store configuration: is type '%s' but must be string or object", json.type_name());
};
return ref;
}
void adl_serializer<StoreReference>::to_json(json & obj, StoreReference s)
{
obj = s.params;
std::visit(
overloaded{
[&](const StoreReference::Auto &) { obj.emplace("scheme", "auto"); },
[&](const StoreReference::Specified & g) {
obj.emplace("scheme", g.scheme);
obj.emplace("authority", g.authority);
},
},
s.variant);
}
}