feat(tvix/serde): initial Nix->serde::Deserialize impl

This will make it possible fairly easily use Nix to represent
arbitrary data structures, e.g. for using Nix as a config language.

Only pure Nix (i.e. no `import` etc.) is supported for now.

Not all types, specifically no struct traversal, are implemented in
this commit.

Change-Id: I9ac91a229a0d12bf818e6e3249f3e5a691599a2c
Reviewed-on: https://cl.tvl.fyi/c/depot/+/7712
Tested-by: BuildkiteCI
Reviewed-by: flokli <flokli@flokli.de>
This commit is contained in:
Vincent Ambo 2022-12-31 18:13:59 +03:00 committed by tazjin
parent 49ee3e3b14
commit 90c32eec7a
8 changed files with 485 additions and 0 deletions

8
tvix/Cargo.lock generated
View file

@ -2214,6 +2214,14 @@ dependencies = [
name = "tvix-nar" name = "tvix-nar"
version = "0.0.0" version = "0.0.0"
[[package]]
name = "tvix-serde"
version = "0.1.0"
dependencies = [
"serde",
"tvix-eval",
]
[[package]] [[package]]
name = "tvix-store" name = "tvix-store"
version = "0.1.0" version = "0.1.0"

View file

@ -93,6 +93,16 @@ rec {
# File a bug if you depend on any for non-debug work! # File a bug if you depend on any for non-debug work!
debug = internal.debugCrate { inherit packageId; }; debug = internal.debugCrate { inherit packageId; };
}; };
"tvix-serde" = rec {
packageId = "tvix-serde";
build = internal.buildRustCrateWithFeatures {
packageId = "tvix-serde";
};
# Debug support which might change between releases.
# File a bug if you depend on any for non-debug work!
debug = internal.debugCrate { inherit packageId; };
};
"tvix-store" = rec { "tvix-store" = rec {
packageId = "tvix-store"; packageId = "tvix-store";
build = internal.buildRustCrateWithFeatures { build = internal.buildRustCrateWithFeatures {
@ -6608,6 +6618,28 @@ rec {
then lib.cleanSourceWith { filter = sourceFilter; src = ./nar; } then lib.cleanSourceWith { filter = sourceFilter; src = ./nar; }
else ./nar; else ./nar;
};
"tvix-serde" = rec {
crateName = "tvix-serde";
version = "0.1.0";
edition = "2021";
# We can't filter paths with references in Nix 2.4
# See https://github.com/NixOS/nix/issues/5410
src =
if (lib.versionOlder builtins.nixVersion "2.4pre20211007")
then lib.cleanSourceWith { filter = sourceFilter; src = ./serde; }
else ./serde;
dependencies = [
{
name = "serde";
packageId = "serde";
}
{
name = "tvix-eval";
packageId = "tvix-eval";
}
];
}; };
"tvix-store" = rec { "tvix-store" = rec {
crateName = "tvix-store"; crateName = "tvix-store";

View file

@ -24,6 +24,7 @@ members = [
"eval/builtin-macros", "eval/builtin-macros",
"nar", "nar",
"nix_cli", "nix_cli",
"serde",
"store", "store",
] ]

8
tvix/serde/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "tvix-serde"
version = "0.1.0"
edition = "2021"
[dependencies]
tvix-eval = { path = "../eval" }
serde = "1.0"

5
tvix/serde/default.nix Normal file
View file

@ -0,0 +1,5 @@
{ depot, ... }:
depot.tvix.crates.workspaceMembers.tvix-serde.build.override {
runTests = true;
}

331
tvix/serde/src/de.rs Normal file
View file

@ -0,0 +1,331 @@
//! Deserialisation from Nix to Rust values.
use serde::de;
use tvix_eval::Value;
use crate::error::Error;
struct Deserializer {
value: tvix_eval::Value,
}
pub fn from_str<'code, T>(src: &'code str) -> Result<T, Error>
where
T: serde::Deserialize<'code>,
{
// First step is to evaluate the Nix code ...
let eval = tvix_eval::Evaluation::new(src, None);
let source = eval.source_map();
let result = eval.evaluate();
if !result.errors.is_empty() {
return Err(Error::NixErrors {
errors: result.errors,
source,
});
}
let de = Deserializer {
value: result.value.expect("value should be present on success"),
};
T::deserialize(de)
}
fn unexpected(expected: &'static str, got: &Value) -> Error {
Error::UnexpectedType {
expected,
got: got.type_of(),
}
}
fn visit_integer<I: TryFrom<i64>>(v: &Value) -> Result<I, Error> {
match v {
Value::Integer(i) => I::try_from(*i).map_err(|_| Error::IntegerConversion {
got: *i,
need: std::any::type_name::<I>(),
}),
_ => Err(unexpected("integer", v)),
}
}
impl<'de> de::Deserializer<'de> for Deserializer {
type Error = Error;
fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
match self.value {
Value::Null => visitor.visit_unit(),
Value::Bool(b) => visitor.visit_bool(b),
Value::Integer(i) => visitor.visit_i64(i),
Value::Float(f) => visitor.visit_f64(f),
Value::String(s) => visitor.visit_string(s.to_string()),
Value::Path(p) => visitor.visit_string(p.to_string_lossy().into()), // TODO: hmm
Value::Attrs(_) => self.deserialize_map(visitor),
Value::List(_) => self.deserialize_seq(visitor),
// tvix-eval types that can not be deserialized through serde.
Value::Closure(_)
| Value::Builtin(_)
| Value::Thunk(_)
| Value::AttrNotFound
| Value::Blueprint(_)
| Value::DeferredUpvalue(_)
| Value::UnresolvedPath(_) => Err(Error::Unserializable {
value_type: self.value.type_of(),
}),
}
}
fn deserialize_bool<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
match self.value {
Value::Bool(b) => visitor.visit_bool(b),
_ => Err(unexpected("bool", &self.value)),
}
}
fn deserialize_i8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_i8(visit_integer(&self.value)?)
}
fn deserialize_i16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_i16(visit_integer(&self.value)?)
}
fn deserialize_i32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_i32(visit_integer(&self.value)?)
}
fn deserialize_i64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_i64(visit_integer(&self.value)?)
}
fn deserialize_u8<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_u8(visit_integer(&self.value)?)
}
fn deserialize_u16<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_u16(visit_integer(&self.value)?)
}
fn deserialize_u32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_u32(visit_integer(&self.value)?)
}
fn deserialize_u64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_u64(visit_integer(&self.value)?)
}
fn deserialize_f32<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
if let Value::Float(f) = self.value {
return visitor.visit_f32(f as f32);
}
Err(unexpected("float", &self.value))
}
fn deserialize_f64<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
if let Value::Float(f) = self.value {
return visitor.visit_f64(f);
}
Err(unexpected("float", &self.value))
}
fn deserialize_char<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
if let Value::String(s) = &self.value {
let chars = s.as_str().chars().collect::<Vec<_>>();
if chars.len() == 1 {
return visitor.visit_char(chars[0]);
}
}
Err(unexpected("char", &self.value))
}
fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
if let Value::String(s) = &self.value {
return visitor.visit_str(s.as_str());
}
Err(unexpected("string", &self.value))
}
fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
if let Value::String(s) = &self.value {
return visitor.visit_str(s.as_str());
}
Err(unexpected("string", &self.value))
}
fn deserialize_bytes<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!("how to represent this?");
}
fn deserialize_byte_buf<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!("how to represent this?");
}
fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!("how to represent this?");
}
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
if let Value::Null = self.value {
return visitor.visit_unit();
}
Err(unexpected("null", &self.value))
}
fn deserialize_unit_struct<V>(
self,
name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!("how to represent this?");
}
fn deserialize_newtype_struct<V>(
self,
name: &'static str,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!("how to represent this?");
}
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!()
}
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!()
}
fn deserialize_tuple_struct<V>(
self,
name: &'static str,
len: usize,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!()
}
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!()
}
fn deserialize_struct<V>(
self,
name: &'static str,
fields: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!()
}
fn deserialize_enum<V>(
self,
name: &'static str,
variants: &'static [&'static str],
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!()
}
fn deserialize_identifier<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
todo!()
}
fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: de::Visitor<'de>,
{
visitor.visit_unit()
}
}

