summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--dhall/src/syntax/text/printer.rs4
-rw-r--r--dhall/tests/parser/success/text/templateB.txt2
-rw-r--r--dhall/tests/parser/success/unit/LambdaUnderscoreB.txt2
-rw-r--r--dhall/tests/parser/success/unit/VariableUnderscoreB.txt2
-rw-r--r--dhall/tests/type-inference/failure/unit/AssertAlphaTrap.txt2
-rw-r--r--serde_dhall/src/deserialize.rs16
-rw-r--r--serde_dhall/src/error.rs11
-rw-r--r--serde_dhall/src/lib.rs120
-rw-r--r--serde_dhall/src/options/de.rs (renamed from serde_dhall/src/options.rs)33
-rw-r--r--serde_dhall/src/options/mod.rs36
-rw-r--r--serde_dhall/src/options/ser.rs234
-rw-r--r--serde_dhall/src/serialize.rs396
-rw-r--r--serde_dhall/src/static_type.rs86
-rw-r--r--serde_dhall/src/value.rs139
-rw-r--r--serde_dhall/tests/de.rs150
-rw-r--r--serde_dhall/tests/serde.rs210
-rw-r--r--serde_dhall/tests/simple_value.rs58
18 files changed, 1278 insertions, 224 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5329eca..0fe7814 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
#### [Unreleased]
+- Implement serialization (https://github.com/Nadrieril/dhall-rust/issues/164)
- BREAKING CHANGE: use u64/i64 instead of usize/isize in `NumKind`
#### [0.7.5] - 2020-10-28
diff --git a/dhall/src/syntax/text/printer.rs b/dhall/src/syntax/text/printer.rs
index 0c2eb2e..4b7b8b8 100644
--- a/dhall/src/syntax/text/printer.rs
+++ b/dhall/src/syntax/text/printer.rs
@@ -156,7 +156,9 @@ fn fmt_label(label: &Label, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
};
if s.is_empty() {
write!(f, "``")
- } else if !is_reserved && s.chars().all(|c| c.is_ascii_alphanumeric()) {
+ } else if !is_reserved
+ && s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
+ {
write!(f, "{}", s)
} else {
write!(f, "`{}`", s)
diff --git a/dhall/tests/parser/success/text/templateB.txt b/dhall/tests/parser/success/text/templateB.txt
index 74303d3..939aa18 100644
--- a/dhall/tests/parser/success/text/templateB.txt
+++ b/dhall/tests/parser/success/text/templateB.txt
@@ -1 +1 @@
-λ(record : { `in_ca` : Bool, name : Text, `taxed_value` : Double, value : Double }) → "Hello ${ record.name }\nYou have just won ${ Double/show record.value } dollars!\n${ if record.`in_ca` then "Well, ${ Double/show record.`taxed_value` } dollars, after taxes" else "" }\n"
+λ(record : { in_ca : Bool, name : Text, taxed_value : Double, value : Double }) → "Hello ${ record.name }\nYou have just won ${ Double/show record.value } dollars!\n${ if record.in_ca then "Well, ${ Double/show record.taxed_value } dollars, after taxes" else "" }\n"
diff --git a/dhall/tests/parser/success/unit/LambdaUnderscoreB.txt b/dhall/tests/parser/success/unit/LambdaUnderscoreB.txt
index 10000d0..32719f0 100644
--- a/dhall/tests/parser/success/unit/LambdaUnderscoreB.txt
+++ b/dhall/tests/parser/success/unit/LambdaUnderscoreB.txt
@@ -1 +1 @@
-λ(`_` : T) → x
+λ(_ : T) → x
diff --git a/dhall/tests/parser/success/unit/VariableUnderscoreB.txt b/dhall/tests/parser/success/unit/VariableUnderscoreB.txt
index 5824cab..31354ec 100644
--- a/dhall/tests/parser/success/unit/VariableUnderscoreB.txt
+++ b/dhall/tests/parser/success/unit/VariableUnderscoreB.txt
@@ -1 +1 @@
-`_`
+_
diff --git a/dhall/tests/type-inference/failure/unit/AssertAlphaTrap.txt b/dhall/tests/type-inference/failure/unit/AssertAlphaTrap.txt
index 0798b19..7241266 100644
--- a/dhall/tests/type-inference/failure/unit/AssertAlphaTrap.txt
+++ b/dhall/tests/type-inference/failure/unit/AssertAlphaTrap.txt
@@ -1,4 +1,4 @@
-Type error: error: unbound variable ``_``
+Type error: error: unbound variable `_`
--> <current file>:1:47
|
1 | assert : (\(_: Bool) -> _) === (\(x: Bool) -> _)
diff --git a/serde_dhall/src/deserialize.rs b/serde_dhall/src/deserialize.rs
index 12b4703..0e518ea 100644
--- a/serde_dhall/src/deserialize.rs
+++ b/serde_dhall/src/deserialize.rs
@@ -47,8 +47,6 @@ pub trait FromDhall: Sealed + Sized {
impl<T> Sealed for T where T: serde::de::DeserializeOwned {}
-struct Deserializer<'a>(Cow<'a, SimpleValue>);
-
/// Deserialize a Rust value from a Dhall [`SimpleValue`].
///
/// # Example
@@ -109,6 +107,8 @@ where
}
}
+struct Deserializer<'a>(Cow<'a, SimpleValue>);
+
impl<'de: 'a, 'a> serde::de::IntoDeserializer<'de, Error> for Deserializer<'a> {
type Deserializer = Deserializer<'a>;
fn into_deserializer(self) -> Self::Deserializer {
@@ -171,9 +171,19 @@ impl<'de: 'a, 'a> serde::Deserializer<'de> for Deserializer<'a> {
}
}
+ fn deserialize_unit<V>(self, visitor: V) -> crate::Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ match self.0.as_ref() {
+ SimpleValue::Record(m) if m.is_empty() => visitor.visit_unit(),
+ _ => self.deserialize_any(visitor),
+ }
+ }
+
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
- bytes byte_buf option unit unit_struct newtype_struct seq
+ bytes byte_buf option unit_struct newtype_struct seq
tuple_struct map struct enum identifier ignored_any
}
}
diff --git a/serde_dhall/src/error.rs b/serde_dhall/src/error.rs
index 896e8b9..3309fe4 100644
--- a/serde_dhall/src/error.rs
+++ b/serde_dhall/src/error.rs
@@ -11,6 +11,7 @@ pub struct Error(pub(crate) ErrorKind);
pub(crate) enum ErrorKind {
Dhall(DhallError),
Deserialize(String),
+ Serialize(String),
}
impl From<ErrorKind> for Error {
@@ -24,6 +25,7 @@ impl std::fmt::Display for Error {
match &self.0 {
ErrorKind::Dhall(err) => write!(f, "{}", err),
ErrorKind::Deserialize(err) => write!(f, "{}", err),
+ ErrorKind::Serialize(err) => write!(f, "{}", err),
}
}
}
@@ -38,3 +40,12 @@ impl serde::de::Error for Error {
ErrorKind::Deserialize(msg.to_string()).into()
}
}
+
+impl serde::ser::Error for Error {
+ fn custom<T>(msg: T) -> Self
+ where
+ T: std::fmt::Display,
+ {
+ ErrorKind::Serialize(msg.to_string()).into()
+ }
+}
diff --git a/serde_dhall/src/lib.rs b/serde_dhall/src/lib.rs
index 8291f74..387278b 100644
--- a/serde_dhall/src/lib.rs
+++ b/serde_dhall/src/lib.rs
@@ -12,13 +12,12 @@
//! YAML. It uses the [Serde][serde] serialization library to provide drop-in support for Dhall
//! for any datatype that supports serde (and that's a lot of them !).
//!
-//! This library is limited to deserializing (reading) Dhall values; serializing (writing)
-//! values to Dhall is not supported.
-//!
//! # Basic usage
//!
-//! The main entrypoint of this library is the [`from_str`](fn.from_str.html) function. It reads a string
-//! containing a Dhall expression and deserializes it into any serde-compatible type.
+//! ## Deserialization (reading)
+//!
+//! The entrypoint for deserialization is the [`from_str`](fn.from_str.html) function. It reads a
+//! string containing a Dhall expression and deserializes it into any serde-compatible type.
//!
//! This could mean a common Rust type like `HashMap`:
//!
@@ -65,16 +64,69 @@
//! # }
//! ```
//!
+//! ## Serialization (writing)
+//!
+//! The entrypoint for serialization is the [`serialize`](fn.serialize.html) function. It takes a
+//! serde-compatible type value and serializes it to a string containing a Dhall expression.
+//!
+//! This could mean a common Rust type like `HashMap`:
+//!
+//! ```rust
+//! # fn main() -> serde_dhall::Result<()> {
+//! use std::collections::HashMap;
+//!
+//! let mut map = HashMap::new();
+//! map.insert("x".to_string(), 1u64);
+//! map.insert("y".to_string(), 2u64);
+//!
+//! let string = serde_dhall::serialize(&map).to_string()?;
+//! assert_eq!(
+//! string,
+//! "{ x = 1, y = 2 }".to_string(),
+//! );
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! or a custom datatype, using serde's `derive` mechanism:
+//!
+//! ```rust
+//! # fn main() -> serde_dhall::Result<()> {
+//! use serde::Serialize;
+//!
+//! #[derive(Serialize)]
+//! struct Point {
+//! x: u64,
+//! y: u64,
+//! }
+//!
+//! let data = Point { x: 1, y: 2 };
+//! let string = serde_dhall::serialize(&data).to_string()?;
+//! assert_eq!(
+//! string,
+//! "{ x = 1, y = 2 }".to_string(),
+//! );
+//! # Ok(())
+//! # }
+//! ```
+//!
+//! Beware that in order to serialize empty options, empty lists or enums correctly, you will need
+//! to provide a type annotation!
+//!
//! # Replacing `serde_json` or `serde_yaml`
//!
//! If you used to consume JSON or YAML, you only need to replace [`serde_json::from_str`] or
//! [`serde_yaml::from_str`] with [`serde_dhall::from_str(…).parse()`](fn.from_str.html).
+//! If you used to produce JSON or YAML, you only need to replace [`serde_json::to_string`] or
+//! [`serde_yaml::to_string`] with [`serde_dhall::serialize(…).to_string()`](fn.serialize.html).
//!
//! [`serde_json::from_str`]: https://docs.serde.rs/serde_json/fn.from_str.html
//! [`serde_yaml::from_str`]: https://docs.serde.rs/serde_yaml/fn.from_str.html
+//! [`serde_json::to_string`]: https://docs.serde.rs/serde_json/fn.to_string.html
+//! [`serde_yaml::to_string`]: https://docs.serde.rs/serde_yaml/fn.to_string.html
//!
//!
-//! # Additional Dhall typechecking
+//! # Additional type annotations
//!
//! When deserializing, normal type checking is done to ensure that the returned value is a valid
//! Dhall value. However types are
@@ -82,11 +134,16 @@
//! matches a given Dhall type. That way, a type error will be caught on the Dhall side, and have
//! pretty and explicit errors that point to the source file.
//!
-//! There are two ways to typecheck a Dhall value in this way: you can provide the type manually or
-//! you can let Rust infer it for you.
+//! It is also possible to provide a type annotation when serializing. This is useful in particular
+//! for types like `HashMap` or [`SimpleValue`] that do not have a fixed type as Dhall values.
+//!
+//! Moreover, some values (namely empty options, empty lists, and enums) _require_ a type annotation
+//! in order to be converted to Dhall, because the resulting Dhall value will contain the type
+//! explicitly.
//!
-//! To let Rust infer the appropriate Dhall type, use the [StaticType](trait.StaticType.html)
-//! trait.
+//! There are two ways to provide a type in this way: you can provide it manually or you can let
+//! Rust infer it for you. To let Rust infer the appropriate Dhall type, use the
+//! [StaticType](trait.StaticType.html) trait.
//!
//! ```rust
//! # fn main() -> serde_dhall::Result<()> {
@@ -121,6 +178,28 @@
//! # }
//! ```
//!
+//! ```
+//! # fn main() -> serde_dhall::Result<()> {
+//! use serde::Serialize;
+//! use serde_dhall::{serialize, StaticType};
+//!
+//! #[derive(Serialize, StaticType)]
+//! enum MyOption {
+//! MyNone,
+//! MySome(u64),
+//! }
+//!
+//! let data = MyOption::MySome(0);
+//! let string = serialize(&data)
+//! .static_type_annotation()
+//! .to_string()?;
+//! // The resulting Dhall string depends on the type annotation; it could not have been
+//! // printed without it.
+//! assert_eq!(string, "< MyNone | MySome: Natural >.MySome 0".to_string());
+//! # Ok(())
+//! # }
+//! ```
+//!
//! To provide a type manually, you need a [`SimpleType`](enum.SimpleType.html) value. You
//! can parse it from some Dhall text like you would parse any other value.
//!
@@ -151,12 +230,27 @@
//! # }
//! ```
//!
+//! ```
+//! # fn main() -> serde_dhall::Result<()> {
+//! use serde_dhall::{serialize, from_str, SimpleValue};
+//!
+//! let ty = from_str("< A | B: Bool >").parse()?;
+//! let data = SimpleValue::Union("A".to_string(), None);
+//! let string = serialize(&data)
+//! .type_annotation(&ty)
+//! .to_string()?;
+//! assert_eq!(string, "< A | B: Bool >.A".to_string());
+//! # Ok(())
+//! # }
+//! ```
+//!
//! # Controlling deserialization
//!
//! If you need more control over the process of reading Dhall values, e.g. disabling
//! imports, see the [`Deserializer`] methods.
//!
//! [`Deserializer`]: struct.Deserializer.html
+//! [`SimpleValue`]: enum.SimpleValue.html
//! [dhall]: https://dhall-lang.org/
//! [serde]: https://docs.serde.rs/serde/
//! [serde::Deserialize]: https://docs.serde.rs/serde/trait.Deserialize.html
@@ -169,6 +263,7 @@ mod test_readme {
mod deserialize;
mod error;
mod options;
+mod serialize;
mod static_type;
/// Dhall values
mod value;
@@ -176,10 +271,11 @@ mod value;
#[doc(hidden)]
pub use dhall_proc_macros::StaticType;
-pub(crate) use deserialize::Sealed;
pub use deserialize::{from_simple_value, FromDhall};
pub(crate) use error::ErrorKind;
pub use error::{Error, Result};
-pub use options::{from_file, from_str, Deserializer};
+pub use options::de::{from_file, from_str, Deserializer};
+pub use options::ser::{serialize, Serializer};
+pub use serialize::ToDhall;
pub use static_type::StaticType;
pub use value::{NumKind, SimpleType, SimpleValue, Value};
diff --git a/serde_dhall/src/options.rs b/serde_dhall/src/options/de.rs
index 7b27114..e4f3456 100644
--- a/serde_dhall/src/options.rs
+++ b/serde_dhall/src/options/de.rs
@@ -2,8 +2,9 @@ use std::path::{Path, PathBuf};
use dhall::Parsed;
+use crate::options::{HasAnnot, ManualAnnot, NoAnnot, StaticAnnot, TypeAnnot};
use crate::SimpleType;
-use crate::{Error, ErrorKind, FromDhall, Result, StaticType, Value};
+use crate::{Error, ErrorKind, FromDhall, Result, Value};
#[derive(Debug, Clone)]
enum Source<'a> {
@@ -12,32 +13,6 @@ enum Source<'a> {
// Url(&'a str),
}
-#[derive(Debug, Clone, Copy)]
-pub struct NoAnnot;
-#[derive(Debug, Clone, Copy)]
-pub struct ManualAnnot<'ty>(&'ty SimpleType);
-#[derive(Debug, Clone, Copy)]
-pub struct StaticAnnot;
-
-pub trait HasAnnot<A> {
- fn get_annot(a: &A) -> Option<SimpleType>;
-}
-impl<T> HasAnnot<NoAnnot> for T {
- fn get_annot(_: &NoAnnot) -> Option<SimpleType> {
- None
- }
-}
-impl<'ty, T> HasAnnot<ManualAnnot<'ty>> for T {
- fn get_annot(a: &ManualAnnot<'ty>) -> Option<SimpleType> {
- Some(a.0.clone())
- }
-}
-impl<T: StaticType> HasAnnot<StaticAnnot> for T {
- fn get_annot(_: &StaticAnnot) -> Option<SimpleType> {
- Some(T::static_type())
- }
-}
-
/// Controls how a Dhall value is read.
///
/// This builder exposes the ability to configure how a value is deserialized and what operations
@@ -252,6 +227,7 @@ impl<'a, A> Deserializer<'a, A> {
fn _parse<T>(&self) -> dhall::error::Result<Value>
where
+ A: TypeAnnot,
T: HasAnnot<A>,
{
let parsed = match &self.source {
@@ -263,7 +239,7 @@ impl<'a, A> Deserializer<'a, A> {
} else {
parsed.skip_resolve()?
};
- let typed = match &T::get_annot(&self.annot) {
+ let typed = match &T::get_annot(self.annot) {
None => resolved.typecheck()?,
Some(ty) => resolved.typecheck_with(ty.to_value().as_hir())?,
};
@@ -287,6 +263,7 @@ impl<'a, A> Deserializer<'a, A> {
/// [`StaticType`]: trait.StaticType.html
pub fn parse<T>(&self) -> Result<T>
where
+ A: TypeAnnot,
T: FromDhall + HasAnnot<A>,
{
let val = self
diff --git a/serde_dhall/src/options/mod.rs b/serde_dhall/src/options/mod.rs
new file mode 100644
index 0000000..9241c45
--- /dev/null
+++ b/serde_dhall/src/options/mod.rs
@@ -0,0 +1,36 @@
+use crate::{SimpleType, StaticType};
+
+pub(crate) mod de;
+pub(crate) mod ser;
+
+#[derive(Debug, Clone, Copy)]
+pub struct NoAnnot;
+#[derive(Debug, Clone, Copy)]
+pub struct ManualAnnot<'ty>(&'ty SimpleType);
+#[derive(Debug, Clone, Copy)]
+pub struct StaticAnnot;
+
+pub trait TypeAnnot: Copy {}
+pub trait HasAnnot<A: TypeAnnot> {
+ fn get_annot(a: A) -> Option<SimpleType>;
+}
+
+impl TypeAnnot for NoAnnot {}
+impl TypeAnnot for ManualAnnot<'_> {}
+impl TypeAnnot for StaticAnnot {}
+
+impl<T> HasAnnot<NoAnnot> for T {
+ fn get_annot(_: NoAnnot) -> Option<SimpleType> {
+ None
+ }
+}
+impl<T> HasAnnot<ManualAnnot<'_>> for T {
+ fn get_annot(a: ManualAnnot<'_>) -> Option<SimpleType> {
+ Some(a.0.clone())
+ }
+}
+impl<T: StaticType> HasAnnot<StaticAnnot> for T {
+ fn get_annot(_: StaticAnnot) -> Option<SimpleType> {
+ Some(T::static_type())
+ }
+}
diff --git a/serde_dhall/src/options/ser.rs b/serde_dhall/src/options/ser.rs
new file mode 100644
index 0000000..d74beb0
--- /dev/null
+++ b/serde_dhall/src/options/ser.rs
@@ -0,0 +1,234 @@
+use crate::options::{HasAnnot, ManualAnnot, NoAnnot, StaticAnnot, TypeAnnot};
+use crate::{Result, SimpleType, ToDhall};
+
+/// Controls how a Dhall value is written.
+///
+/// This builder exposes the ability to configure how a value is serialized, and to set type
+/// annotations.
+///
+/// When using [`Serializer`], you'll create it with [`serialize`], then chain calls to methods to
+/// set each option, then call [`to_string`]. This will give you a [`Result<String>`] containing
+/// the input serialized to Dhall.
+///
+/// Note that if you do not provide a type annotation, some values may not be convertible to Dhall,
+/// like empty lists or enums.
+///
+/// [`Serializer`]: struct.Serializer.html
+/// [`serialize`]: fn.serialize.html
+/// [`to_string`]: struct.Serializer.html#method.to_string
+/// [`Result<String>`]: type.Result.html
+///
+/// # Examples
+///
+/// Serializing without a type annotation:
+///
+/// ```rust
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde_dhall::serialize;
+///
+/// let string = serialize(&1i64).to_string()?;
+/// assert_eq!(string, "+1".to_string());
+/// # Ok(())
+/// # }
+/// ```
+///
+/// Serializing with an automatic type annotation:
+///
+/// ```rust
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde_dhall::serialize;
+///
+/// let data: Option<u64> = None;
+/// let string = serialize(&data).static_type_annotation().to_string()?;
+/// assert_eq!(string, "None Natural".to_string());
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Debug, Clone)]
+pub struct Serializer<'a, T, A> {
+ data: &'a T,
+ annot: A,
+}
+
+impl<'a, T> Serializer<'a, T, NoAnnot> {
+ /// Provides a type to the serialization process. The provided value will be checked against
+ /// that type, and the type will be used when Dhall needs it, like for empty lists or for
+ /// unions.
+ ///
+ /// In many cases the Dhall type that corresponds to a Rust type can be inferred automatically.
+ /// See the [`StaticType`] trait and the [`static_type_annotation`] method for that.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # fn main() -> serde_dhall::Result<()> {
+ /// use serde_dhall::{serialize, from_str, SimpleType, SimpleValue};
+ ///
+ /// let ty = from_str("< A | B: Bool >").parse()?;
+ /// let data = SimpleValue::Union("A".to_string(), None);
+ /// let string = serialize(&data)
+ /// .type_annotation(&ty)
+ /// .to_string()?;
+ /// assert_eq!(string, "< A | B: Bool >.A".to_string());
+ ///
+ /// // Invalid data fails the type validation; serialization would have succeeded otherwise.
+ /// let ty = SimpleType::Integer;
+ /// assert!(
+ /// serialize(&Some(0u64))
+ /// .type_annotation(&ty)
+ /// .to_string()
+ /// .is_err()
+ /// );
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`static_type_annotation`]: struct.Serializer.html#method.static_type_annotation
+ /// [`StaticType`]: trait.StaticType.html
+ pub fn type_annotation<'ty>(
+ self,
+ ty: &'ty SimpleType,
+ ) -> Serializer<'a, T, ManualAnnot<'ty>> {
+ Serializer {
+ annot: ManualAnnot(ty),
+ data: self.data,
+ }
+ }
+
+ /// Uses the type of `T` in the serialization process. This will be used when Dhall needs it,
+ /// like for empty lists or for unions.
+ ///
+ /// `T` must implement the [`StaticType`] trait. If it doesn't, you can use [`type_annotation`]
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # fn main() -> serde_dhall::Result<()> {
+ /// use serde::Serialize;
+ /// use serde_dhall::{serialize, StaticType};
+ ///
+ /// #[derive(Serialize, StaticType)]
+ /// enum MyOption {
+ /// MyNone,
+ /// MySome(u64),
+ /// }
+ ///
+ /// let data = MyOption::MySome(0);
+ /// let string = serialize(&data)
+ /// .static_type_annotation()
+ /// .to_string()?;
+ /// // The resulting Dhall string depends on the type annotation; it could not have been
+ /// // printed without it.
+ /// assert_eq!(string, "< MyNone | MySome: Natural >.MySome 0".to_string());
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`type_annotation`]: struct.Serializer.html#method.type_annotation
+ /// [`StaticType`]: trait.StaticType.html
+ pub fn static_type_annotation(self) -> Serializer<'a, T, StaticAnnot> {
+ Serializer {
+ annot: StaticAnnot,
+ data: self.data,
+ }
+ }
+}
+
+impl<'a, T, A> Serializer<'a, T, A>
+where
+ A: TypeAnnot,
+{
+ /// Prints the chosen value with the options provided.
+ ///
+ /// If you enabled static annotations, `T` is required to implement [`StaticType`].
+ ///
+ /// Note that if you do not provide a type annotation, some values may not be convertible to
+ /// Dhall, like empty lists or enums.
+ ///
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn main() -> serde_dhall::Result<()> {
+ /// use serde_dhall::serialize;
+ ///
+ /// let string = serialize(&1i64).static_type_annotation().to_string()?;
+ /// assert_eq!(string, "+1".to_string());
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`StaticType`]: trait.StaticType.html
+ pub fn to_string(&self) -> Result<String>
+ where
+ T: ToDhall + HasAnnot<A>,
+ {
+ let val = self.data.to_dhall(T::get_annot(self.annot).as_ref())?;
+ Ok(val.to_string())
+ }
+}
+
+/// Serialize a value to a string of Dhall text.
+///
+/// This returns a [`Serializer`] object. Call the [`to_string`] method to get the serialized
+/// value, or use other [`Serializer`] methods to control the serialization process.
+///
+/// In order to process certain values (like unions or empty lists) correctly, it is necessary to
+/// add a type annotation (with [`static_type_annotation`] or [`type_annotation`]).
+///
+/// # Examples
+///
+/// ```rust
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde::Serialize;
+/// use serde_dhall::{serialize, StaticType};
+///
+/// #[derive(Serialize)]
+/// struct Point {
+/// x: u64,
+/// y: u64,
+/// }
+///
+///
+/// let data = Point { x: 0, y: 0 };
+/// let string = serialize(&data).to_string()?;
+/// assert_eq!(string, "{ x = 0, y = 0 }");
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```rust
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde::Serialize;
+/// use serde_dhall::{serialize, StaticType};
+///
+/// #[derive(Serialize, StaticType)]
+/// enum MyOption {
+/// MyNone,
+/// MySome(u64),
+/// }
+///
+/// let data = MyOption::MySome(0);
+/// let string = serialize(&data)
+/// .static_type_annotation()
+/// .to_string()?;
+/// // The resulting Dhall string depends on the type annotation; it could not have been
+/// // printed without it.
+/// assert_eq!(string, "< MyNone | MySome: Natural >.MySome 0".to_string());
+/// # Ok(())
+/// # }
+/// ```
+///
+/// [`Serializer`]: struct.Serializer.html
+/// [`type_annotation`]: struct.Serializer.html#method.type_annotation
+/// [`static_type_annotation`]: struct.Serializer.html#method.static_type_annotation
+/// [`to_string`]: struct.Serializer.html#method.to_string
+pub fn serialize<T>(data: &T) -> Serializer<'_, T, NoAnnot>
+where
+ T: ToDhall,
+{
+ Serializer {
+ data,
+ annot: NoAnnot,
+ }
+}
diff --git a/serde_dhall/src/serialize.rs b/serde_dhall/src/serialize.rs
new file mode 100644
index 0000000..286c04d
--- /dev/null
+++ b/serde_dhall/src/serialize.rs
@@ -0,0 +1,396 @@
+use serde::ser;
+use std::collections::BTreeMap;
+
+use dhall::syntax::NumKind;
+
+use crate::value::SimpleValue;
+use crate::{Error, ErrorKind, Result, SimpleType, Value};
+use SimpleValue::*;
+
+pub trait Sealed {}
+
+/// A data structure that can be serialized from a Dhall expression.
+///
+/// This is automatically implemented for any type that [serde] can serialize.
+/// In fact, this trait cannot be implemented manually. To implement it for your type,
+/// use serde's derive mechanism.
+///
+/// # Example
+///
+/// ```rust
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde::Serialize;
+///
+/// // Use serde's derive
+/// #[derive(Serialize)]
+/// struct Point {
+/// x: u64,
+/// y: u64,
+/// }
+///
+/// // Convert a Point to a Dhall string.
+/// let point = Point { x: 0, y: 0 };
+/// let point_str = serde_dhall::serialize(&point).to_string()?;
+/// assert_eq!(point_str, "{ x = 0, y = 0 }".to_string());
+/// # Ok(())
+/// # }
+/// ```
+///
+/// [serde]: https://serde.rs
+pub trait ToDhall: Sealed {
+ #[doc(hidden)]
+ fn to_dhall(&self, ty: Option<&SimpleType>) -> Result<Value>;
+}
+
+impl<T> Sealed for T where T: ser::Serialize {}
+
+impl<T> ToDhall for T
+where
+ T: ser::Serialize,
+{
+ fn to_dhall(&self, ty: Option<&SimpleType>) -> Result<Value> {
+ let sval: SimpleValue = self.serialize(Serializer)?;
+ sval.into_value(ty)
+ }
+}
+
+#[derive(Default, Clone, Copy)]
+struct Serializer;
+
+impl ser::Serializer for Serializer {
+ type Ok = SimpleValue;
+ type Error = Error;
+
+ type SerializeSeq = SeqSerializer;
+ type SerializeTuple = TupleSerializer;
+ type SerializeTupleStruct = ser::Impossible<Self::Ok, Self::Error>;
+ type SerializeTupleVariant = ser::Impossible<Self::Ok, Self::Error>;
+ type SerializeMap = MapSerializer;
+ type SerializeStruct = StructSerializer;
+ type SerializeStructVariant = ser::Impossible<Self::Ok, Self::Error>;
+
+ fn serialize_bool(self, v: bool) -> Result<Self::Ok> {
+ Ok(Num(NumKind::Bool(v)))
+ }
+
+ fn serialize_i8(self, v: i8) -> Result<Self::Ok> {
+ self.serialize_i64(i64::from(v))
+ }
+ fn serialize_i16(self, v: i16) -> Result<Self::Ok> {
+ self.serialize_i64(i64::from(v))
+ }
+ fn serialize_i32(self, v: i32) -> Result<Self::Ok> {
+ self.serialize_i64(i64::from(v))
+ }
+ fn serialize_i64(self, v: i64) -> Result<Self::Ok> {
+ Ok(Num(NumKind::Integer(v)))
+ }
+
+ fn serialize_u8(self, v: u8) -> Result<Self::Ok> {
+ self.serialize_u64(u64::from(v))
+ }
+ fn serialize_u16(self, v: u16) -> Result<Self::Ok> {
+ self.serialize_u64(u64::from(v))
+ }
+ fn serialize_u32(self, v: u32) -> Result<Self::Ok> {
+ self.serialize_u64(u64::from(v))
+ }
+ fn serialize_u64(self, v: u64) -> Result<Self::Ok> {
+ Ok(Num(NumKind::Natural(v)))
+ }
+
+ fn serialize_f32(self, v: f32) -> Result<Self::Ok> {
+ self.serialize_f64(f64::from(v))
+ }
+ fn serialize_f64(self, v: f64) -> Result<Self::Ok> {
+ Ok(Num(NumKind::Double(v.into())))
+ }
+
+ fn serialize_char(self, v: char) -> Result<Self::Ok> {
+ self.serialize_str(&v.to_string())
+ }
+ fn serialize_str(self, v: &str) -> Result<Self::Ok> {
+ Ok(Text(v.to_owned()))
+ }
+
+ fn serialize_bytes(self, _v: &[u8]) -> Result<Self::Ok> {
+ Err(ErrorKind::Serialize(
+ "Unsupported data for serialization: byte array".to_owned(),
+ )
+ .into())
+ }
+
+ fn serialize_none(self) -> Result<Self::Ok> {
+ Ok(Optional(None))
+ }
+ fn serialize_some<T>(self, v: &T) -> Result<Self::Ok>
+ where
+ T: ?Sized + ser::Serialize,
+ {
+ Ok(Optional(Some(Box::new(v.serialize(self)?))))
+ }
+
+ fn serialize_unit(self) -> Result<Self::Ok> {
+ Ok(Record(Default::default()))
+ }
+
+ fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok> {
+ self.serialize_unit()
+ }
+ fn serialize_newtype_struct<T>(
+ self,
+ _name: &'static str,
+ _value: &T,
+ ) -> Result<Self::Ok>
+ where
+ T: ?Sized + ser::Serialize,
+ {
+ Err(ErrorKind::Serialize(
+ "Unsupported data for serialization: newtype struct".to_owned(),
+ )
+ .into())
+ }
+ fn serialize_struct(
+ self,
+ _name: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeStruct> {
+ Ok(StructSerializer::default())
+ }
+
+ fn serialize_unit_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ variant: &'static str,
+ ) -> Result<Self::Ok> {
+ Ok(Union(variant.to_owned(), None))
+ }
+ fn serialize_newtype_variant<T>(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ variant: &'static str,
+ value: &T,
+ ) -> Result<Self::Ok>
+ where
+ T: ?Sized + ser::Serialize,
+ {
+ let value = value.serialize(self)?;
+ Ok(Union(variant.to_owned(), Some(Box::new(value))))
+ }
+ fn serialize_tuple_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeTupleVariant> {
+ Err(ErrorKind::Serialize(
+ "Unsupported data for serialization: tuple variant".to_owned(),
+ )
+ .into())
+ }
+ fn serialize_struct_variant(
+ self,
+ _name: &'static str,
+ _variant_index: u32,
+ _variant: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeStructVariant> {
+ Err(ErrorKind::Serialize(
+ "Unsupported data for serialization: struct variant".to_owned(),
+ )
+ .into())
+ }
+
+ fn serialize_tuple(self, _len: usize) -> Result<Self::SerializeTuple> {
+ Ok(TupleSerializer::default())
+ }
+ fn serialize_tuple_struct(
+ self,
+ _name: &'static str,
+ _len: usize,
+ ) -> Result<Self::SerializeTupleStruct> {
+ Err(ErrorKind::Serialize(
+ "Unsupported data for serialization: tuple struct".to_owned(),
+ )
+ .into())
+ }
+
+ fn serialize_seq(self, _len: Option<usize>) -> Result<Self::SerializeSeq> {
+ Ok(SeqSerializer::default())
+ }
+
+ fn serialize_map(self, _len: Option<usize>) -> Result<Self::SerializeMap> {
+ Ok(MapSerializer::default())
+ }
+}
+
+#[derive(Default)]
+struct SeqSerializer(Vec<SimpleValue>);
+
+impl ser::SerializeSeq for SeqSerializer {
+ type Ok = SimpleValue;
+ type Error = Error;
+
+ fn serialize_element<T>(&mut self, value: &T) -> Result<()>
+ where
+ T: ?Sized + ser::Serialize,
+ {
+ self.0.push(value.serialize(Serializer)?);
+ Ok(())
+ }
+
+ fn end(self) -> Result<Self::Ok> {
+ Ok(List(self.0))
+ }
+}
+
+#[derive(Default)]
+struct TupleSerializer(Vec<SimpleValue>);
+
+impl ser::SerializeTuple for TupleSerializer {
+ type Ok = SimpleValue;
+ type Error = Error;
+
+ fn serialize_element<T>(&mut self, value: &T) -> Result<()>
+ where
+ T: ?Sized + ser::Serialize,
+ {
+ self.0.push(value.serialize(Serializer)?);
+ Ok(())
+ }
+
+ fn end(self) -> Result<Self::Ok> {
+ Ok(Record(
+ self.0
+ .into_iter()
+ .enumerate()
+ .map(|(i, x)| (format!("_{}", i + 1), x))
+ .collect(),
+ ))
+ }
+}
+
+#[derive(Default)]
+struct MapSerializer {
+ map: BTreeMap<String, SimpleValue>,
+ key: Option<String>,
+ val: Option<SimpleValue>,
+}
+
+impl ser::SerializeMap for MapSerializer {
+ type Ok = SimpleValue;
+ type Error = Error;
+
+ fn serialize_key<T>(&mut self, key: &T) -> Result<()>
+ where
+ T: ?Sized + ser::Serialize,
+ {
+ let key = match key.serialize(Serializer)? {
+ Text(key) => key,
+ _ => return Err(<Error as ser::Error>::custom("not a string")),
+ };
+ if let Some(val) = self.val.take() {
+ self.map.insert(key, val);
+ } else {
+ self.key = Some(key);
+ }
+ Ok(())
+ }
+
+ fn serialize_value<T>(&mut self, val: &T) -> Result<()>
+ where
+ T: ?Sized + ser::Serialize,
+ {
+ let val: SimpleValue = val.serialize(Serializer)?;
+ if let Some(key) = self.key.take() {
+ self.map.insert(key, val);
+ } else {
+ self.val = Some(val);
+ }
+ Ok(())
+ }
+
+ fn end(self) -> Result<Self::Ok> {
+ Ok(Record(self.map))
+ }
+}
+
+#[derive(Default)]
+struct StructSerializer(BTreeMap<String, SimpleValue>);
+
+impl ser::SerializeStruct for StructSerializer {
+ type Ok = SimpleValue;
+ type Error = Error;
+
+ fn serialize_field<T>(&mut self, key: &'static str, val: &T) -> Result<()>
+ where
+ T: ?Sized + ser::Serialize,
+ {
+ let val: SimpleValue = val.serialize(Serializer)?;
+ self.0.insert(key.into(), val);
+ Ok(())
+ }
+
+ fn end(self) -> Result<Self::Ok> {
+ Ok(Record(self.0))
+ }
+}
+
+impl serde::ser::Serialize for SimpleValue {
+ fn serialize<S>(
+ &self,
+ serializer: S,
+ ) -> std::result::Result<S::Ok, S::Error>
+ where
+ S: serde::ser::Serializer,
+ {
+ use serde::ser::{SerializeMap, SerializeSeq};
+ use NumKind::*;
+ use SimpleValue::*;
+
+ match self {
+ Num(Bool(x)) => serializer.serialize_bool(*x),
+ Num(Natural(x)) => serializer.serialize_u64(*x),
+ Num(Integer(x)) => serializer.serialize_i64(*x),
+ Num(Double(x)) => serializer.serialize_f64((*x).into()),
+ Text(x) => serializer.serialize_str(x),
+ List(xs) => {
+ let mut seq = serializer.serialize_seq(Some(xs.len()))?;
+ for x in xs {
+ seq.serialize_element(x)?;
+ }
+ seq.end()
+ }
+ Optional(None) => serializer.serialize_none(),
+ Optional(Some(x)) => serializer.serialize_some(x),
+ Record(m) => {
+ let mut map = serializer.serialize_map(Some(m.len()))?;
+ for (k, v) in m {
+ map.serialize_entry(k, v)?;
+ }
+ map.end()
+ }
+ // serde's enum support is yet again really limited. We can't avoid a memleak here :(.
+ Union(field_name, None) => {
+ let field_name: Box<str> = field_name.clone().into();
+ serializer.serialize_unit_variant(
+ "SimpleValue",
+ 0,
+ Box::leak(field_name),
+ )
+ }
+ Union(field_name, Some(x)) => {
+ let field_name: Box<str> = field_name.clone().into();
+ serializer.serialize_newtype_variant(
+ "SimpleValue",
+ 0,
+ Box::leak(field_name),
+ x,
+ )
+ }
+ }
+ }
+}
diff --git a/serde_dhall/src/static_type.rs b/serde_dhall/src/static_type.rs
index 3c5da18..586cd4d 100644
--- a/serde_dhall/src/static_type.rs
+++ b/serde_dhall/src/static_type.rs
@@ -10,6 +10,10 @@ use crate::SimpleType;
/// corresponds to them. For example, `HashMap<String, u64>` could correspond to multiple different
/// Dhall types, e.g. `{ foo: Natural, bar: Natural }` and `{ baz: Natural }`.
///
+/// See also [the table of type correspondances].
+///
+/// [the table of type correspondances]: enum.SimpleType.html#type-correspondence
+///
/// # Example
///
/// ```rust
@@ -29,28 +33,6 @@ use crate::SimpleType;
/// # Ok(())
/// # }
/// ```
-///
-/// # Type correspondence
-///
-/// The following Dhall types correspond to the following Rust types:
-///
-/// Dhall | Rust
-/// -------|------
-/// `Bool` | `bool`
-/// `Natural` | `u64`, `u32`, ...
-/// `Integer` | `i64`, `i32`, ...
-/// `Double` | `f64`, `f32`, ...
-/// `Text` | `String`
-/// `List T` | `Vec<T>`
-/// `Optional T` | `Option<T>`
-/// `{ x: T, y: U }` | structs
-/// `{ _1: T, _2: U }` | `(T, U)`, structs
-/// `{ x: T, y: T }` | `HashMap<String, T>`, structs
-/// `< x: T \| y: U >` | enums
-/// `Prelude.Map.Type Text T` | `HashMap<String, T>`, structs
-/// `T -> U` | unsupported
-/// `Prelude.JSON.Type` | unsupported
-/// `Prelude.Map.Type T U` | unsupported
pub trait StaticType {
/// Return the Dhall type that represents this type.
///
@@ -103,6 +85,26 @@ derive_builtin!(i32, Integer);
derive_builtin!(f64, Double);
derive_builtin!(f32, Double);
derive_builtin!(String, Text);
+derive_builtin!(&str, Text);
+
+impl StaticType for () {
+ fn static_type() -> SimpleType {
+ SimpleType::Record(vec![].into_iter().collect())
+ }
+}
+
+impl<A> StaticType for (A,)
+where
+ A: StaticType,
+{
+ fn static_type() -> SimpleType {
+ SimpleType::Record(
+ vec![("_1".to_owned(), A::static_type())]
+ .into_iter()
+ .collect(),
+ )
+ }
+}
impl<A, B> StaticType for (A, B)
where
@@ -121,6 +123,46 @@ where
}
}
+impl<A, B, C> StaticType for (A, B, C)
+where
+ A: StaticType,
+ B: StaticType,
+ C: StaticType,
+{
+ fn static_type() -> SimpleType {
+ SimpleType::Record(
+ vec![
+ ("_1".to_owned(), A::static_type()),
+ ("_2".to_owned(), B::static_type()),
+ ("_3".to_owned(), C::static_type()),
+ ]
+ .into_iter()
+ .collect(),
+ )
+ }
+}
+
+impl<A, B, C, D> StaticType for (A, B, C, D)
+where
+ A: StaticType,
+ B: StaticType,
+ C: StaticType,
+ D: StaticType,
+{
+ fn static_type() -> SimpleType {
+ SimpleType::Record(
+ vec![
+ ("_1".to_owned(), A::static_type()),
+ ("_2".to_owned(), B::static_type()),
+ ("_3".to_owned(), C::static_type()),
+ ("_4".to_owned(), D::static_type()),
+ ]
+ .into_iter()
+ .collect(),
+ )
+ }
+}
+
impl<T, E> StaticType for std::result::Result<T, E>
where
T: StaticType,
diff --git a/serde_dhall/src/value.rs b/serde_dhall/src/value.rs
index d7116d9..b8ad199 100644
--- a/serde_dhall/src/value.rs
+++ b/serde_dhall/src/value.rs
@@ -6,7 +6,7 @@ use dhall::semantics::{Hir, HirKind, Nir, NirKind};
pub use dhall::syntax::NumKind;
use dhall::syntax::{Expr, ExprKind, Span};
-use crate::{Error, ErrorKind, FromDhall, Result, Sealed};
+use crate::{Error, ErrorKind, FromDhall, Result, ToDhall};
#[doc(hidden)]
/// An arbitrary Dhall value.
@@ -124,6 +124,28 @@ pub enum SimpleValue {
/// [`StaticType`]: trait.StaticType.html
/// [`Deserializer::static_type_annotation`]: options/struct.Deserializer.html#method.static_type_annotation
///
+/// # Type correspondence
+///
+/// The following Dhall types correspond to the following Rust types:
+///
+/// Dhall | Rust
+/// -------|------
+/// `Bool` | `bool`
+/// `Natural` | `u64`, `u32`, ...
+/// `Integer` | `i64`, `i32`, ...
+/// `Double` | `f64`, `f32`, ...
+/// `Text` | `String`
+/// `List T` | `Vec<T>`
+/// `Optional T` | `Option<T>`
+/// `{ x: T, y: U }` | structs
+/// `{ _1: T, _2: U }` | `(T, U)`, structs
+/// `{ x: T, y: T }` | `HashMap<String, T>`, structs
+/// `< x: T \| y: U >` | enums
+/// `Prelude.Map.Type Text T` | `HashMap<String, T>`, structs
+/// `T -> U` | unsupported
+/// `Prelude.JSON.Type` | unsupported
+/// `Prelude.Map.Type T U` | unsupported
+///
/// # Examples
///
/// ```rust
@@ -159,7 +181,6 @@ pub enum SimpleValue {
/// # Ok(())
/// # }
/// ```
-///
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SimpleType {
/// Corresponds to the Dhall type `Bool`
@@ -292,6 +313,110 @@ impl SimpleValue {
_ => return None,
})
}
+
+ // Converts this to `Hir`, using the optional type annotation. Without the type, things like
+ // empty lists and unions will fail to convert.
+ fn to_hir(&self, ty: Option<&SimpleType>) -> Result<Hir> {
+ use SimpleType as T;
+ use SimpleValue as V;
+ let hir = |k| Hir::new(HirKind::Expr(k), Span::Artificial);
+ let type_error = || {
+ Error(ErrorKind::Serialize(format!(
+ "expected a value of type {}, found {:?}",
+ ty.unwrap().to_hir().to_expr(Default::default()),
+ self
+ )))
+ };
+ let type_missing = || {
+ Error(ErrorKind::Serialize(format!(
+ "cannot serialize value without a type annotation: {:?}",
+ self
+ )))
+ };
+ let kind = match (self, ty) {
+ (V::Num(num @ NumKind::Bool(_)), Some(T::Bool))
+ | (V::Num(num @ NumKind::Natural(_)), Some(T::Natural))
+ | (V::Num(num @ NumKind::Integer(_)), Some(T::Integer))
+ | (V::Num(num @ NumKind::Double(_)), Some(T::Double))
+ | (V::Num(num), None) => ExprKind::Num(num.clone()),
+ (V::Text(v), Some(T::Text)) | (V::Text(v), None) => {
+ ExprKind::TextLit(v.clone().into())
+ }
+
+ (V::Optional(None), None) => return Err(type_missing()),
+ (V::Optional(None), Some(T::Optional(t))) => {
+ ExprKind::Op(OpKind::App(
+ hir(ExprKind::Builtin(Builtin::OptionalNone)),
+ t.to_hir(),
+ ))
+ }
+ (V::Optional(Some(v)), None) => ExprKind::SomeLit(v.to_hir(None)?),
+ (V::Optional(Some(v)), Some(T::Optional(t))) => {
+ ExprKind::SomeLit(v.to_hir(Some(t))?)
+ }
+
+ (V::List(v), None) if v.is_empty() => return Err(type_missing()),
+ (V::List(v), Some(T::List(t))) if v.is_empty() => {
+ ExprKind::EmptyListLit(hir(ExprKind::Op(OpKind::App(
+ hir(ExprKind::Builtin(Builtin::List)),
+ t.to_hir(),
+ ))))
+ }
+ (V::List(v), None) => ExprKind::NEListLit(
+ v.iter().map(|v| v.to_hir(None)).collect::<Result<_>>()?,
+ ),
+ (V::List(v), Some(T::List(t))) => ExprKind::NEListLit(
+ v.iter().map(|v| v.to_hir(Some(t))).collect::<Result<_>>()?,
+ ),
+
+ (V::Record(v), None) => ExprKind::RecordLit(
+ v.iter()
+ .map(|(k, v)| Ok((k.clone().into(), v.to_hir(None)?)))
+ .collect::<Result<_>>()?,
+ ),
+ (V::Record(v), Some(T::Record(t))) => ExprKind::RecordLit(
+ v.iter()
+ .map(|(k, v)| match t.get(k) {
+ Some(t) => Ok((k.clone().into(), v.to_hir(Some(t))?)),
+ None => Err(type_error()),
+ })
+ .collect::<Result<_>>()?,
+ ),
+
+ (V::Union(..), None) => return Err(type_missing()),
+ (V::Union(variant, Some(v)), Some(T::Union(t))) => {
+ match t.get(variant) {
+ Some(Some(variant_t)) => ExprKind::Op(OpKind::App(
+ hir(ExprKind::Op(OpKind::Field(
+ ty.unwrap().to_hir(),
+ variant.clone().into(),
+ ))),
+ v.to_hir(Some(variant_t))?,
+ )),
+ _ => return Err(type_error()),
+ }
+ }
+ (V::Union(variant, None), Some(T::Union(t))) => {
+ match t.get(variant) {
+ Some(None) => ExprKind::Op(OpKind::Field(
+ ty.unwrap().to_hir(),
+ variant.clone().into(),
+ )),
+ _ => return Err(type_error()),
+ }
+ }
+
+ (_, Some(_)) => return Err(type_error()),
+ };
+ Ok(hir(kind))
+ }
+ pub(crate) fn into_value(self, ty: Option<&SimpleType>) -> Result<Value> {
+ Ok(Value {
+ hir: self.to_hir(ty)?,
+ as_simple_val: Some(self),
+ as_simple_ty: None,
+ })
+ }
}
impl SimpleType {
@@ -376,8 +501,9 @@ impl SimpleType {
}
}
-impl Sealed for Value {}
-impl Sealed for SimpleType {}
+impl crate::deserialize::Sealed for Value {}
+impl crate::deserialize::Sealed for SimpleType {}
+impl crate::serialize::Sealed for Value {}
impl FromDhall for Value {
fn from_dhall(v: &Value) -> Result<Self> {
@@ -394,6 +520,11 @@ impl FromDhall for SimpleType {
})
}
}
+impl ToDhall for Value {
+ fn to_dhall(&self, _ty: Option<&SimpleType>) -> Result<Value> {
+ Ok(self.clone())
+ }
+}
impl Eq for Value {}
impl PartialEq for Value {
diff --git a/serde_dhall/tests/de.rs b/serde_dhall/tests/de.rs
deleted file mode 100644
index 3abaad2..0000000
--- a/serde_dhall/tests/de.rs
+++ /dev/null
@@ -1,150 +0,0 @@
-use serde::Deserialize;
-use serde_dhall::{from_str, FromDhall, NumKind, SimpleValue, StaticType};
-
-#[test]
-fn test_de_typed() {
- fn parse<T: FromDhall + StaticType>(s: &str) -> T {
- from_str(s).static_type_annotation().parse().unwrap()
- }
-
- assert_eq!(parse::<bool>("True"), true);
-
- assert_eq!(parse::<u64>("1"), 1);
- assert_eq!(parse::<u32>("1"), 1);
- assert_eq!(parse::<usize>("1"), 1);
-
- assert_eq!(parse::<i64>("+1"), 1);
- assert_eq!(parse::<i32>("+1"), 1);
- assert_eq!(parse::<isize>("+1"), 1);
-
- assert_eq!(parse::<f64>("1.0"), 1.0);
- assert_eq!(parse::<f32>("1.0"), 1.0);
-
- assert_eq!(parse::<String>(r#""foo""#), "foo".to_owned());
- assert_eq!(parse::<Vec<u64>>("[] : List Natural"), <Vec<u64>>::new());
- assert_eq!(parse::<Vec<u64>>("[1, 2]"), vec![1, 2]);
- assert_eq!(parse::<Option<u64>>("None Natural"), None);
- assert_eq!(parse::<Option<u64>>("Some 1"), Some(1));
-
- assert_eq!(
- parse::<(u64, String)>(r#"{ _1 = 1, _2 = "foo" }"#),
- (1, "foo".to_owned())
- );
-
- #[derive(Debug, PartialEq, Eq, Deserialize, StaticType)]
- struct Foo {
- x: u64,
- y: i64,
- }
- assert_eq!(parse::<Foo>("{ x = 1, y = -2 }"), Foo { x: 1, y: -2 });
-
- #[derive(Debug, PartialEq, Eq, Deserialize, StaticType)]
- enum Bar {
- X(u64),
- Y(i64),
- }
- assert_eq!(parse::<Bar>("< X: Natural | Y: Integer >.X 1"), Bar::X(1));
-
- #[derive(Debug, PartialEq, Eq, Deserialize, StaticType)]
- enum Baz {
- X,
- Y(i64),
- }
- assert_eq!(parse::<Baz>("< X | Y: Integer >.X"), Baz::X);
-
- assert!(from_str("< X | Y: Integer >.Y")
- .static_type_annotation()
- .parse::<Baz>()
- .is_err());
-}
-
-#[test]
-fn test_de_untyped() {
- use std::collections::BTreeMap;
- use std::collections::HashMap;
-
- fn parse<T: FromDhall>(s: &str) -> T {
- from_str(s).parse().unwrap()
- }
-
- // Test tuples on record of wrong type
- assert_eq!(
- parse::<(u64, String, i64)>(r#"{ y = "foo", x = 1, z = +42 }"#),
- (1, "foo".to_owned(), 42)
- );
-
- let mut expected_map = HashMap::new();
- expected_map.insert("x".to_string(), 1);
- expected_map.insert("y".to_string(), 2);
- assert_eq!(
- parse::<HashMap<String, u64>>("{ x = 1, y = 2 }"),
- expected_map
- );
- assert_eq!(
- parse::<HashMap<String, u64>>("toMap { x = 1, y = 2 }"),
- expected_map
- );
-
- let mut expected_map = HashMap::new();
- expected_map.insert("if".to_string(), 1);
- expected_map.insert("FOO_BAR".to_string(), 2);
- expected_map.insert("baz-kux".to_string(), 3);
- assert_eq!(
- parse::<HashMap<String, u64>>("{ `if` = 1, FOO_BAR = 2, baz-kux = 3 }"),
- expected_map
- );
-
- let mut expected_map = BTreeMap::new();
- expected_map.insert("x".to_string(), 1);
- expected_map.insert("y".to_string(), 2);
- assert_eq!(
- parse::<BTreeMap<String, u64>>("{ x = 1, y = 2 }"),
- expected_map
- );
-
- #[derive(Debug, PartialEq, Eq, Deserialize)]
- struct Foo {
- x: u64,
- y: Option<u64>,
- }
- // Omit optional field
- assert_eq!(parse::<Foo>("{ x = 1 }"), Foo { x: 1, y: None });
-
- // https://github.com/Nadrieril/dhall-rust/issues/155
- assert!(from_str("List/length [True, 42]").parse::<bool>().is_err());
-}
-
-#[test]
-fn test_de_simplevalue() {
- let bool_true = SimpleValue::Num(NumKind::Bool(true));
- // https://github.com/Nadrieril/dhall-rust/issues/184
- assert_eq!(
- from_str("[ True ]").parse::<Vec<SimpleValue>>().unwrap(),
- vec![bool_true.clone()]
- );
-
- assert_eq!(
- from_str("< Foo >.Foo").parse::<SimpleValue>().unwrap(),
- SimpleValue::Union("Foo".into(), None)
- );
- assert_eq!(
- from_str("< Foo: Bool >.Foo True")
- .parse::<SimpleValue>()
- .unwrap(),
- SimpleValue::Union("Foo".into(), Some(Box::new(bool_true.clone())))
- );
-
- #[derive(Debug, PartialEq, Eq, Deserialize)]
- struct Foo {
- foo: SimpleValue,
- }
- assert_eq!(
- from_str("{ foo = True }").parse::<Foo>().unwrap(),
- Foo {
- foo: bool_true.clone()
- },
- );
-}
-
-// TODO: test various builder configurations
-// In particular test cloning and reusing builder
diff --git a/serde_dhall/tests/serde.rs b/serde_dhall/tests/serde.rs
new file mode 100644
index 0000000..4c184e7
--- /dev/null
+++ b/serde_dhall/tests/serde.rs
@@ -0,0 +1,210 @@
+mod serde {
+ use serde::{Deserialize, Serialize};
+ use serde_dhall::{from_str, serialize, FromDhall, StaticType, ToDhall};
+
+ fn assert_de<T>(s: &str, x: T)
+ where
+ T: FromDhall + StaticType + PartialEq + std::fmt::Debug,
+ {
+ assert_eq!(
+ from_str(s)
+ .static_type_annotation()
+ .parse::<T>()
+ .map_err(|e| e.to_string()),
+ Ok(x)
+ );
+ }
+ fn assert_ser<T>(s: &str, x: T)
+ where
+ T: ToDhall + StaticType + PartialEq + std::fmt::Debug,
+ {
+ assert_eq!(
+ serialize(&x)
+ .static_type_annotation()
+ .to_string()
+ .map_err(|e| e.to_string()),
+ Ok(s.to_string())
+ );
+ }
+ fn assert_serde<T>(s: &str, x: T)
+ where
+ T: ToDhall
+ + FromDhall
+ + StaticType
+ + PartialEq
+ + std::fmt::Debug
+ + Clone,
+ {
+ assert_de(s, x.clone());
+ assert_ser(s, x);
+ }
+
+ #[test]
+ fn numbers() {
+ assert_serde("True", true);
+
+ assert_serde("1", 1u64);
+ assert_serde("1", 1u32);
+ assert_serde("1", 1usize);
+
+ assert_serde("+1", 1i64);
+ assert_serde("+1", 1i32);
+ assert_serde("+1", 1isize);
+
+ assert_serde("1.0", 1.0f64);
+ assert_serde("1.0", 1.0f32);
+ }
+
+ #[test]
+ fn text() {
+ assert_serde(r#""foo""#, "foo".to_owned());
+ assert_ser(r#""foo""#, "foo");
+ }
+
+ #[test]
+ fn list() {
+ assert_serde("[] : List Natural", <Vec<u64>>::new());
+ assert_serde("[] : List Text", <Vec<String>>::new());
+ assert_serde(
+ r#"["foo", "bar"]"#,
+ vec!["foo".to_owned(), "bar".to_owned()],
+ );
+ assert_ser(r#"["foo", "bar"]"#, vec!["foo", "bar"]);
+ assert_serde::<Vec<u64>>("[1, 2]", vec![1, 2]);
+ }
+
+ #[test]
+ fn optional() {
+ assert_serde("None Natural", None::<u64>);
+ assert_serde("None Text", None::<String>);
+ assert_serde("Some 1", Some(1u64));
+ assert_eq!(
+ serialize(&None::<u64>).to_string().map_err(|e| e.to_string()),
+ Err("cannot serialize value without a type annotation: Optional(None)".to_string())
+ );
+ }
+
+ #[test]
+ fn tuple() {
+ assert_serde::<()>(r#"{=}"#, ());
+ assert_serde::<(u64, String)>(
+ r#"{ _1 = 1, _2 = "foo" }"#,
+ (1, "foo".to_owned()),
+ );
+ assert_serde::<(u64, u64, u64, u64)>(
+ r#"{ _1 = 1, _2 = 2, _3 = 3, _4 = 4 }"#,
+ (1, 2, 3, 4),
+ );
+ }
+
+ #[test]
+ fn structs() {
+ // #[derive(
+ // Debug, Clone, PartialEq, Eq, Deserialize, Serialize, StaticType,
+ // )]
+ // struct Foo;
+ // assert_serde::<Foo>("{=}", Foo);
+
+ // #[derive(
+ // Debug, Clone, PartialEq, Eq, Deserialize, Serialize, StaticType,
+ // )]
+ // struct Bar(u64);
+ // assert_serde::<Bar>("{ _1 = 1 }", Bar (1));
+
+ #[derive(
+ Debug, Clone, PartialEq, Eq, Deserialize, Serialize, StaticType,
+ )]
+ struct Baz {
+ x: u64,
+ y: i64,
+ }
+ assert_serde::<Baz>("{ x = 1, y = -2 }", Baz { x: 1, y: -2 });
+ }
+
+ #[test]
+ fn enums() {
+ #[derive(
+ Debug, Clone, PartialEq, Eq, Deserialize, Serialize, StaticType,
+ )]
+ enum Foo {
+ X(u64),
+ Y(i64),
+ }
+ assert_serde::<Foo>("< X: Natural | Y: Integer >.X 1", Foo::X(1));
+
+ #[derive(
+ Debug, Clone, PartialEq, Eq, Deserialize, Serialize, StaticType,
+ )]
+ enum Bar {
+ X,
+ Y(i64),
+ }
+ assert_serde::<Bar>("< X | Y: Integer >.X", Bar::X);
+
+ assert!(from_str("< X | Y: Integer >.Y")
+ .static_type_annotation()
+ .parse::<Bar>()
+ .is_err());
+ }
+
+ #[test]
+ fn test_de_untyped() {
+ use std::collections::BTreeMap;
+ use std::collections::HashMap;
+
+ fn parse<T: FromDhall>(s: &str) -> T {
+ from_str(s).parse().unwrap()
+ }
+
+ // Test tuples on record of wrong type. Fields are just taken in alphabetic order.
+ assert_eq!(
+ parse::<(u64, String, i64)>(r#"{ y = "foo", x = 1, z = +42 }"#),
+ (1, "foo".to_owned(), 42)
+ );
+
+ let mut expected_map = HashMap::new();
+ expected_map.insert("x".to_string(), 1);
+ expected_map.insert("y".to_string(), 2);
+ assert_eq!(
+ parse::<HashMap<String, u64>>("{ x = 1, y = 2 }"),
+ expected_map
+ );
+ assert_eq!(
+ parse::<HashMap<String, u64>>("toMap { x = 1, y = 2 }"),
+ expected_map
+ );
+
+ let mut expected_map = HashMap::new();
+ expected_map.insert("if".to_string(), 1);
+ expected_map.insert("FOO_BAR".to_string(), 2);
+ expected_map.insert("baz-kux".to_string(), 3);
+ assert_eq!(
+ parse::<HashMap<String, u64>>(
+ "{ `if` = 1, FOO_BAR = 2, baz-kux = 3 }"
+ ),
+ expected_map
+ );
+
+ let mut expected_map = BTreeMap::new();
+ expected_map.insert("x".to_string(), 1);
+ expected_map.insert("y".to_string(), 2);
+ assert_eq!(
+ parse::<BTreeMap<String, u64>>("{ x = 1, y = 2 }"),
+ expected_map
+ );
+
+ #[derive(Debug, PartialEq, Eq, Deserialize)]
+ struct Foo {
+ x: u64,
+ y: Option<u64>,
+ }
+ // Omit optional field
+ assert_eq!(parse::<Foo>("{ x = 1 }"), Foo { x: 1, y: None });
+
+ // https://github.com/Nadrieril/dhall-rust/issues/155
+ assert!(from_str("List/length [True, 42]").parse::<bool>().is_err());
+ }
+
+ // TODO: test various builder configurations
+ // In particular test cloning and reusing builder
+}
diff --git a/serde_dhall/tests/simple_value.rs b/serde_dhall/tests/simple_value.rs
new file mode 100644
index 0000000..3bd9d64
--- /dev/null
+++ b/serde_dhall/tests/simple_value.rs
@@ -0,0 +1,58 @@
+mod simple_value {
+ use serde::{Deserialize, Serialize};
+ use serde_dhall::{
+ from_str, serialize, FromDhall, NumKind, SimpleValue, ToDhall,
+ };
+
+ fn assert_de<T>(s: &str, x: T)
+ where
+ T: FromDhall + PartialEq + std::fmt::Debug,
+ {
+ assert_eq!(from_str(s).parse::<T>().map_err(|e| e.to_string()), Ok(x));
+ }
+ fn assert_ser<T>(s: &str, x: T)
+ where
+ T: ToDhall + PartialEq + std::fmt::Debug,
+ {
+ assert_eq!(
+ serialize(&x).to_string().map_err(|e| e.to_string()),
+ Ok(s.to_string())
+ );
+ }
+ fn assert_serde<T>(s: &str, x: T)
+ where
+ T: ToDhall + FromDhall + PartialEq + std::fmt::Debug + Clone,
+ {
+ assert_de(s, x.clone());
+ assert_ser(s, x);
+ }
+
+ #[test]
+ fn test_serde() {
+ let bool_true = SimpleValue::Num(NumKind::Bool(true));
+ // https://github.com/Nadrieril/dhall-rust/issues/184
+ assert_serde("[True]", vec![bool_true.clone()]);
+
+ assert_de("< Foo >.Foo", SimpleValue::Union("Foo".into(), None));
+ assert_de(
+ "< Foo: Bool >.Foo True",
+ SimpleValue::Union("Foo".into(), Some(Box::new(bool_true.clone()))),
+ );
+
+ assert_eq!(
+ serialize(&SimpleValue::Union("Foo".into(), None)).to_string().map_err(|e| e.to_string()),
+ Err("cannot serialize value without a type annotation: Union(\"Foo\", None)".to_string())
+ );
+
+ #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+ struct Foo {
+ foo: SimpleValue,
+ }
+ assert_serde(
+ "{ foo = True }",
+ Foo {
+ foo: bool_true.clone(),
+ },
+ );
+ }
+}