about summary refs log tree commit diff
path: root/tvix/eval/src/value/json.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tvix/eval/src/value/json.rs')
-rw-r--r--tvix/eval/src/value/json.rs63
1 files changed, 50 insertions, 13 deletions
diff --git a/tvix/eval/src/value/json.rs b/tvix/eval/src/value/json.rs
index 7ab1655d9055..c48e9c1f4e85 100644
--- a/tvix/eval/src/value/json.rs
+++ b/tvix/eval/src/value/json.rs
@@ -6,6 +6,7 @@
 use super::{CoercionKind, Value};
 use crate::errors::{CatchableErrorKind, ErrorKind};
 use crate::generators::{self, GenCo};
+use crate::NixContext;
 
 use bstr::ByteSlice;
 use serde_json::value::to_value;
@@ -13,22 +14,32 @@ use serde_json::Value as Json; // name clash with *our* `Value`
 use serde_json::{Map, Number};
 
 impl Value {
-    pub async fn into_json(
+    /// Transforms the structure into a JSON
+    /// and accumulate all encountered context in the second's element
+    /// of the return type.
+    pub async fn into_contextful_json(
         self,
         co: &GenCo,
-    ) -> Result<Result<Json, CatchableErrorKind>, ErrorKind> {
+    ) -> Result<Result<(Json, NixContext), CatchableErrorKind>, ErrorKind> {
         let self_forced = generators::request_force(co, self).await;
+        let mut context = NixContext::new();
 
         let value = match self_forced {
             Value::Null => Json::Null,
             Value::Bool(b) => Json::Bool(b),
             Value::Integer(i) => Json::Number(Number::from(i)),
             Value::Float(f) => to_value(f)?,
-            Value::String(s) => Json::String(s.to_str()?.to_owned()),
+            Value::String(s) => {
+                context.mimic(&s);
+
+                Json::String(s.to_str()?.to_owned())
+            }
 
             Value::Path(p) => {
                 let imported = generators::request_path_import(co, *p).await;
-                Json::String(imported.to_string_lossy().to_string())
+                let path = imported.to_string_lossy().to_string();
+                context = context.append(crate::NixContextElement::Plain(path.clone()));
+                Json::String(path)
             }
 
             Value::List(l) => {
@@ -36,7 +47,10 @@ impl Value {
 
                 for val in l.into_iter() {
                     match generators::request_to_json(co, val).await {
-                        Ok(v) => out.push(v),
+                        Ok((v, mut ctx)) => {
+                            context = context.join(&mut ctx);
+                            out.push(v)
+                        }
                         Err(cek) => return Ok(Err(cek)),
                     }
                 }
@@ -62,7 +76,14 @@ impl Value {
                         .await?
                     {
                         Value::Catchable(cek) => return Ok(Err(*cek)),
-                        Value::String(s) => return Ok(Ok(Json::String(s.to_str()?.to_owned()))),
+                        Value::String(s) => {
+                            // We need a fresh context here because `__toString` will discard
+                            // everything.
+                            let mut fresh = NixContext::new();
+                            fresh.mimic(&s);
+
+                            return Ok(Ok((Json::String(s.to_str()?.to_owned()), fresh)));
+                        }
                         _ => panic!("Value::coerce_to_string_() returned a non-string!"),
                     }
                 }
@@ -79,7 +100,10 @@ impl Value {
                     out.insert(
                         name.to_str()?.to_owned(),
                         match generators::request_to_json(co, value).await {
-                            Ok(v) => v,
+                            Ok((v, mut ctx)) => {
+                                context = context.join(&mut ctx);
+                                v
+                            }
                             Err(cek) => return Ok(Err(cek)),
                         },
                     );
@@ -97,21 +121,34 @@ impl Value {
             | val @ Value::Blueprint(_)
             | val @ Value::DeferredUpvalue(_)
             | val @ Value::UnresolvedPath(_)
-            | val @ Value::Json(_)
+            | val @ Value::Json(..)
             | val @ Value::FinaliseRequest(_) => {
                 return Err(ErrorKind::NotSerialisableToJson(val.type_of()))
             }
         };
 
-        Ok(Ok(value))
+        Ok(Ok((value, context)))
     }
 
     /// Generator version of the above, which wraps responses in
-    /// Value::Json.
-    pub(crate) async fn into_json_generator(self, co: GenCo) -> Result<Value, ErrorKind> {
-        match self.into_json(&co).await? {
+    /// [`Value::Json`].
+    pub(crate) async fn into_contextful_json_generator(
+        self,
+        co: GenCo,
+    ) -> Result<Value, ErrorKind> {
+        match self.into_contextful_json(&co).await? {
             Err(cek) => Ok(Value::from(cek)),
-            Ok(json) => Ok(Value::Json(Box::new(json))),
+            Ok((json, ctx)) => Ok(Value::Json(Box::new((json, ctx)))),
         }
     }
+
+    /// Transforms the structure into a JSON
+    /// All the accumulated context is ignored, use [`into_contextful_json`]
+    /// to obtain the resulting context of the JSON object.
+    pub async fn into_json(
+        self,
+        co: &GenCo,
+    ) -> Result<Result<Json, CatchableErrorKind>, ErrorKind> {
+        Ok(self.into_contextful_json(co).await?.map(|(json, _)| json))
+    }
 }