92
tvix/serde/src/error.rs Normal file
View file

@ -0,0 +1,92 @@
//! When serialising Nix goes wrong ...
use std::error;
use std::fmt::Display;
#[derive(Clone, Debug)]
pub enum Error {
/// Attempted to deserialise an unsupported Nix value (such as a
/// function) that can not be represented by the
/// [`serde::Deserialize`] trait.
Unserializable { value_type: &'static str },
/// Expected to deserialize a value that is unsupported by Nix.
Unsupported { wanted: &'static str },
/// Expected a specific type, but got something else on the Nix side.
UnexpectedType {
expected: &'static str,
got: &'static str,
},
/// Deserialisation error returned from `serde::de`.
Deserialization(String),
/// Deserialized integer did not fit.
IntegerConversion { got: i64, need: &'static str },
/// Evaluation of the supplied Nix code failed while computing the
/// value for deserialisation.
NixErrors {
errors: Vec<tvix_eval::Error>,
source: tvix_eval::SourceCode,
},
}
impl Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::Unserializable { value_type } => write!(
f,
"can not deserialise a Nix '{}' into a Rust type",
value_type
),
Error::Unsupported { wanted } => {
write!(f, "can not deserialize a '{}' from a Nix value", wanted)
}
Error::UnexpectedType { expected, got } => {
write!(f, "expected type {}, but got Nix type {}", expected, got)
}
Error::NixErrors { errors, source } => {
writeln!(
f,
"{} occured during Nix evaluation: ",
if errors.len() == 1 { "error" } else { "errors" }
)?;
for err in errors {
writeln!(f, "{}", err.fancy_format_str(&source))?;
}
Ok(())
}
Error::Deserialization(err) => write!(f, "deserialisation error occured: {}", err),
Error::IntegerConversion { got, need } => {
write!(f, "i64({}) does not fit in a {}", got, need)
}
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Self::NixErrors { errors, .. } => errors.first().map(|e| e as &dyn error::Error),
_ => None,
}
}
}
impl serde::de::Error for Error {
fn custom<T>(err: T) -> Self
where
T: Display,
{
Self::Deserialization(err.to_string())
}
}

8
tvix/serde/src/lib.rs Normal file
View file

@ -0,0 +1,8 @@
//! `tvix-serde` implements (de-)serialisation of Rust data structures
//! to/from Nix. This is intended to make it easy to use Nix as as
//! configuration language.
mod de;
mod error;
pub use de::from_str;