Skip to content

Commit

Permalink
Merge pull request #8760 from iFreilicht/fix-json-load-assertion-errors
Browse files Browse the repository at this point in the history
Fix derivation load assertion errors
  • Loading branch information
Ericson2314 committed Aug 7, 2023
2 parents 635df5e + 3fefc2b commit 9113b42
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 12 deletions.
2 changes: 2 additions & 0 deletions doc/manual/src/release-notes/rl-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
These functions are useful for converting between flake references encoded as attribute sets and URLs.

- [`builtins.toJSON`](@docroot@/language/builtins.md#builtins-parseFlakeRef) now prints [--show-trace](@docroot@/command-ref/conf-file.html#conf-show-trace) items for the path in which it finds an evaluation error.

- Error messages regarding malformed input to [`derivation add`](@docroot@/command-ref/new-cli/nix3-derivation-add.md) are now clearer and more detailed.
40 changes: 28 additions & 12 deletions src/libstore/derivations.cc
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,7 @@ DerivationOutput DerivationOutput::fromJSON(
const ExperimentalFeatureSettings & xpSettings)
{
std::set<std::string_view> keys;
ensureType(_json, nlohmann::detail::value_t::object);
auto json = (std::map<std::string, nlohmann::json>) _json;

for (const auto & [key, _] : json)
Expand Down Expand Up @@ -1097,36 +1098,51 @@ Derivation Derivation::fromJSON(
const Store & store,
const nlohmann::json & json)
{
using nlohmann::detail::value_t;

Derivation res;

res.name = json["name"];
ensureType(json, value_t::object);

{
auto & outputsObj = json["outputs"];
res.name = ensureType(valueAt(json, "name"), value_t::string);

try {
auto & outputsObj = ensureType(valueAt(json, "outputs"), value_t::object);
for (auto & [outputName, output] : outputsObj.items()) {
res.outputs.insert_or_assign(
outputName,
DerivationOutput::fromJSON(store, res.name, outputName, output));
}
} catch (Error & e) {
e.addTrace({}, "while reading key 'outputs'");
throw;
}

{
auto & inputsList = json["inputSrcs"];
try {
auto & inputsList = ensureType(valueAt(json, "inputSrcs"), value_t::array);
for (auto & input : inputsList)
res.inputSrcs.insert(store.parseStorePath(static_cast<const std::string &>(input)));
} catch (Error & e) {
e.addTrace({}, "while reading key 'inputSrcs'");
throw;
}

{
auto & inputDrvsObj = json["inputDrvs"];
for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items())
try {
auto & inputDrvsObj = ensureType(valueAt(json, "inputDrvs"), value_t::object);
for (auto & [inputDrvPath, inputOutputs] : inputDrvsObj.items()) {
ensureType(inputOutputs, value_t::array);
res.inputDrvs[store.parseStorePath(inputDrvPath)] =
static_cast<const StringSet &>(inputOutputs);
}
} catch (Error & e) {
e.addTrace({}, "while reading key 'inputDrvs'");
throw;
}

res.platform = json["system"];
res.builder = json["builder"];
res.args = json["args"];
res.env = json["env"];
res.platform = ensureType(valueAt(json, "system"), value_t::string);
res.builder = ensureType(valueAt(json, "builder"), value_t::string);
res.args = ensureType(valueAt(json, "args"), value_t::array);
res.env = ensureType(valueAt(json, "env"), value_t::object);

return res;
}
Expand Down
24 changes: 24 additions & 0 deletions src/libutil/json-utils.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "json-utils.hh"
#include "error.hh"

namespace nix {

Expand All @@ -16,4 +17,27 @@ nlohmann::json * get(nlohmann::json & map, const std::string & key)
return &*i;
}

const nlohmann::json & valueAt(
const nlohmann::json & map,
const std::string & key)
{
if (!map.contains(key))
throw Error("Expected JSON object to contain key '%s' but it doesn't", key);

return map[key];
}

const nlohmann::json & ensureType(
const nlohmann::json & value,
nlohmann::json::value_type expectedType
)
{
if (value.type() != expectedType)
throw Error(
"Expected JSON value to be of type '%s' but it is of type '%s'",
nlohmann::json(expectedType).type_name(),
value.type_name());

return value;
}
}
22 changes: 22 additions & 0 deletions src/libutil/json-utils.hh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ const nlohmann::json * get(const nlohmann::json & map, const std::string & key);

nlohmann::json * get(nlohmann::json & map, const std::string & key);

/**
* Get the value of a json object at a key safely, failing
* with a Nix Error if the key does not exist.
*
* Use instead of nlohmann::json::at() to avoid ugly exceptions.
*
* _Does not check whether `map` is an object_, use `ensureType` for that.
*/
const nlohmann::json & valueAt(
const nlohmann::json & map,
const std::string & key);

/**
* Ensure the type of a json object is what you expect, failing
* with a Nix Error if it isn't.
*
* Use before type conversions and element access to avoid ugly exceptions.
*/
const nlohmann::json & ensureType(
const nlohmann::json & value,
nlohmann::json::value_type expectedType);

/**
* For `adl_serializer<std::optional<T>>` below, we need to track what
* types are not already using `null`. Only for them can we use `null`
Expand Down

0 comments on commit 9113b42

Please sign in to comment.