Skip to content

Commit 88705f8

Browse files
Shahar "Dawn" Orfzakaria
Shahar "Dawn" Or
andcommitted
New CLI flag --replace-eval-errors
Co-authored-by: Farid Zakaria <[email protected]>
1 parent cdbe788 commit 88705f8

File tree

14 files changed

+93
-20
lines changed

14 files changed

+93
-20
lines changed

doc/manual/source/command-ref/nix-instantiate.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,11 @@ standard input.
112112
When used with `--eval`, print the resulting value as an JSON
113113
representation of the abstract syntax tree rather than as a Nix expression.
114114
115+
- `--replace-eval-errors`
116+
117+
When used with `--eval` and `--json`, replace any evaluation errors with the string
118+
`"«evaluation error»"`.
119+
115120
- `--xml`
116121
117122
When used with `--eval`, print the resulting value as an XML
@@ -205,3 +210,10 @@ $ nix-instantiate --eval --xml --strict --expr '{ x = {}; }'
205210
</attrs>
206211
</expr>
207212
```
213+
214+
Replacing evaluation errors:
215+
216+
```console
217+
$ nix-instantiate --eval --json --replace-eval-errors --expr '{ a = throw "fail"; }'
218+
{"a":"«evaluation error»"}
219+
```

src/libexpr-tests/json.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace nix {
99
std::string getJSONValue(Value& value) {
1010
std::stringstream ss;
1111
NixStringContext ps;
12-
printValueAsJSON(state, true, value, noPos, ss, ps);
12+
printValueAsJSON(state, true, false, value, noPos, ss, ps);
1313
return ss.str();
1414
}
1515
};

src/libexpr/include/nix/expr/value-to-json.hh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010

1111
namespace nix {
1212

13-
nlohmann::json printValueAsJSON(EvalState & state, bool strict,
13+
nlohmann::json printValueAsJSON(EvalState & state, bool strict, bool replaceEvalErrors,
1414
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore = true);
1515

16-
void printValueAsJSON(EvalState & state, bool strict,
16+
void printValueAsJSON(EvalState & state, bool strict, bool replaceEvalErrors,
1717
Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore = true);
1818

1919

src/libexpr/primops.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,7 +1388,7 @@ static void derivationStrictInternal(
13881388

13891389
if (i->name == state.sStructuredAttrs) continue;
13901390

1391-
jsonObject->emplace(key, printValueAsJSON(state, true, *i->value, pos, context));
1391+
jsonObject->emplace(key, printValueAsJSON(state, true, false, *i->value, pos, context));
13921392

13931393
if (i->name == state.sBuilder)
13941394
drv.builder = state.forceString(*i->value, context, pos, context_below);
@@ -2328,7 +2328,7 @@ static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Val
23282328
{
23292329
std::ostringstream out;
23302330
NixStringContext context;
2331-
printValueAsJSON(state, true, *args[0], pos, out, context);
2331+
printValueAsJSON(state, true, false, *args[0], pos, out, context);
23322332
v.mkString(toView(out), context);
23332333
}
23342334

src/libexpr/primops/fetchTree.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ static void fetchTree(
135135
attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
136136
} else if (state.symbols[attr.name] == "publicKeys") {
137137
experimentalFeatureSettings.require(Xp::VerifiedFetches);
138-
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
138+
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, false, *attr.value, pos, context).dump());
139139
}
140140
else
141141
state.error<TypeError>("argument '%s' to '%s' is %s while a string, Boolean or integer is expected",

src/libexpr/value-to-json.cc

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
#include <cstdlib>
77
#include <iomanip>
88
#include <nlohmann/json.hpp>
9+
#include <typeinfo>
910

1011

1112
namespace nix {
1213
using json = nlohmann::json;
1314
// TODO: rename. It doesn't print.
14-
json printValueAsJSON(EvalState & state, bool strict,
15+
json printValueAsJSON(EvalState & state, bool strict, bool replaceEvalErrors,
1516
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore)
1617
{
1718
checkInterrupt();
@@ -54,13 +55,27 @@ json printValueAsJSON(EvalState & state, bool strict,
5455
break;
5556
}
5657
if (auto i = v.attrs()->get(state.sOutPath))
57-
return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore);
58+
return printValueAsJSON(state, strict, replaceEvalErrors, *i->value, i->pos, context, copyToStore);
5859
else {
5960
out = json::object();
6061
for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) {
6162
try {
62-
out.emplace(state.symbols[a->name], printValueAsJSON(state, strict, *a->value, a->pos, context, copyToStore));
63+
out.emplace(state.symbols[a->name], printValueAsJSON(state, strict, replaceEvalErrors, *a->value, a->pos, context, copyToStore));
6364
} catch (Error & e) {
65+
std::cerr << "Caught an Error of type: " << typeid(e).name() << std::endl;
66+
// std::cerr << "Caught an Error of type: " << e.message() << std::endl;
67+
// std::cerr << "Caught an Error of type: " << e.what() << std::endl;
68+
69+
// TODO: Figure out what Error is here?
70+
// We seem to be not catching FileNotFoundError.
71+
bool isEvalError = dynamic_cast<EvalError *>(&e);
72+
bool isFileNotFoundError = dynamic_cast<FileNotFound *>(&e);
73+
// Restrict replaceEvalErrors only only evaluation errors
74+
if (replaceEvalErrors && (isEvalError || isFileNotFoundError)) {
75+
out.emplace(state.symbols[a->name], "«evaluation error»");
76+
continue;
77+
}
78+
6479
e.addTrace(state.positions[a->pos],
6580
HintFmt("while evaluating attribute '%1%'", state.symbols[a->name]));
6681
throw;
@@ -75,8 +90,9 @@ json printValueAsJSON(EvalState & state, bool strict,
7590
int i = 0;
7691
for (auto elem : v.listItems()) {
7792
try {
78-
out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
93+
out.push_back(printValueAsJSON(state, strict, replaceEvalErrors, *elem, pos, context, copyToStore));
7994
} catch (Error & e) {
95+
// TODO: Missing catch
8096
e.addTrace(state.positions[pos],
8197
HintFmt("while evaluating list element at index %1%", i));
8298
throw;
@@ -106,11 +122,11 @@ json printValueAsJSON(EvalState & state, bool strict,
106122
return out;
107123
}
108124

109-
void printValueAsJSON(EvalState & state, bool strict,
125+
void printValueAsJSON(EvalState & state, bool strict, bool replaceEvalErrors,
110126
Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore)
111127
{
112128
try {
113-
str << printValueAsJSON(state, strict, v, pos, context, copyToStore);
129+
str << printValueAsJSON(state, strict, replaceEvalErrors, v, pos, context, copyToStore);
114130
} catch (nlohmann::json::exception & e) {
115131
throw JSONSerializationError("JSON serialization error: %s", e.what());
116132
}

src/libflake/flake.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static void parseFlakeInputAttr(
9191
if (attr.name == state.symbols.create("publicKeys")) {
9292
experimentalFeatureSettings.require(Xp::VerifiedFetches);
9393
NixStringContext emptyContext = {};
94-
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, attr.pos, emptyContext).dump());
94+
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, false, *attr.value, attr.pos, emptyContext).dump());
9595
} else
9696
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
9797
state.symbols[attr.name], showType(*attr.value)).debugThrow();

src/libutil/posix-source-accessor.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ void PosixSourceAccessor::readFile(
5454
#endif
5555
));
5656
if (!fd)
57-
throw SysError("opening file '%1%'", ap.string());
57+
throw SysError("opening file9 '%1%'", ap.string());
5858

5959
struct stat st;
6060
if (fstat(fromDescriptorReadOnly(fd.get()), &st) == -1)

src/nix-env/nix-env.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -984,7 +984,7 @@ static void queryJSON(Globals & globals, std::vector<PackageInfo> & elems, bool
984984
metaObj[j] = nullptr;
985985
} else {
986986
NixStringContext context;
987-
metaObj[j] = printValueAsJSON(*globals.state, true, *v, noPos, context);
987+
metaObj[j] = printValueAsJSON(*globals.state, true, false, *v, noPos, context);
988988
}
989989
}
990990
}

src/nix-instantiate/nix-instantiate.cc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ static int rootNr = 0;
2828
enum OutputKind { okPlain, okRaw, okXML, okJSON };
2929

3030
void processExpr(EvalState & state, const Strings & attrPaths,
31-
bool parseOnly, bool strict, Bindings & autoArgs,
31+
bool parseOnly, bool strict, bool replaceEvalErrors, Bindings & autoArgs,
3232
bool evalOnly, OutputKind output, bool location, Expr * e)
3333
{
3434
if (parseOnly) {
@@ -58,7 +58,7 @@ void processExpr(EvalState & state, const Strings & attrPaths,
5858
else if (output == okXML)
5959
printValueAsXML(state, strict, location, vRes, std::cout, context, noPos);
6060
else if (output == okJSON) {
61-
printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context);
61+
printValueAsJSON(state, strict, replaceEvalErrors, vRes, v.determinePos(noPos), std::cout, context);
6262
std::cout << std::endl;
6363
} else {
6464
if (strict) state.forceValueDeep(vRes);
@@ -106,6 +106,7 @@ static int main_nix_instantiate(int argc, char * * argv)
106106
OutputKind outputKind = okPlain;
107107
bool xmlOutputSourceLocation = true;
108108
bool strict = false;
109+
bool replaceEvalErrors = false;
109110
Strings attrPaths;
110111
bool wantsReadWrite = false;
111112

@@ -147,6 +148,8 @@ static int main_nix_instantiate(int argc, char * * argv)
147148
xmlOutputSourceLocation = false;
148149
else if (*arg == "--strict")
149150
strict = true;
151+
else if (*arg == "--replace-eval-errors")
152+
replaceEvalErrors = true;
150153
else if (*arg == "--dry-run")
151154
settings.readOnlyMode = true;
152155
else if (*arg != "" && arg->at(0) == '-')
@@ -184,7 +187,7 @@ static int main_nix_instantiate(int argc, char * * argv)
184187

185188
if (readStdin) {
186189
Expr * e = state->parseStdin();
187-
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
190+
processExpr(*state, attrPaths, parseOnly, strict, replaceEvalErrors, autoArgs,
188191
evalOnly, outputKind, xmlOutputSourceLocation, e);
189192
} else if (files.empty() && !fromArgs)
190193
files.push_back("./default.nix");
@@ -193,7 +196,7 @@ static int main_nix_instantiate(int argc, char * * argv)
193196
Expr * e = fromArgs
194197
? state->parseExprFromString(i, state->rootPath("."))
195198
: state->parseExprFromFile(resolveExprPath(lookupFileArg(*state, i)));
196-
processExpr(*state, attrPaths, parseOnly, strict, autoArgs,
199+
processExpr(*state, attrPaths, parseOnly, strict, replaceEvalErrors, autoArgs,
197200
evalOnly, outputKind, xmlOutputSourceLocation, e);
198201
}
199202

src/nix/eval.cc

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace nix::fs { using namespace std::filesystem; }
1515
struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
1616
{
1717
bool raw = false;
18+
bool replaceEvalErrors = false;
1819
std::optional<std::string> apply;
1920
std::optional<std::filesystem::path> writeTo;
2021

@@ -39,6 +40,12 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
3940
.labels = {"path"},
4041
.handler = {&writeTo},
4142
});
43+
44+
addFlag({
45+
.longName = "replace-eval-errors",
46+
.description = "When used with `--json` the Nix evaluator will replace evaluation errors with a fixed value.",
47+
.handler = {&replaceEvalErrors, true},
48+
});
4249
}
4350

4451
std::string description() override
@@ -118,7 +125,7 @@ struct CmdEval : MixJSON, InstallableValueCommand, MixReadOnlyOption
118125
}
119126

120127
else if (json) {
121-
printJSON(printValueAsJSON(*state, true, *v, pos, context, false));
128+
printJSON(printValueAsJSON(*state, true, replaceEvalErrors, *v, pos, context, false));
122129
}
123130

124131
else {

src/nix/eval.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ R""(
4848
# cat ./out/subdir/bla
4949
123
5050

51+
* Replace evaluation errors:
52+
53+
```console
54+
$ nix eval --json --replace-eval-errors --expr '{ a = throw "fail"; }'
55+
{"a":"«evaluation error»"}
56+
```
57+
5158
# Description
5259

5360
This command evaluates the given Nix expression, and prints the result on standard output.

tests/functional/eval.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,23 @@ nix-instantiate --eval -E 'assert 1 + 2 == 3; true'
3535
[[ $(nix-instantiate -A int --eval - < "./eval.nix") == 123 ]]
3636
[[ "$(nix-instantiate --eval -E '{"assert"=1;bar=2;}')" == '{ "assert" = 1; bar = 2; }' ]]
3737

38+
expected="$(echo '{
39+
"missingAttr":"«evaluation error»",
40+
"insideAList":["«evaluation error»"],
41+
"deeper":{v:"«evaluation error»"},
42+
"failedAssertion":"«evaluation error»",
43+
"missingFile":"«evaluation error»",
44+
"missingImport":"«evaluation error»",
45+
"outOfBounds":"«evaluation error»",
46+
"failedCoersion":"«evaluation error»",
47+
"failedAddition":"«evaluation error»"
48+
}' | tr -d '\n')"
49+
actual="$(nix-instantiate --eval --json --strict --replace-eval-errors "./replace-eval-errors.nix")"
50+
[[ $actual == "$expected" ]] || diff --unified <(echo "$actual") <(echo "$expected") >&2
51+
52+
actual="$(nix eval --json --replace-eval-errors -f "./replace-eval-errors.nix")"
53+
[[ $actual == "$expected" ]] || diff --unified <(echo "$actual") <(echo "$expected") >&2
54+
3855
# Check that symlink cycles don't cause a hang.
3956
ln -sfn cycle.nix "$TEST_ROOT/cycle.nix"
4057
(! nix eval --file "$TEST_ROOT/cycle.nix")
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
missingAttr = let bar = { }; in bar.notExist;
3+
insideAList = [ (throw "a throw") ];
4+
deeper = { v = throw "v"; };
5+
failedAssertion = assert true; assert false; null;
6+
missingFile = builtins.readFile ./missing-file.txt;
7+
missingImport = import ./missing-import.nix;
8+
outOfBounds = builtins.elemAt [ 1 2 3 ] 100;
9+
failedCoersion = "${1}";
10+
failedAddition = 1.0 + "a string";
11+
}

0 commit comments

Comments
 (0)