feat(tvix/eval): implement serde::Deserialize for Value

Co-Authored-By: Vincent Ambo <tazjin@tvl.su>

Change-Id: Ib6f7d1f4f4faac36b44f5f75cccc57bf912cf606
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7626
Reviewed-by: tazjin <tazjin@tvl.su>
Tested-by: BuildkiteCI
This commit is contained in:
Ryan Lahfa 2022-12-24 18:18:26 +01:00 committed by tazjin
parent c011a6130c
commit 805219a2fa
10 changed files with 114 additions and 50 deletions

1
tvix/Cargo.lock generated
View file

@ -840,6 +840,7 @@ dependencies = [
"imbl-sized-chunks", "imbl-sized-chunks",
"rand_core 0.6.4", "rand_core 0.6.4",
"rand_xoshiro", "rand_xoshiro",
"serde",
"version_check", "version_check",
] ]

View file

@ -2453,6 +2453,11 @@ rec {
name = "rand_xoshiro"; name = "rand_xoshiro";
packageId = "rand_xoshiro"; packageId = "rand_xoshiro";
} }
{
name = "serde";
packageId = "serde";
optional = true;
}
]; ];
buildDependencies = [ buildDependencies = [
{ {
@ -2460,6 +2465,12 @@ rec {
packageId = "version_check"; packageId = "version_check";
} }
]; ];
devDependencies = [
{
name = "serde";
packageId = "serde";
}
];
features = { features = {
"arbitrary" = [ "dep:arbitrary" ]; "arbitrary" = [ "dep:arbitrary" ];
"proptest" = [ "dep:proptest" ]; "proptest" = [ "dep:proptest" ];
@ -2468,6 +2479,7 @@ rec {
"refpool" = [ "dep:refpool" ]; "refpool" = [ "dep:refpool" ];
"serde" = [ "dep:serde" ]; "serde" = [ "dep:serde" ];
}; };
resolvedDefaultFeatures = [ "serde" ];
}; };
"imbl-sized-chunks" = rec { "imbl-sized-chunks" = rec {
crateName = "imbl-sized-chunks"; crateName = "imbl-sized-chunks";
@ -4700,7 +4712,7 @@ rec {
"derive" = [ "serde_derive" ]; "derive" = [ "serde_derive" ];
"serde_derive" = [ "dep:serde_derive" ]; "serde_derive" = [ "dep:serde_derive" ];
}; };
resolvedDefaultFeatures = [ "alloc" "default" "derive" "serde_derive" "std" ]; resolvedDefaultFeatures = [ "alloc" "default" "derive" "rc" "serde_derive" "std" ];
}; };
"serde_derive" = rec { "serde_derive" = rec {
crateName = "serde_derive"; crateName = "serde_derive";
@ -6570,6 +6582,7 @@ rec {
{ {
name = "imbl"; name = "imbl";
packageId = "imbl"; packageId = "imbl";
features = [ "serde" ];
} }
{ {
name = "path-clean"; name = "path-clean";
@ -6597,6 +6610,7 @@ rec {
{ {
name = "serde"; name = "serde";
packageId = "serde"; packageId = "serde";
features = [ "rc" "derive" ];
} }
{ {
name = "serde_json"; name = "serde_json";

View file

@ -14,13 +14,13 @@ builtin-macros = { path = "./builtin-macros", package = "tvix-eval-builtin-macro
codemap = "0.1.3" codemap = "0.1.3"
codemap-diagnostic = "0.1.1" codemap-diagnostic = "0.1.1"
dirs = "4.0.0" dirs = "4.0.0"
imbl = "2.0" imbl = { version = "2.0", features = [ "serde" ] }
path-clean = "0.1" path-clean = "0.1"
proptest = { version = "1.0.0", default_features = false, features = ["std", "alloc", "break-dead-code", "tempfile"], optional = true } proptest = { version = "1.0.0", default_features = false, features = ["std", "alloc", "break-dead-code", "tempfile"], optional = true }
regex = "1.6.0" regex = "1.6.0"
rnix = "0.11.0" rnix = "0.11.0"
rowan = "*" # pinned by rnix rowan = "*" # pinned by rnix
serde = "1.0" serde = { version = "1.0", features = [ "rc", "derive" ] }
serde_json = "1.0" serde_json = "1.0"
smol_str = "0.1" smol_str = "0.1"
tabwriter = "1.2" tabwriter = "1.2"

View file

@ -339,8 +339,8 @@ mod pure_builtins {
#[builtin("fromJSON")] #[builtin("fromJSON")]
fn builtin_from_json(_: &mut VM, json: Value) -> Result<Value, ErrorKind> { fn builtin_from_json(_: &mut VM, json: Value) -> Result<Value, ErrorKind> {
let json_str = json.to_str()?; let json_str = json.to_str()?;
let json: serde_json::Value = serde_json::from_str(&json_str)?;
json.try_into() serde_json::from_str(&json_str).map_err(|err| err.into())
} }
#[builtin("genericClosure")] #[builtin("genericClosure")]

View file

@ -1 +1 @@
[ { Image = { Animated = false; Height = 600; IDs = [ 116 943 234 38793 true false null -100 ]; Latitude = 37.7668; Longitude = -122.3959; Thumbnail = { Height = 125; Url = "http://www.example.com/image/481989943"; Width = 100; }; Title = "View from 15th Floor"; Width = 800; }; } { name = "a"; value = "b"; } ] [ { Image = { Animated = false; Height = 600; IDs = [ 116 943 234 38793 true false null -100 ]; Latitude = 37.7668; Longitude = -122.3959; Thumbnail = { Height = 125; Url = "http://www.example.com/image/481989943"; Width = 100; }; Title = "View from 15th Floor"; Width = 800; }; } { name = "a"; value = "b"; } [ 1 2 3 4 ] ]

View file

@ -20,4 +20,5 @@
} }
'') '')
(builtins.fromJSON ''{"name": "a", "value": "b"}'') (builtins.fromJSON ''{"name": "a", "value": "b"}'')
(builtins.fromJSON "[ 1, 2, 3, 4 ]")
] ]

View file

@ -8,6 +8,8 @@
use std::iter::FromIterator; use std::iter::FromIterator;
use imbl::{ordmap, OrdMap}; use imbl::{ordmap, OrdMap};
use serde::de::{Deserializer, Error, Visitor};
use serde::Deserialize;
use crate::errors::ErrorKind; use crate::errors::ErrorKind;
use crate::vm::VM; use crate::vm::VM;
@ -20,7 +22,7 @@ use super::Value;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
#[derive(Clone, Debug)] #[derive(Clone, Debug, Deserialize)]
enum AttrsRep { enum AttrsRep {
Empty, Empty,
@ -138,6 +140,39 @@ impl TotalDisplay for NixAttrs {
} }
} }
impl<'de> Deserialize<'de> for NixAttrs {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct MapVisitor;
impl<'de> Visitor<'de> for MapVisitor {
type Value = NixAttrs;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a valid Nix attribute set")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: serde::de::MapAccess<'de>,
{
let mut stack_array = Vec::with_capacity(map.size_hint().unwrap_or(0) * 2);
while let Some((key, value)) = map.next_entry()? {
stack_array.push(key);
stack_array.push(value);
}
NixAttrs::construct(stack_array.len() / 2, stack_array).map_err(A::Error::custom)
}
}
deserializer.deserialize_map(MapVisitor)
}
}
#[cfg(feature = "arbitrary")] #[cfg(feature = "arbitrary")]
mod arbitrary { mod arbitrary {
use super::*; use super::*;

View file

@ -3,6 +3,8 @@ use std::ops::Index;
use imbl::{vector, Vector}; use imbl::{vector, Vector};
use serde::Deserialize;
use crate::errors::ErrorKind; use crate::errors::ErrorKind;
use crate::vm::VM; use crate::vm::VM;
@ -11,7 +13,7 @@ use super::TotalDisplay;
use super::Value; use super::Value;
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, Deserialize)]
pub struct NixList(Vector<Value>); pub struct NixList(Vector<Value>);
impl TotalDisplay for NixList { impl TotalDisplay for NixList {

View file

@ -6,6 +6,8 @@ use std::path::PathBuf;
use std::rc::Rc; use std::rc::Rc;
use std::{cell::Ref, fmt::Display}; use std::{cell::Ref, fmt::Display};
use serde::Deserialize;
#[cfg(feature = "arbitrary")] #[cfg(feature = "arbitrary")]
mod arbitrary; mod arbitrary;
mod attrs; mod attrs;
@ -31,30 +33,41 @@ pub use thunk::Thunk;
use self::thunk::ThunkSet; use self::thunk::ThunkSet;
#[warn(variant_size_differences)] #[warn(variant_size_differences)]
#[derive(Clone, Debug)] #[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
pub enum Value { pub enum Value {
Null, Null,
Bool(bool), Bool(bool),
Integer(i64), Integer(i64),
Float(f64), Float(f64),
String(NixString), String(NixString),
#[serde(skip)]
Path(PathBuf), Path(PathBuf),
Attrs(Box<NixAttrs>), Attrs(Box<NixAttrs>),
List(NixList), List(NixList),
#[serde(skip)]
Closure(Rc<Closure>), // must use Rc<Closure> here in order to get proper pointer equality Closure(Rc<Closure>), // must use Rc<Closure> here in order to get proper pointer equality
#[serde(skip)]
Builtin(Builtin), Builtin(Builtin),
// Internal values that, while they technically exist at runtime, // Internal values that, while they technically exist at runtime,
// are never returned to or created directly by users. // are never returned to or created directly by users.
#[serde(skip)]
Thunk(Thunk), Thunk(Thunk),
// See [`compiler::compile_select_or()`] for explanation // See [`compiler::compile_select_or()`] for explanation
#[serde(skip)]
AttrNotFound, AttrNotFound,
// this can only occur in Chunk::Constants and nowhere else // this can only occur in Chunk::Constants and nowhere else
#[serde(skip)]
Blueprint(Rc<Lambda>), Blueprint(Rc<Lambda>),
#[serde(skip)]
DeferredUpvalue(StackIdx), DeferredUpvalue(StackIdx),
#[serde(skip)]
UnresolvedPath(PathBuf), UnresolvedPath(PathBuf),
} }
@ -542,47 +555,9 @@ impl From<PathBuf> for Value {
} }
} }
impl TryFrom<serde_json::Value> for Value { impl From<Vec<Value>> for Value {
type Error = ErrorKind; fn from(val: Vec<Value>) -> Self {
Self::List(NixList::from_vec(val))
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(Value::List(
a.into_iter()
.map(Value::try_from)
.collect::<Result<imbl::Vector<_>, _>>()?
.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_iter(
obj.into_iter()
.map(|(k, v)| Ok((k.into(), v.try_into()?)))
.collect::<Result<Vec<(NixString, Value)>, ErrorKind>>()?
.into_iter(),
))),
}
}
}
} }
} }

View file

@ -8,6 +8,9 @@ use std::ops::Deref;
use std::path::Path; use std::path::Path;
use std::{borrow::Cow, fmt::Display, str::Chars}; use std::{borrow::Cow, fmt::Display, str::Chars};
use serde::de::{Deserializer, Visitor};
use serde::Deserialize;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
enum StringRepr { enum StringRepr {
Smol(SmolStr), Smol(SmolStr),
@ -68,6 +71,39 @@ impl Hash for NixString {
} }
} }
impl<'de> Deserialize<'de> for NixString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct StringVisitor;
impl<'de> Visitor<'de> for StringVisitor {
type Value = NixString;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a valid Nix string")
}
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.into())
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(v.into())
}
}
deserializer.deserialize_string(StringVisitor)
}
}
#[cfg(feature = "arbitrary")] #[cfg(feature = "arbitrary")]
mod arbitrary { mod arbitrary {
use super::*; use super::*;