aboutsummaryrefslogtreecommitdiff
path: root/src/libexpr/value
diff options
context:
space:
mode:
authorJohn Ericson <John.Ericson@Obsidian.Systems>2023-01-03 11:44:59 -0500
committerJohn Ericson <John.Ericson@Obsidian.Systems>2023-01-10 13:10:49 -0500
commit5576d5e987e907bf13ae6c7fe79ececce4e86e2d (patch)
tree058d40aea1fc3775061a99baa838a20e09963ba6 /src/libexpr/value
parentda64f026dd7b12d72ffbc15752e8b95707fa1f9f (diff)
Parse string context elements properly
Prior to this change, we had a bunch of ad-hoc string manipulation code scattered around. This made it hard to figure out what data model for string contexts is. Now, we still store string contexts most of the time as encoded strings --- I was wary of the performance implications of changing that --- but whenever we parse them we do so only through the `NixStringContextElem::parse` method, which handles all cases. This creates a data type that is very similar to `DerivedPath` but: - Represents the funky `=<drvpath>` case as properly distinct from the others. - Only encodes a single output, no wildcards and no set, for the "built" case. (I would like to deprecate `=<path>`, after which we are in spitting distance of `DerivedPath` and could maybe get away with fewer types, but that is another topic for another day.)
Diffstat (limited to 'src/libexpr/value')
-rw-r--r--src/libexpr/value/context.cc67
-rw-r--r--src/libexpr/value/context.hh90
2 files changed, 157 insertions, 0 deletions
diff --git a/src/libexpr/value/context.cc b/src/libexpr/value/context.cc
new file mode 100644
index 000000000..61d9c53df
--- /dev/null
+++ b/src/libexpr/value/context.cc
@@ -0,0 +1,67 @@
+#include "value/context.hh"
+#include "store-api.hh"
+
+#include <optional>
+
+namespace nix {
+
+NixStringContextElem NixStringContextElem::parse(const Store & store, std::string_view s0)
+{
+ std::string_view s = s0;
+
+ if (s.size() == 0) {
+ throw BadNixStringContextElem(s0,
+ "String context element should never be an empty string");
+ }
+ switch (s.at(0)) {
+ case '!': {
+ s = s.substr(1); // advance string to parse after first !
+ size_t index = s.find("!");
+ // This makes index + 1 safe. Index can be the length (one after index
+ // of last character), so given any valid character index --- a
+ // successful find --- we can add one.
+ if (index == std::string_view::npos) {
+ throw BadNixStringContextElem(s0,
+ "String content element beginning with '!' should have a second '!'");
+ }
+ return NixStringContextElem::Built {
+ .drvPath = store.parseStorePath(s.substr(index + 1)),
+ .output = std::string(s.substr(0, index)),
+ };
+ }
+ case '=': {
+ return NixStringContextElem::DrvDeep {
+ .drvPath = store.parseStorePath(s.substr(1)),
+ };
+ }
+ default: {
+ return NixStringContextElem::Opaque {
+ .path = store.parseStorePath(s),
+ };
+ }
+ }
+}
+
+std::string NixStringContextElem::to_string(const Store & store) const {
+ return std::visit(overloaded {
+ [&](const NixStringContextElem::Built & b) {
+ std::string res;
+ res += '!';
+ res += b.output;
+ res += '!';
+ res += store.printStorePath(b.drvPath);
+ return res;
+ },
+ [&](const NixStringContextElem::DrvDeep & d) {
+ std::string res;
+ res += '=';
+ res += store.printStorePath(d.drvPath);
+ return res;
+ },
+ [&](const NixStringContextElem::Opaque & o) {
+ return store.printStorePath(o.path);
+ },
+ }, raw());
+}
+
+}
diff --git a/src/libexpr/value/context.hh b/src/libexpr/value/context.hh
new file mode 100644
index 000000000..d8008c436
--- /dev/null
+++ b/src/libexpr/value/context.hh
@@ -0,0 +1,90 @@
+#pragma once
+
+#include "util.hh"
+#include "path.hh"
+
+#include <optional>
+
+#include <nlohmann/json_fwd.hpp>
+
+namespace nix {
+
+class BadNixStringContextElem : public Error
+{
+public:
+ std::string_view raw;
+
+ template<typename... Args>
+ BadNixStringContextElem(std::string_view raw_, const Args & ... args)
+ : Error("")
+ {
+ raw = raw_;
+ auto hf = hintfmt(args...);
+ err.msg = hintfmt("Bad String Context element: %1%: %2%", normaltxt(hf.str()), raw);
+ }
+};
+
+class Store;
+
+/* Plain opaque path to some store object.
+
+ Encoded as just the path: ‘<path>’.
+*/
+struct NixStringContextElem_Opaque {
+ StorePath path;
+};
+
+/* Path to a derivation and its entire build closure.
+
+ The path doesn't just refer to derivation itself and its closure, but
+ also all outputs of all derivations in that closure (including the
+ root derivation).
+
+ Encoded in the form ‘=<drvPath>’.
+*/
+struct NixStringContextElem_DrvDeep {
+ StorePath drvPath;
+};
+
+/* Derivation output.
+
+ Encoded in the form ‘!<output>!<drvPath>’.
+*/
+struct NixStringContextElem_Built {
+ StorePath drvPath;
+ std::string output;
+};
+
+using _NixStringContextElem_Raw = std::variant<
+ NixStringContextElem_Opaque,
+ NixStringContextElem_DrvDeep,
+ NixStringContextElem_Built
+>;
+
+struct NixStringContextElem : _NixStringContextElem_Raw {
+ using Raw = _NixStringContextElem_Raw;
+ using Raw::Raw;
+
+ using Opaque = NixStringContextElem_Opaque;
+ using DrvDeep = NixStringContextElem_DrvDeep;
+ using Built = NixStringContextElem_Built;
+
+ inline const Raw & raw() const {
+ return static_cast<const Raw &>(*this);
+ }
+ inline Raw & raw() {
+ return static_cast<Raw &>(*this);
+ }
+
+ /* Decode a context string, one of:
+ - ‘<path>’
+ - ‘=<path>’
+ - ‘!<name>!<path>’
+ */
+ static NixStringContextElem parse(const Store & store, std::string_view s);
+ std::string to_string(const Store & store) const;
+};
+
+typedef std::vector<NixStringContextElem> NixStringContext;
+
+}