1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-11 03:21:03 +01:00

Improve wrong format message with nix hash convert

We have the machinery to make a more informative error, telling the
user what format was actually encountered, and not just that it is not
the format that was requested.
This commit is contained in:
John Ericson 2025-12-05 11:21:47 -05:00
parent 5f42e5ebb7
commit b61885786d
5 changed files with 43 additions and 22 deletions

View file

@ -182,8 +182,10 @@ Hash Hash::parseSRI(std::string_view original)
* *
* @param resolveAlgo resolves the parsed type (or throws an error when it is not * @param resolveAlgo resolves the parsed type (or throws an error when it is not
* possible.) * possible.)
*
* @return the parsed hash and the format it was parsed from
*/ */
static Hash parseAnyHelper(std::string_view rest, auto resolveAlgo) static std::pair<Hash, HashFormat> parseAnyHelper(std::string_view rest, auto resolveAlgo)
{ {
bool isSRI = false; bool isSRI = false;
@ -203,34 +205,45 @@ static Hash parseAnyHelper(std::string_view rest, auto resolveAlgo)
HashAlgorithm algo = resolveAlgo(std::move(optParsedAlgo)); HashAlgorithm algo = resolveAlgo(std::move(optParsedAlgo));
auto [decode, formatName] = [&]() -> DecodeNamePair { auto [decode, formatName, format] = [&]() -> std::tuple<decltype(base16::decode) *, std::string_view, HashFormat> {
if (isSRI) { if (isSRI) {
/* In the SRI case, we always are using Base64. If the /* In the SRI case, we always are using Base64. If the
length is wrong, get an error later. */ length is wrong, get an error later. */
return {base64::decode, "SRI"}; return {base64::decode, "SRI", HashFormat::SRI};
} else { } else {
/* Otherwise, decide via the length of the hash (for the /* Otherwise, decide via the length of the hash (for the
given algorithm) what base encoding it is. */ given algorithm) what base encoding it is. */
return baseExplicit(baseFromSize(rest, algo)); auto format = baseFromSize(rest, algo);
auto [decode, formatName] = baseExplicit(format);
return {decode, formatName, format};
} }
}(); }();
return parseLowLevel(rest, algo, {decode, formatName}); return {parseLowLevel(rest, algo, {decode, formatName}), format};
} }
Hash Hash::parseAnyPrefixed(std::string_view original) Hash Hash::parseAnyPrefixed(std::string_view original)
{ {
return parseAnyHelper(original, [&](std::optional<HashAlgorithm> optParsedAlgo) { return parseAnyHelper(
// Either the string or user must provide the type, if they both do they original,
// must agree. [&](std::optional<HashAlgorithm> optParsedAlgo) {
if (!optParsedAlgo) // Either the string or user must provide the type, if they both do they
throw BadHash("hash '%s' does not include a type", original); // must agree.
if (!optParsedAlgo)
throw BadHash("hash '%s' does not include a type", original);
return *optParsedAlgo; return *optParsedAlgo;
}); })
.first;
} }
Hash Hash::parseAny(std::string_view original, std::optional<HashAlgorithm> optAlgo) Hash Hash::parseAny(std::string_view original, std::optional<HashAlgorithm> optAlgo)
{
return parseAnyReturningFormat(original, optAlgo).first;
}
std::pair<Hash, HashFormat>
Hash::parseAnyReturningFormat(std::string_view original, std::optional<HashAlgorithm> optAlgo)
{ {
return parseAnyHelper(original, [&](std::optional<HashAlgorithm> optParsedAlgo) { return parseAnyHelper(original, [&](std::optional<HashAlgorithm> optParsedAlgo) {
// Either the string or user must provide the type, if they both do they // Either the string or user must provide the type, if they both do they

View file

@ -79,6 +79,12 @@ struct Hash
*/ */
static Hash parseAny(std::string_view s, std::optional<HashAlgorithm> optAlgo); static Hash parseAny(std::string_view s, std::optional<HashAlgorithm> optAlgo);
/**
* Like `parseAny`, but also returns the format the hash was parsed from.
*/
static std::pair<Hash, HashFormat>
parseAnyReturningFormat(std::string_view s, std::optional<HashAlgorithm> optAlgo);
/** /**
* Parse a hash from a string representation like the above, except the * Parse a hash from a string representation like the above, except the
* type prefix is mandatory is there is no separate argument. * type prefix is mandatory is there is no separate argument.

View file

@ -27,7 +27,7 @@ R""(
```console ```console
# nix hash convert --hash-algo sha256 --from nix32 ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0= # nix hash convert --hash-algo sha256 --from nix32 ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=
error: input hash 'ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=' does not have the expected format '--from nix32' error: input hash 'ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=' has format 'base64', but '--from nix32' was specified
# nix hash convert --hash-algo sha256 --from nix32 1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s # nix hash convert --hash-algo sha256 --from nix32 1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s
sha256-ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0= sha256-ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=

View file

@ -248,11 +248,13 @@ struct CmdHashConvert : Command
void run() override void run() override
{ {
for (const auto & s : hashStrings) { for (const auto & s : hashStrings) {
Hash h = from == HashFormat::SRI ? Hash::parseSRI(s) : Hash::parseAny(s, algo); auto [h, parsedFormat] = Hash::parseAnyReturningFormat(s, algo);
if (from && from != HashFormat::SRI if (from && *from != parsedFormat) {
&& h.to_string(*from, false) != (from == HashFormat::Base16 ? toLower(s) : s)) { throw BadHash(
auto from_as_string = printHashFormat(*from); "input hash '%s' has format '%s', but '--from %s' was specified",
throw BadHash("input hash '%s' does not have the expected format for '--from %s'", s, from_as_string); s,
printHashFormat(parsedFormat),
printHashFormat(*from));
} }
logger->cout(h.to_string(to, to == HashFormat::SRI)); logger->cout(h.to_string(to, to == HashFormat::SRI));
} }

View file

@ -93,10 +93,10 @@ try3() {
# Asserting input format fails. # Asserting input format fails.
# #
expectStderr 1 nix hash convert --hash-algo "$1" --from sri "$2" | grepQuiet "is not SRI" expectStderr 1 nix hash convert --hash-algo "$1" --from sri "$2" | grepQuiet "'base16', but '--from sri'"
expectStderr 1 nix hash convert --hash-algo "$1" --from nix32 "$2" | grepQuiet "input hash" expectStderr 1 nix hash convert --hash-algo "$1" --from nix32 "$2" | grepQuiet "'base16', but '--from nix32'"
expectStderr 1 nix hash convert --hash-algo "$1" --from base16 "$3" | grepQuiet "input hash" expectStderr 1 nix hash convert --hash-algo "$1" --from base16 "$3" | grepQuiet "'nix32', but '--from base16'"
expectStderr 1 nix hash convert --hash-algo "$1" --from nix32 "$4" | grepQuiet "input hash" expectStderr 1 nix hash convert --hash-algo "$1" --from nix32 "$4" | grepQuiet "'base64', but '--from nix32'"
# Base-16 hashes can be in uppercase. # Base-16 hashes can be in uppercase.
nix hash convert --hash-algo "$1" --from base16 "$(echo "$2" | tr '[:lower:]' '[:upper:]')" nix hash convert --hash-algo "$1" --from base16 "$(echo "$2" | tr '[:lower:]' '[:upper:]')"