about summary refs log tree commit diff
path: root/tvix/eval/src/value
diff options
context:
space:
mode:
authorGriffin Smith <root@gws.fyi>2022-10-10T04·32-0400
committergrfn <grfn@gws.fyi>2022-10-15T20·35+0000
commit5eb89be68246f1e5a8cd28e48d5cec75921ca97a (patch)
tree73a8d48a4c04e2b41ef100b18560c438e9a0832c /tvix/eval/src/value
parent277c69cbe5aac853b26d6173e07262f8cc7aff12 (diff)
feat(tvix/eval): Implement builtins.fromJSON r/5135
Using `serde_json` for parsing JSON here, plus an `impl FromJSON for
Value`. The latter is primarily to stay "dependency light" for now -
likely going with an actual serde `Deserialize` impl in the future is
going to be way better as it allows saving significantly on intermediary
allocations.

Change-Id: I152a0448ff7c87cf7ebaac927c38912b99de1c18
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6920
Tested-by: BuildkiteCI
Reviewed-by: tazjin <tazjin@tvl.su>
Diffstat (limited to 'tvix/eval/src/value')
-rw-r--r--tvix/eval/src/value/attrs.rs14
-rw-r--r--tvix/eval/src/value/mod.rs48
2 files changed, 58 insertions, 4 deletions
diff --git a/tvix/eval/src/value/attrs.rs b/tvix/eval/src/value/attrs.rs
index 318a8cfa8209..e9d5a239a3cf 100644
--- a/tvix/eval/src/value/attrs.rs
+++ b/tvix/eval/src/value/attrs.rs
@@ -274,6 +274,12 @@ impl NixAttrs {
         NixAttrs(AttrsRep::Map(map))
     }
 
+    /// Construct an optimized "KV"-style attribute set given the value for the
+    /// `"name"` key, and the value for the `"value"` key
+    pub(crate) fn from_kv(name: Value, value: Value) -> Self {
+        NixAttrs(AttrsRep::KV { name, value })
+    }
+
     /// Compare `self` against `other` for equality using Nix equality semantics
     pub fn nix_eq(&self, other: &Self, vm: &mut VM) -> Result<bool, ErrorKind> {
         match (&self.0, &other.0) {
@@ -376,10 +382,10 @@ fn attempt_optimise_kv(slice: &mut [Value]) -> Option<NixAttrs> {
         }
     };
 
-    Some(NixAttrs(AttrsRep::KV {
-        name: slice[name_idx].clone(),
-        value: slice[value_idx].clone(),
-    }))
+    Some(NixAttrs::from_kv(
+        slice[name_idx].clone(),
+        slice[value_idx].clone(),
+    ))
 }
 
 /// Set an attribute on an in-construction attribute set, while
diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs
index 8672ffc1bb89..175b33bfa2e8 100644
--- a/tvix/eval/src/value/mod.rs
+++ b/tvix/eval/src/value/mod.rs
@@ -390,6 +390,54 @@ impl From<PathBuf> for Value {
     }
 }
 
+impl From<Vec<Value>> for Value {
+    fn from(val: Vec<Value>) -> Self {
+        Self::List(NixList::from(val))
+    }
+}
+
+impl TryFrom<serde_json::Value> for Value {
+    type Error = ErrorKind;
+
+    fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
+        // TODO(grfn): Replace with a real serde::Deserialize impl (for perf)
+        match value {
+            serde_json::Value::Null => Ok(Self::Null),
+            serde_json::Value::Bool(b) => Ok(Self::Bool(b)),
+            serde_json::Value::Number(n) => {
+                if let Some(i) = n.as_i64() {
+                    Ok(Self::Integer(i))
+                } else if let Some(f) = n.as_f64() {
+                    Ok(Self::Float(f))
+                } else {
+                    Err(ErrorKind::FromJsonError(format!(
+                        "JSON number not representable as Nix value: {n}"
+                    )))
+                }
+            }
+            serde_json::Value::String(s) => Ok(s.into()),
+            serde_json::Value::Array(a) => Ok(a
+                .into_iter()
+                .map(Value::try_from)
+                .collect::<Result<Vec<_>, _>>()?
+                .into()),
+            serde_json::Value::Object(obj) => {
+                match (obj.len(), obj.get("name"), obj.get("value")) {
+                    (2, Some(name), Some(value)) => Ok(Self::attrs(NixAttrs::from_kv(
+                        name.clone().try_into()?,
+                        value.clone().try_into()?,
+                    ))),
+                    _ => Ok(Self::attrs(NixAttrs::from_map(
+                        obj.into_iter()
+                            .map(|(k, v)| Ok((k.into(), v.try_into()?)))
+                            .collect::<Result<_, ErrorKind>>()?,
+                    ))),
+                }
+            }
+        }
+    }
+}
+
 fn type_error(expected: &'static str, actual: &Value) -> ErrorKind {
     ErrorKind::TypeError {
         expected,