forked from lix-project/lix
Merge pull request #8374 from obsidiansystems/improve-path-setting
Split `OptionalPathSetting` from `PathSetting`
This commit is contained in:
commit
48fe0ed554
|
@ -5,14 +5,14 @@ namespace nix {
|
||||||
|
|
||||||
HookInstance::HookInstance()
|
HookInstance::HookInstance()
|
||||||
{
|
{
|
||||||
debug("starting build hook '%s'", settings.buildHook);
|
debug("starting build hook '%s'", concatStringsSep(" ", settings.buildHook.get()));
|
||||||
|
|
||||||
auto buildHookArgs = tokenizeString<std::list<std::string>>(settings.buildHook.get());
|
auto buildHookArgs = settings.buildHook.get();
|
||||||
|
|
||||||
if (buildHookArgs.empty())
|
if (buildHookArgs.empty())
|
||||||
throw Error("'build-hook' setting is empty");
|
throw Error("'build-hook' setting is empty");
|
||||||
|
|
||||||
auto buildHook = buildHookArgs.front();
|
auto buildHook = canonPath(buildHookArgs.front());
|
||||||
buildHookArgs.pop_front();
|
buildHookArgs.pop_front();
|
||||||
|
|
||||||
Strings args;
|
Strings args;
|
||||||
|
|
|
@ -64,8 +64,9 @@ void handleDiffHook(
|
||||||
const Path & tryA, const Path & tryB,
|
const Path & tryA, const Path & tryB,
|
||||||
const Path & drvPath, const Path & tmpDir)
|
const Path & drvPath, const Path & tmpDir)
|
||||||
{
|
{
|
||||||
auto diffHook = settings.diffHook;
|
auto & diffHookOpt = settings.diffHook.get();
|
||||||
if (diffHook != "" && settings.runDiffHook) {
|
if (diffHookOpt && settings.runDiffHook) {
|
||||||
|
auto & diffHook = *diffHookOpt;
|
||||||
try {
|
try {
|
||||||
auto diffRes = runProgram(RunOptions {
|
auto diffRes = runProgram(RunOptions {
|
||||||
.program = diffHook,
|
.program = diffHook,
|
||||||
|
@ -1427,7 +1428,8 @@ void LocalDerivationGoal::startDaemon()
|
||||||
Store::Params params;
|
Store::Params params;
|
||||||
params["path-info-cache-size"] = "0";
|
params["path-info-cache-size"] = "0";
|
||||||
params["store"] = worker.store.storeDir;
|
params["store"] = worker.store.storeDir;
|
||||||
params["root"] = getLocalStore().rootDir;
|
if (auto & optRoot = getLocalStore().rootDir.get())
|
||||||
|
params["root"] = *optRoot;
|
||||||
params["state"] = "/no-such-path";
|
params["state"] = "/no-such-path";
|
||||||
params["log"] = "/no-such-path";
|
params["log"] = "/no-such-path";
|
||||||
auto store = make_ref<RestrictedStore>(params,
|
auto store = make_ref<RestrictedStore>(params,
|
||||||
|
|
|
@ -100,7 +100,10 @@ Settings::Settings()
|
||||||
if (!pathExists(nixExePath)) {
|
if (!pathExists(nixExePath)) {
|
||||||
nixExePath = getSelfExe().value_or("nix");
|
nixExePath = getSelfExe().value_or("nix");
|
||||||
}
|
}
|
||||||
buildHook = nixExePath + " __build-remote";
|
buildHook = {
|
||||||
|
nixExePath,
|
||||||
|
"__build-remote",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadConfFile()
|
void loadConfFile()
|
||||||
|
|
|
@ -236,7 +236,7 @@ public:
|
||||||
)",
|
)",
|
||||||
{"build-timeout"}};
|
{"build-timeout"}};
|
||||||
|
|
||||||
PathSetting buildHook{this, true, "", "build-hook",
|
Setting<Strings> buildHook{this, {}, "build-hook",
|
||||||
R"(
|
R"(
|
||||||
The path to the helper program that executes remote builds.
|
The path to the helper program that executes remote builds.
|
||||||
|
|
||||||
|
@ -556,8 +556,8 @@ public:
|
||||||
line.
|
line.
|
||||||
)"};
|
)"};
|
||||||
|
|
||||||
PathSetting diffHook{
|
OptionalPathSetting diffHook{
|
||||||
this, true, "", "diff-hook",
|
this, std::nullopt, "diff-hook",
|
||||||
R"(
|
R"(
|
||||||
Absolute path to an executable capable of diffing build
|
Absolute path to an executable capable of diffing build
|
||||||
results. The hook is executed if `run-diff-hook` is true, and the
|
results. The hook is executed if `run-diff-hook` is true, and the
|
||||||
|
|
|
@ -15,22 +15,22 @@ struct LocalFSStoreConfig : virtual StoreConfig
|
||||||
// it to omit the call to the Setting constructor. Clang works fine
|
// it to omit the call to the Setting constructor. Clang works fine
|
||||||
// either way.
|
// either way.
|
||||||
|
|
||||||
const PathSetting rootDir{(StoreConfig*) this, true, "",
|
const OptionalPathSetting rootDir{(StoreConfig*) this, std::nullopt,
|
||||||
"root",
|
"root",
|
||||||
"Directory prefixed to all other paths."};
|
"Directory prefixed to all other paths."};
|
||||||
|
|
||||||
const PathSetting stateDir{(StoreConfig*) this, false,
|
const PathSetting stateDir{(StoreConfig*) this,
|
||||||
rootDir != "" ? rootDir + "/nix/var/nix" : settings.nixStateDir,
|
rootDir.get() ? *rootDir.get() + "/nix/var/nix" : settings.nixStateDir,
|
||||||
"state",
|
"state",
|
||||||
"Directory where Nix will store state."};
|
"Directory where Nix will store state."};
|
||||||
|
|
||||||
const PathSetting logDir{(StoreConfig*) this, false,
|
const PathSetting logDir{(StoreConfig*) this,
|
||||||
rootDir != "" ? rootDir + "/nix/var/log/nix" : settings.nixLogDir,
|
rootDir.get() ? *rootDir.get() + "/nix/var/log/nix" : settings.nixLogDir,
|
||||||
"log",
|
"log",
|
||||||
"directory where Nix will store log files."};
|
"directory where Nix will store log files."};
|
||||||
|
|
||||||
const PathSetting realStoreDir{(StoreConfig*) this, false,
|
const PathSetting realStoreDir{(StoreConfig*) this,
|
||||||
rootDir != "" ? rootDir + "/nix/store" : storeDir, "real",
|
rootDir.get() ? *rootDir.get() + "/nix/store" : storeDir, "real",
|
||||||
"Physical path of the Nix store."};
|
"Physical path of the Nix store."};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@ struct StoreConfig : public Config
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const PathSetting storeDir_{this, false, settings.nixStore,
|
const PathSetting storeDir_{this, settings.nixStore,
|
||||||
"store",
|
"store",
|
||||||
R"(
|
R"(
|
||||||
Logical location of the Nix store, usually
|
Logical location of the Nix store, usually
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
#include "config.hh"
|
#include "config.hh"
|
||||||
|
#include "json-utils.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
template<typename T>
|
template<typename T>
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
#include "args.hh"
|
#include "args.hh"
|
||||||
#include "hash.hh"
|
#include "hash.hh"
|
||||||
|
#include "json-utils.hh"
|
||||||
|
|
||||||
#include <glob.h>
|
#include <glob.h>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
void Args::addFlag(Flag && flag_)
|
void Args::addFlag(Flag && flag_)
|
||||||
|
@ -247,11 +246,7 @@ nlohmann::json Args::toJSON()
|
||||||
j["arity"] = flag->handler.arity;
|
j["arity"] = flag->handler.arity;
|
||||||
if (!flag->labels.empty())
|
if (!flag->labels.empty())
|
||||||
j["labels"] = flag->labels;
|
j["labels"] = flag->labels;
|
||||||
// TODO With C++23 use `std::optional::tranform`
|
j["experimental-feature"] = flag->experimentalFeature;
|
||||||
if (auto & xp = flag->experimentalFeature)
|
|
||||||
j["experimental-feature"] = showExperimentalFeature(*xp);
|
|
||||||
else
|
|
||||||
j["experimental-feature"] = nullptr;
|
|
||||||
flags[name] = std::move(j);
|
flags[name] = std::move(j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,11 +411,7 @@ nlohmann::json MultiCommand::toJSON()
|
||||||
cat["id"] = command->category();
|
cat["id"] = command->category();
|
||||||
cat["description"] = trim(categories[command->category()]);
|
cat["description"] = trim(categories[command->category()]);
|
||||||
j["category"] = std::move(cat);
|
j["category"] = std::move(cat);
|
||||||
// TODO With C++23 use `std::optional::tranform`
|
cat["experimental-feature"] = command->experimentalFeature();
|
||||||
if (auto xp = command->experimentalFeature())
|
|
||||||
cat["experimental-feature"] = showExperimentalFeature(*xp);
|
|
||||||
else
|
|
||||||
cat["experimental-feature"] = nullptr;
|
|
||||||
cmds[name] = std::move(j);
|
cmds[name] = std::move(j);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,8 +53,11 @@ template<> void BaseSetting<std::set<ExperimentalFeature>>::appendOrSet(std::set
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void BaseSetting<T>::appendOrSet(T && newValue, bool append)
|
void BaseSetting<T>::appendOrSet(T && newValue, bool append)
|
||||||
{
|
{
|
||||||
static_assert(!trait::appendable, "using default `appendOrSet` implementation with an appendable type");
|
static_assert(
|
||||||
|
!trait::appendable,
|
||||||
|
"using default `appendOrSet` implementation with an appendable type");
|
||||||
assert(!append);
|
assert(!append);
|
||||||
|
|
||||||
value = std::move(newValue);
|
value = std::move(newValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,4 +74,60 @@ void BaseSetting<T>::set(const std::string & str, bool append)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string & category);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
|
||||||
|
{
|
||||||
|
args.addFlag({
|
||||||
|
.longName = name,
|
||||||
|
.description = fmt("Set the `%s` setting.", name),
|
||||||
|
.category = category,
|
||||||
|
.labels = {"value"},
|
||||||
|
.handler = {[this](std::string s) { overridden = true; set(s); }},
|
||||||
|
.experimentalFeature = experimentalFeature,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isAppendable())
|
||||||
|
args.addFlag({
|
||||||
|
.longName = "extra-" + name,
|
||||||
|
.description = fmt("Append to the `%s` setting.", name),
|
||||||
|
.category = category,
|
||||||
|
.labels = {"value"},
|
||||||
|
.handler = {[this](std::string s) { overridden = true; set(s, true); }},
|
||||||
|
.experimentalFeature = experimentalFeature,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#define DECLARE_CONFIG_SERIALISER(TY) \
|
||||||
|
template<> TY BaseSetting< TY >::parse(const std::string & str) const; \
|
||||||
|
template<> std::string BaseSetting< TY >::to_string() const;
|
||||||
|
|
||||||
|
DECLARE_CONFIG_SERIALISER(std::string)
|
||||||
|
DECLARE_CONFIG_SERIALISER(std::optional<std::string>)
|
||||||
|
DECLARE_CONFIG_SERIALISER(bool)
|
||||||
|
DECLARE_CONFIG_SERIALISER(Strings)
|
||||||
|
DECLARE_CONFIG_SERIALISER(StringSet)
|
||||||
|
DECLARE_CONFIG_SERIALISER(StringMap)
|
||||||
|
DECLARE_CONFIG_SERIALISER(std::set<ExperimentalFeature>)
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T BaseSetting<T>::parse(const std::string & str) const
|
||||||
|
{
|
||||||
|
static_assert(std::is_integral<T>::value, "Integer required.");
|
||||||
|
|
||||||
|
if (auto n = string2Int<T>(str))
|
||||||
|
return *n;
|
||||||
|
else
|
||||||
|
throw UsageError("setting '%s' has invalid value '%s'", name, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
std::string BaseSetting<T>::to_string() const
|
||||||
|
{
|
||||||
|
static_assert(std::is_integral<T>::value, "Integer required.");
|
||||||
|
|
||||||
|
return std::to_string(value);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -219,29 +219,6 @@ void AbstractSetting::convertToArg(Args & args, const std::string & category)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
void BaseSetting<T>::convertToArg(Args & args, const std::string & category)
|
|
||||||
{
|
|
||||||
args.addFlag({
|
|
||||||
.longName = name,
|
|
||||||
.description = fmt("Set the `%s` setting.", name),
|
|
||||||
.category = category,
|
|
||||||
.labels = {"value"},
|
|
||||||
.handler = {[this](std::string s) { overridden = true; set(s); }},
|
|
||||||
.experimentalFeature = experimentalFeature,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isAppendable())
|
|
||||||
args.addFlag({
|
|
||||||
.longName = "extra-" + name,
|
|
||||||
.description = fmt("Append to the `%s` setting.", name),
|
|
||||||
.category = category,
|
|
||||||
.labels = {"value"},
|
|
||||||
.handler = {[this](std::string s) { overridden = true; set(s, true); }},
|
|
||||||
.experimentalFeature = experimentalFeature,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template<> std::string BaseSetting<std::string>::parse(const std::string & str) const
|
template<> std::string BaseSetting<std::string>::parse(const std::string & str) const
|
||||||
{
|
{
|
||||||
return str;
|
return str;
|
||||||
|
@ -252,21 +229,17 @@ template<> std::string BaseSetting<std::string>::to_string() const
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<> std::optional<std::string> BaseSetting<std::optional<std::string>>::parse(const std::string & str) const
|
||||||
T BaseSetting<T>::parse(const std::string & str) const
|
|
||||||
{
|
{
|
||||||
static_assert(std::is_integral<T>::value, "Integer required.");
|
if (str == "")
|
||||||
if (auto n = string2Int<T>(str))
|
return std::nullopt;
|
||||||
return *n;
|
|
||||||
else
|
else
|
||||||
throw UsageError("setting '%s' has invalid value '%s'", name, str);
|
return { str };
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<> std::string BaseSetting<std::optional<std::string>>::to_string() const
|
||||||
std::string BaseSetting<T>::to_string() const
|
|
||||||
{
|
{
|
||||||
static_assert(std::is_integral<T>::value, "Integer required.");
|
return value ? *value : "";
|
||||||
return std::to_string(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> bool BaseSetting<bool>::parse(const std::string & str) const
|
template<> bool BaseSetting<bool>::parse(const std::string & str) const
|
||||||
|
@ -403,15 +376,25 @@ template class BaseSetting<StringSet>;
|
||||||
template class BaseSetting<StringMap>;
|
template class BaseSetting<StringMap>;
|
||||||
template class BaseSetting<std::set<ExperimentalFeature>>;
|
template class BaseSetting<std::set<ExperimentalFeature>>;
|
||||||
|
|
||||||
|
static Path parsePath(const AbstractSetting & s, const std::string & str)
|
||||||
|
{
|
||||||
|
if (str == "")
|
||||||
|
throw UsageError("setting '%s' is a path and paths cannot be empty", s.name);
|
||||||
|
else
|
||||||
|
return canonPath(str);
|
||||||
|
}
|
||||||
|
|
||||||
Path PathSetting::parse(const std::string & str) const
|
Path PathSetting::parse(const std::string & str) const
|
||||||
{
|
{
|
||||||
if (str == "") {
|
return parsePath(*this, str);
|
||||||
if (allowEmpty)
|
}
|
||||||
return "";
|
|
||||||
|
std::optional<Path> OptionalPathSetting::parse(const std::string & str) const
|
||||||
|
{
|
||||||
|
if (str == "")
|
||||||
|
return std::nullopt;
|
||||||
else
|
else
|
||||||
throw UsageError("setting '%s' cannot be empty", name);
|
return parsePath(*this, str);
|
||||||
} else
|
|
||||||
return canonPath(str);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GlobalConfig::set(const std::string & name, const std::string & value)
|
bool GlobalConfig::set(const std::string & name, const std::string & value)
|
||||||
|
|
|
@ -353,21 +353,20 @@ public:
|
||||||
/**
|
/**
|
||||||
* A special setting for Paths. These are automatically canonicalised
|
* A special setting for Paths. These are automatically canonicalised
|
||||||
* (e.g. "/foo//bar/" becomes "/foo/bar").
|
* (e.g. "/foo//bar/" becomes "/foo/bar").
|
||||||
|
*
|
||||||
|
* It is mandatory to specify a path; i.e. the empty string is not
|
||||||
|
* permitted.
|
||||||
*/
|
*/
|
||||||
class PathSetting : public BaseSetting<Path>
|
class PathSetting : public BaseSetting<Path>
|
||||||
{
|
{
|
||||||
bool allowEmpty;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
PathSetting(Config * options,
|
PathSetting(Config * options,
|
||||||
bool allowEmpty,
|
|
||||||
const Path & def,
|
const Path & def,
|
||||||
const std::string & name,
|
const std::string & name,
|
||||||
const std::string & description,
|
const std::string & description,
|
||||||
const std::set<std::string> & aliases = {})
|
const std::set<std::string> & aliases = {})
|
||||||
: BaseSetting<Path>(def, true, name, description, aliases)
|
: BaseSetting<Path>(def, true, name, description, aliases)
|
||||||
, allowEmpty(allowEmpty)
|
|
||||||
{
|
{
|
||||||
options->addSetting(this);
|
options->addSetting(this);
|
||||||
}
|
}
|
||||||
|
@ -379,6 +378,30 @@ public:
|
||||||
void operator =(const Path & v) { this->assign(v); }
|
void operator =(const Path & v) { this->assign(v); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Like `PathSetting`, but the absence of a path is also allowed.
|
||||||
|
*
|
||||||
|
* `std::optional` is used instead of the empty string for clarity.
|
||||||
|
*/
|
||||||
|
class OptionalPathSetting : public BaseSetting<std::optional<Path>>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
OptionalPathSetting(Config * options,
|
||||||
|
const std::optional<Path> & def,
|
||||||
|
const std::string & name,
|
||||||
|
const std::string & description,
|
||||||
|
const std::set<std::string> & aliases = {})
|
||||||
|
: BaseSetting<std::optional<Path>>(def, true, name, description, aliases)
|
||||||
|
{
|
||||||
|
options->addSetting(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Path> parse(const std::string & str) const override;
|
||||||
|
|
||||||
|
void operator =(const std::optional<Path> & v) { this->assign(v); }
|
||||||
|
};
|
||||||
|
|
||||||
struct GlobalConfig : public AbstractConfig
|
struct GlobalConfig : public AbstractConfig
|
||||||
{
|
{
|
||||||
typedef std::vector<Config*> ConfigRegistrations;
|
typedef std::vector<Config*> ConfigRegistrations;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#include "comparator.hh"
|
#include "comparator.hh"
|
||||||
#include "error.hh"
|
#include "error.hh"
|
||||||
#include "nlohmann/json_fwd.hpp"
|
#include "json-utils.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -94,4 +94,10 @@ public:
|
||||||
void to_json(nlohmann::json &, const ExperimentalFeature &);
|
void to_json(nlohmann::json &, const ExperimentalFeature &);
|
||||||
void from_json(const nlohmann::json &, ExperimentalFeature &);
|
void from_json(const nlohmann::json &, ExperimentalFeature &);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It is always rendered as a string
|
||||||
|
*/
|
||||||
|
template<>
|
||||||
|
struct json_avoids_null<ExperimentalFeature> : std::true_type {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
19
src/libutil/json-utils.cc
Normal file
19
src/libutil/json-utils.cc
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#include "json-utils.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
const nlohmann::json * get(const nlohmann::json & map, const std::string & key)
|
||||||
|
{
|
||||||
|
auto i = map.find(key);
|
||||||
|
if (i == map.end()) return nullptr;
|
||||||
|
return &*i;
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json * get(nlohmann::json & map, const std::string & key)
|
||||||
|
{
|
||||||
|
auto i = map.find(key);
|
||||||
|
if (i == map.end()) return nullptr;
|
||||||
|
return &*i;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,21 +2,77 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
const nlohmann::json * get(const nlohmann::json & map, const std::string & key)
|
const nlohmann::json * get(const nlohmann::json & map, const std::string & key);
|
||||||
{
|
|
||||||
auto i = map.find(key);
|
|
||||||
if (i == map.end()) return nullptr;
|
|
||||||
return &*i;
|
|
||||||
}
|
|
||||||
|
|
||||||
nlohmann::json * get(nlohmann::json & map, const std::string & key)
|
nlohmann::json * get(nlohmann::json & map, const std::string & key);
|
||||||
{
|
|
||||||
auto i = map.find(key);
|
/**
|
||||||
if (i == map.end()) return nullptr;
|
* For `adl_serializer<std::optional<T>>` below, we need to track what
|
||||||
return &*i;
|
* types are not already using `null`. Only for them can we use `null`
|
||||||
}
|
* to represent `std::nullopt`.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct json_avoids_null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle numbers in default impl
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct json_avoids_null : std::bool_constant<std::is_integral<T>::value> {};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct json_avoids_null<std::nullptr_t> : std::false_type {};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct json_avoids_null<bool> : std::true_type {};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
struct json_avoids_null<std::string> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct json_avoids_null<std::vector<T>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct json_avoids_null<std::list<T>> : std::true_type {};
|
||||||
|
|
||||||
|
template<typename K, typename V>
|
||||||
|
struct json_avoids_null<std::map<K, V>> : std::true_type {};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace nlohmann {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This "instance" is widely requested, see
|
||||||
|
* https://github.com/nlohmann/json/issues/1749, but momentum has stalled
|
||||||
|
* out. Writing there here in Nix as a stop-gap.
|
||||||
|
*
|
||||||
|
* We need to make sure the underlying type does not use `null` for this to
|
||||||
|
* round trip. We do that with a static assert.
|
||||||
|
*/
|
||||||
|
template<typename T>
|
||||||
|
struct adl_serializer<std::optional<T>> {
|
||||||
|
static std::optional<T> from_json(const json & json) {
|
||||||
|
static_assert(
|
||||||
|
nix::json_avoids_null<T>::value,
|
||||||
|
"null is already in use for underlying type's JSON");
|
||||||
|
return json.is_null()
|
||||||
|
? std::nullopt
|
||||||
|
: std::optional { adl_serializer<T>::from_json(json) };
|
||||||
|
}
|
||||||
|
static void to_json(json & json, std::optional<T> t) {
|
||||||
|
static_assert(
|
||||||
|
nix::json_avoids_null<T>::value,
|
||||||
|
"null is already in use for underlying type's JSON");
|
||||||
|
if (t)
|
||||||
|
adl_serializer<T>::to_json(json, *t);
|
||||||
|
else
|
||||||
|
json = nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue