summaryrefslogtreecommitdiff
path: root/serde_dhall
diff options
context:
space:
mode:
Diffstat (limited to 'serde_dhall')
-rw-r--r--serde_dhall/Cargo.toml10
-rw-r--r--serde_dhall/src/deserialize.rs143
-rw-r--r--serde_dhall/src/error.rs40
-rw-r--r--serde_dhall/src/lib.rs316
-rw-r--r--serde_dhall/src/options.rs368
-rw-r--r--serde_dhall/src/serde.rs144
-rw-r--r--serde_dhall/src/static_type.rs119
-rw-r--r--serde_dhall/src/value.rs287
-rw-r--r--serde_dhall/tests/de.rs28
-rw-r--r--serde_dhall/tests/traits.rs6
-rw-r--r--serde_dhall/tests/version_numbers.rs17
11 files changed, 1061 insertions, 417 deletions
diff --git a/serde_dhall/Cargo.toml b/serde_dhall/Cargo.toml
index b0030e9..6af883a 100644
--- a/serde_dhall/Cargo.toml
+++ b/serde_dhall/Cargo.toml
@@ -11,5 +11,11 @@ edition = "2018"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
-dhall = { version = "0.4.0", path = "../dhall" }
-dhall_proc_macros = { version = "0.4.0", path = "../dhall_proc_macros" }
+dhall = { version = "=0.4.0", path = "../dhall" }
+dhall_proc_macros = { version = "=0.4.0", path = "../dhall_proc_macros" }
+doc-comment = "0.3"
+reqwest = { version = "0.10", features = ["blocking"] }
+url = "2.1"
+
+[dev-dependencies]
+version-sync = "0.8"
diff --git a/serde_dhall/src/deserialize.rs b/serde_dhall/src/deserialize.rs
new file mode 100644
index 0000000..92be2e9
--- /dev/null
+++ b/serde_dhall/src/deserialize.rs
@@ -0,0 +1,143 @@
+use serde::de::value::{
+ MapAccessDeserializer, MapDeserializer, SeqDeserializer,
+};
+use std::borrow::Cow;
+
+use dhall::syntax::NumKind;
+
+use crate::value::SimpleValue;
+use crate::{Error, ErrorKind, Result, Value};
+
+pub trait Sealed {}
+
+/// A data structure that can be deserialized from a Dhall expression.
+///
+/// This is automatically implemented for any type that [serde] can deserialize.
+/// 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::Deserialize;
+///
+/// // Use serde's derive
+/// #[derive(Deserialize)]
+/// struct Point {
+/// x: u64,
+/// y: u64,
+/// }
+///
+/// // Convert a Dhall string to a Point.
+/// let point: Point = serde_dhall::from_str("{ x = 1, y = 1 + 1 }").parse()?;
+/// # Ok(())
+/// # }
+/// ```
+///
+/// [serde]: https://serde.rs
+pub trait FromDhall: Sealed + Sized {
+ #[doc(hidden)]
+ fn from_dhall(v: &Value) -> Result<Self>;
+}
+
+impl<T> Sealed for T where T: serde::de::DeserializeOwned {}
+
+struct Deserializer<'a>(Cow<'a, SimpleValue>);
+
+impl<T> FromDhall for T
+where
+ T: serde::de::DeserializeOwned,
+{
+ fn from_dhall(v: &Value) -> Result<Self> {
+ let sval = v.to_simple_value().ok_or_else(|| {
+ Error(ErrorKind::Deserialize(format!(
+ "this cannot be deserialized into the serde data model: {}",
+ v
+ )))
+ })?;
+ T::deserialize(Deserializer(Cow::Owned(sval)))
+ }
+}
+
+impl<'de: 'a, 'a> serde::de::IntoDeserializer<'de, Error> for Deserializer<'a> {
+ type Deserializer = Deserializer<'a>;
+ fn into_deserializer(self) -> Self::Deserializer {
+ self
+ }
+}
+
+impl<'de: 'a, 'a> serde::Deserializer<'de> for Deserializer<'a> {
+ type Error = Error;
+
+ fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ use std::convert::TryInto;
+ use NumKind::*;
+ use SimpleValue::*;
+
+ let val = |x| Deserializer(Cow::Borrowed(x));
+ match self.0.as_ref() {
+ Num(Bool(x)) => visitor.visit_bool(*x),
+ Num(Natural(x)) => {
+ if let Ok(x64) = (*x).try_into() {
+ visitor.visit_u64(x64)
+ } else if let Ok(x32) = (*x).try_into() {
+ visitor.visit_u32(x32)
+ } else {
+ unimplemented!()
+ }
+ }
+ Num(Integer(x)) => {
+ if let Ok(x64) = (*x).try_into() {
+ visitor.visit_i64(x64)
+ } else if let Ok(x32) = (*x).try_into() {
+ visitor.visit_i32(x32)
+ } else {
+ unimplemented!()
+ }
+ }
+ Num(Double(x)) => visitor.visit_f64((*x).into()),
+ Text(x) => visitor.visit_str(x),
+ List(xs) => {
+ visitor.visit_seq(SeqDeserializer::new(xs.iter().map(val)))
+ }
+ Optional(None) => visitor.visit_none(),
+ Optional(Some(x)) => visitor.visit_some(val(x)),
+ Record(m) => visitor.visit_map(MapDeserializer::new(
+ m.iter().map(|(k, v)| (k.as_ref(), val(v))),
+ )),
+ Union(field_name, Some(x)) => visitor.visit_enum(
+ MapAccessDeserializer::new(MapDeserializer::new(
+ Some((field_name.as_str(), val(x))).into_iter(),
+ )),
+ ),
+ Union(field_name, None) => visitor.visit_enum(
+ MapAccessDeserializer::new(MapDeserializer::new(
+ Some((field_name.as_str(), ())).into_iter(),
+ )),
+ ),
+ }
+ }
+
+ fn deserialize_tuple<V>(self, _: usize, visitor: V) -> Result<V::Value>
+ where
+ V: serde::de::Visitor<'de>,
+ {
+ let val = |x| Deserializer(Cow::Borrowed(x));
+ match self.0.as_ref() {
+ // Blindly takes keys in sorted order.
+ SimpleValue::Record(m) => visitor
+ .visit_seq(SeqDeserializer::new(m.iter().map(|(_, v)| val(v)))),
+ _ => 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
+ tuple_struct map struct enum identifier ignored_any
+ }
+}
diff --git a/serde_dhall/src/error.rs b/serde_dhall/src/error.rs
new file mode 100644
index 0000000..896e8b9
--- /dev/null
+++ b/serde_dhall/src/error.rs
@@ -0,0 +1,40 @@
+use dhall::error::Error as DhallError;
+
+/// Alias for a `Result` with the error type `serde_dhall::Error`.
+pub type Result<T> = std::result::Result<T, Error>;
+
+/// Errors that can occur when deserializing Dhall data.
+#[derive(Debug)]
+pub struct Error(pub(crate) ErrorKind);
+
+#[derive(Debug)]
+pub(crate) enum ErrorKind {
+ Dhall(DhallError),
+ Deserialize(String),
+}
+
+impl From<ErrorKind> for Error {
+ fn from(kind: ErrorKind) -> Error {
+ Error(kind)
+ }
+}
+
+impl std::fmt::Display for Error {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ match &self.0 {
+ ErrorKind::Dhall(err) => write!(f, "{}", err),
+ ErrorKind::Deserialize(err) => write!(f, "{}", err),
+ }
+ }
+}
+
+impl std::error::Error for Error {}
+
+impl serde::de::Error for Error {
+ fn custom<T>(msg: T) -> Self
+ where
+ T: std::fmt::Display,
+ {
+ ErrorKind::Deserialize(msg.to_string()).into()
+ }
+}
diff --git a/serde_dhall/src/lib.rs b/serde_dhall/src/lib.rs
index 0a53420..c478b2a 100644
--- a/serde_dhall/src/lib.rs
+++ b/serde_dhall/src/lib.rs
@@ -1,4 +1,5 @@
#![doc(html_root_url = "https://docs.rs/serde_dhall/0.4.0")]
+#![warn(missing_docs, missing_doc_code_examples)]
//! [Dhall][dhall] is a programmable configuration language that provides a non-repetitive
//! alternative to JSON and YAML.
//!
@@ -16,20 +17,20 @@
//!
//! # Basic usage
//!
-//! The main entrypoint of this library is the [`from_str`][from_str] function. It reads a string
+//! 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.
//!
//! This could mean a common Rust type like `HashMap`:
//!
//! ```rust
-//! # fn main() -> serde_dhall::de::Result<()> {
+//! # fn main() -> serde_dhall::Result<()> {
//! use std::collections::HashMap;
//!
//! // Some Dhall data
//! let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }";
//!
//! // Deserialize it to a Rust type.
-//! let deserialized_map: HashMap<String, usize> = serde_dhall::from_str(data)?;
+//! let deserialized_map: HashMap<String, usize> = serde_dhall::from_str(data).parse()?;
//!
//! let mut expected_map = HashMap::new();
//! expected_map.insert("x".to_string(), 1);
@@ -43,10 +44,10 @@
//! or a custom datatype, using serde's `derive` mechanism:
//!
//! ```rust
-//! # fn main() -> serde_dhall::de::Result<()> {
+//! # fn main() -> serde_dhall::Result<()> {
//! use serde::Deserialize;
//!
-//! #[derive(Debug, Deserialize)]
+//! #[derive(Deserialize)]
//! struct Point {
//! x: u64,
//! y: u64,
@@ -56,7 +57,7 @@
//! let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }";
//!
//! // Convert the Dhall string to a Point.
-//! let point: Point = serde_dhall::from_str(data)?;
+//! let point: Point = serde_dhall::from_str(data).parse()?;
//! assert_eq!(point.x, 1);
//! assert_eq!(point.y, 2);
//!
@@ -64,67 +65,82 @@
//! # }
//! ```
//!
-//! # 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
-//! `T -> U` | unsupported
-//! `Prelude.JSON.Type` | unsupported
-//! `Prelude.Map.Type T U` | unsupported
-//!
-//!
//! # 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][from_str].
+//! 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).
//!
-//! [serde_json::from_str]: https://docs.serde.rs/serde_json/de/fn.from_str.html
-//! [serde_yaml::from_str]: https://docs.serde.rs/serde_yaml/fn.from_str.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
//!
//!
//! # Additional Dhall typechecking
//!
//! When deserializing, normal type checking is done to ensure that the returned value is a valid
-//! Dhall value, and that it can be deserialized into the required Rust type. However types are
-//! first-class in Dhall, and this library allows you to additionally check that some input data
+//! Dhall value. However types are
+//! first-class in Dhall, and this library allows you to additionally check that the input data
//! 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: you can provide the type as Dhall text or you
-//! can let Rust infer it for you.
+//! 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.
//!
-//! To provide a type written in Dhall, first parse it into a [`serde_dhall::Value`][Value], then
-//! pass it to [`from_str_check_type`][from_str_check_type].
+//! To let Rust infer the appropriate Dhall type, use the [StaticType](trait.StaticType.html)
+//! trait.
//!
//! ```rust
-//! # fn main() -> serde_dhall::de::Result<()> {
-//! use serde_dhall::Value;
+//! # fn main() -> serde_dhall::Result<()> {
+//! use serde::Deserialize;
+//! use serde_dhall::StaticType;
+//!
+//! #[derive(Deserialize, StaticType)]
+//! struct Point {
+//! x: u64,
+//! y: u64,
+//! }
+//!
+//! // Some Dhall data
+//! let data = "{ x = 1, y = 1 + 1 }";
+//!
+//! // Convert the Dhall string to a Point.
+//! let point = serde_dhall::from_str(data)
+//! .static_type_annotation()
+//! .parse::<Point>()?;
+//! assert_eq!(point.x, 1);
+//! assert_eq!(point.y, 2);
+//!
+//! // Invalid data fails the type validation
+//! let invalid_data = "{ x = 1, z = 0.3 }";
+//! assert!(
+//! serde_dhall::from_str(invalid_data)
+//! .static_type_annotation()
+//! .parse::<Point>()
+//! .is_err()
+//! );
+//! # 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.
+//!
+//! ```rust
+//! # fn main() -> serde_dhall::Result<()> {
+//! use serde_dhall::SimpleType;
//! use std::collections::HashMap;
//!
//! // Parse a Dhall type
//! let point_type_str = "{ x: Natural, y: Natural }";
-//! let point_type: Value = serde_dhall::from_str(point_type_str)?;
+//! let point_type = serde_dhall::from_str(point_type_str).parse::<SimpleType>()?;
//!
//! // Some Dhall data
//! let point_data = "{ x = 1, y = 1 + 1 }";
//!
//! // Deserialize the data to a Rust type. This checks that
//! // the data matches the provided type.
-//! let deserialized_map: HashMap<String, usize> =
-//! serde_dhall::from_str_check_type(point_data, &point_type)?;
+//! let deserialized_map = serde_dhall::from_str(point_data)
+//! .type_annotation(&point_type)
+//! .parse::<HashMap<String, usize>>()?;
//!
//! let mut expected_map = HashMap::new();
//! expected_map.insert("x".to_string(), 1);
@@ -135,207 +151,35 @@
//! # }
//! ```
//!
-//! You can also let Rust infer the appropriate Dhall type, using the [StaticType] trait.
-//!
-//! ```rust
-//! # fn main() -> serde_dhall::de::Result<()> {
-//! use serde::Deserialize;
-//! use serde_dhall::StaticType;
+//! # Controlling deserialization
//!
-//! #[derive(Debug, Deserialize, StaticType)]
-//! struct Point {
-//! x: u64,
-//! y: u64,
-//! }
-//!
-//! // Some Dhall data
-//! let data = "{ x = 1, y = 1 + 1 }";
-//!
-//! // Convert the Dhall string to a Point.
-//! let point: Point = serde_dhall::from_str_auto_type(data)?;
-//! assert_eq!(point.x, 1);
-//! assert_eq!(point.y, 2);
-//!
-//! // Invalid data fails the type validation
-//! let invalid_data = "{ x = 1, z = 0.3 }";
-//! assert!(serde_dhall::from_str_auto_type::<Point>(invalid_data).is_err());
-//! # Ok(())
-//! # }
-//! ```
+//! If you need more control over the process of reading Dhall values, e.g. disabling
+//! imports, see the [`Deserializer`] methods.
//!
+//! [`Deserializer`]: struct.Deserializer.html
//! [dhall]: https://dhall-lang.org/
//! [serde]: https://docs.serde.rs/serde/
//! [serde::Deserialize]: https://docs.serde.rs/serde/trait.Deserialize.html
-mod serde;
+#[cfg(doctest)]
+mod test_readme {
+ doc_comment::doctest!("../../README.md");
+}
+
+mod deserialize;
+mod error;
+mod options;
mod static_type;
+/// Dhall values
+mod value;
-#[doc(inline)]
-pub use de::{from_str, from_str_auto_type, from_str_check_type};
#[doc(hidden)]
pub use dhall_proc_macros::StaticType;
-pub use static_type::StaticType;
-#[doc(inline)]
-pub use value::Value;
-
-// A Dhall value.
-#[doc(hidden)]
-pub mod value {
- use dhall::syntax::Builtin;
- use dhall::{Normalized, NormalizedExpr, Parsed};
-
- use super::de::{Error, Result};
-
- /// A Dhall value
- #[derive(Debug, Clone, PartialEq, Eq)]
- pub struct Value(Normalized);
- impl Value {
- pub fn from_str(s: &str, ty: Option<&Value>) -> Result<Self> {
- Value::from_str_using_dhall_error_type(s, ty).map_err(Error::Dhall)
- }
- fn from_str_using_dhall_error_type(
- s: &str,
- ty: Option<&Value>,
- ) -> dhall::error::Result<Self> {
- let resolved = Parsed::parse_str(s)?.resolve()?;
- let typed = match ty {
- None => resolved.typecheck()?,
- Some(t) => resolved.typecheck_with(t.as_normalized())?,
- };
- Ok(Value(typed.normalize()))
- }
- pub(crate) fn to_expr(&self) -> NormalizedExpr {
- self.0.to_expr()
- }
- pub(crate) fn as_normalized(&self) -> &Normalized {
- &self.0
- }
-
- pub(crate) fn make_builtin_type(b: Builtin) -> Self {
- Value(Normalized::make_builtin_type(b))
- }
- pub(crate) fn make_optional_type(t: Value) -> Self {
- Value(Normalized::make_optional_type(t.0))
- }
- pub(crate) fn make_list_type(t: Value) -> Self {
- Value(Normalized::make_list_type(t.0))
- }
- // Made public for the StaticType derive macro
- #[doc(hidden)]
- pub fn make_record_type(
- kts: impl Iterator<Item = (String, Value)>,
- ) -> Self {
- Value(Normalized::make_record_type(kts.map(|(k, t)| (k, t.0))))
- }
- #[doc(hidden)]
- pub fn make_union_type(
- kts: impl Iterator<Item = (String, Option<Value>)>,
- ) -> Self {
- Value(Normalized::make_union_type(
- kts.map(|(k, t)| (k, t.map(|t| t.0))),
- ))
- }
- }
-
- impl super::de::sealed::Sealed for Value {}
-
- impl super::de::Deserialize for Value {
- fn from_dhall(v: &Value) -> Result<Self> {
- Ok(v.clone())
- }
- }
-}
-
-/// Deserialize Dhall data to a Rust data structure.
-pub mod de {
- use super::StaticType;
- use super::Value;
- pub use error::{Error, Result};
-
- mod error {
- use dhall::error::Error as DhallError;
-
- pub type Result<T> = std::result::Result<T, Error>;
-
- #[derive(Debug)]
- #[non_exhaustive]
- pub enum Error {
- Dhall(DhallError),
- Deserialize(String),
- }
-
- impl std::fmt::Display for Error {
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
- match self {
- Error::Dhall(err) => write!(f, "{}", err),
- Error::Deserialize(err) => write!(f, "{}", err),
- }
- }
- }
-
- impl std::error::Error for Error {}
-
- impl serde::de::Error for Error {
- fn custom<T>(msg: T) -> Self
- where
- T: std::fmt::Display,
- {
- Error::Deserialize(msg.to_string())
- }
- }
- }
-
- pub(crate) mod sealed {
- pub trait Sealed {}
- }
-
- /// A data structure that can be deserialized from a Dhall expression
- ///
- /// This is automatically implemented for any type that [serde][serde]
- /// can deserialize.
- ///
- /// This trait cannot be implemented manually.
- pub trait Deserialize: sealed::Sealed + Sized {
- /// See [serde_dhall::from_str][crate::from_str]
- fn from_dhall(v: &Value) -> Result<Self>;
- }
-
- /// Deserialize an instance of type `T` from a string of Dhall text.
- ///
- /// This will recursively resolve all imports in the expression, and
- /// typecheck it before deserialization. Relative imports will be resolved relative to the
- /// provided file. More control over this process is not yet available
- /// but will be in a coming version of this crate.
- pub fn from_str<T>(s: &str) -> Result<T>
- where
- T: Deserialize,
- {
- T::from_dhall(&Value::from_str(s, None)?)
- }
-
- /// Deserialize an instance of type `T` from a string of Dhall text,
- /// additionally checking that it matches the supplied type.
- ///
- /// Like [from_str], but this additionally checks that
- /// the type of the provided expression matches the supplied type.
- pub fn from_str_check_type<T>(s: &str, ty: &Value) -> Result<T>
- where
- T: Deserialize,
- {
- T::from_dhall(&Value::from_str(s, Some(ty))?)
- }
-
- /// Deserialize an instance of type `T` from a string of Dhall text,
- /// additionally checking that it matches the type of `T`.
- ///
- /// Like [from_str], but this additionally checks that
- /// the type of the provided expression matches the output type `T`. The [StaticType] trait
- /// captures Rust types that are valid Dhall types.
- pub fn from_str_auto_type<T>(s: &str) -> Result<T>
- where
- T: Deserialize + StaticType,
- {
- from_str_check_type(s, &<T as StaticType>::static_type())
- }
-}
+pub use deserialize::FromDhall;
+pub(crate) use deserialize::Sealed;
+pub(crate) use error::ErrorKind;
+pub use error::{Error, Result};
+pub use options::{from_file, from_str, Deserializer};
+pub use static_type::StaticType;
+pub use value::{SimpleType, Value};
diff --git a/serde_dhall/src/options.rs b/serde_dhall/src/options.rs
new file mode 100644
index 0000000..06a4368
--- /dev/null
+++ b/serde_dhall/src/options.rs
@@ -0,0 +1,368 @@
+use std::path::{Path, PathBuf};
+
+use dhall::Parsed;
+
+use crate::SimpleType;
+use crate::{Error, ErrorKind, FromDhall, Result, StaticType, Value};
+
+#[derive(Debug, Clone)]
+enum Source<'a> {
+ Str(&'a str),
+ File(PathBuf),
+ // 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
+/// are permitted during evaluation.
+///
+/// Generally speaking, when using [`Deserializer`], you'll create it with [`from_str`] or [`from_file`], then
+/// chain calls to methods to set each option, then call [`parse`]. This will give you a
+/// [`Result<T>`] where `T` is a deserializable type of your choice.
+///
+/// [`Deserializer`]: struct.Deserializer.html
+/// [`from_str`]: fn.from_str.html
+/// [`from_file`]: fn.from_file.html
+/// [`parse`]: struct.Deserializer.html#method.parse
+/// [`Result<T>`]: type.Result.html
+///
+/// # Examples
+///
+/// Reading from a file:
+///
+/// ```no_run
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde_dhall::from_file;
+///
+/// let data = from_file("foo.dhall").parse::<u64>()?;
+/// # Ok(())
+/// # }
+/// ```
+///
+/// Reading from a file and checking the value against a provided type:
+///
+/// ```no_run
+/// # fn main() -> serde_dhall::Result<()> {
+/// use std::collections::HashMap;
+/// use serde_dhall::{from_file, from_str};
+///
+/// let ty = from_str("{ x: Natural, y: Natural }").parse()?;
+/// let data = from_file("foo.dhall")
+/// .type_annotation(&ty)
+/// .parse::<HashMap<String, usize>>()?;
+/// # Ok(())
+/// # }
+/// ```
+#[derive(Debug, Clone)]
+pub struct Deserializer<'a, A> {
+ source: Source<'a>,
+ annot: A,
+ allow_imports: bool,
+ // allow_remote_imports: bool,
+ // use_cache: bool,
+}
+
+impl<'a> Deserializer<'a, NoAnnot> {
+ fn default_with_source(source: Source<'a>) -> Self {
+ Deserializer {
+ source,
+ annot: NoAnnot,
+ allow_imports: true,
+ // allow_remote_imports: true,
+ // use_cache: true,
+ }
+ }
+ fn from_str(s: &'a str) -> Self {
+ Self::default_with_source(Source::Str(s))
+ }
+ fn from_file<P: AsRef<Path>>(path: P) -> Self {
+ Self::default_with_source(Source::File(path.as_ref().to_owned()))
+ }
+ // fn from_url(url: &'a str) -> Self {
+ // Self::default_with_source(Source::Url(url))
+ // }
+
+ /// Ensures that the parsed value matches the provided type.
+ ///
+ /// 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 std::collections::HashMap;
+ /// use serde::Deserialize;
+ /// use serde_dhall::{from_str, SimpleType};
+ ///
+ /// // Parse a Dhall type
+ /// let type_str = "{ x: Natural, y: Natural }";
+ /// let ty = from_str(type_str).parse::<SimpleType>()?;
+ ///
+ /// // Parse some Dhall data.
+ /// let data = "{ x = 1, y = 1 + 1 }";
+ /// let point = from_str(data)
+ /// .type_annotation(&ty)
+ /// .parse::<HashMap<String, usize>>()?;
+ /// assert_eq!(point.get("y"), Some(&2));
+ ///
+ /// // Invalid data fails the type validation; deserialization would have succeeded otherwise.
+ /// let invalid_data = "{ x = 1, z = 3 }";
+ /// assert!(
+ /// from_str(invalid_data)
+ /// .type_annotation(&ty)
+ /// .parse::<HashMap<String, usize>>()
+ /// .is_err()
+ /// );
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`static_type_annotation`]: struct.Deserializer.html#method.static_type_annotation
+ /// [`StaticType`]: trait.StaticType.html
+ pub fn type_annotation<'ty>(
+ self,
+ ty: &'ty SimpleType,
+ ) -> Deserializer<'a, ManualAnnot<'ty>> {
+ Deserializer {
+ annot: ManualAnnot(ty),
+ source: self.source,
+ allow_imports: self.allow_imports,
+ }
+ }
+
+ /// Ensures that the parsed value matches the type of `T`.
+ ///
+ /// `T` must implement the [`StaticType`] trait. If it doesn't, you can use [`type_annotation`]
+ /// to provide a type manually.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # fn main() -> serde_dhall::Result<()> {
+ /// use serde::Deserialize;
+ /// use serde_dhall::StaticType;
+ ///
+ /// #[derive(Deserialize, StaticType)]
+ /// struct Point {
+ /// x: u64,
+ /// y: Option<u64>,
+ /// }
+ ///
+ /// // Some Dhall data
+ /// let data = "{ x = 1, y = Some (1 + 1) }";
+ ///
+ /// // Convert the Dhall string to a Point.
+ /// let point = serde_dhall::from_str(data)
+ /// .static_type_annotation()
+ /// .parse::<Point>()?;
+ /// assert_eq!(point.x, 1);
+ /// assert_eq!(point.y, Some(2));
+ ///
+ /// // Invalid data fails the type validation; deserialization would have succeeded otherwise.
+ /// let invalid_data = "{ x = 1 }";
+ /// assert!(
+ /// serde_dhall::from_str(invalid_data)
+ /// .static_type_annotation()
+ /// .parse::<Point>()
+ /// .is_err()
+ /// );
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`type_annotation`]: struct.Deserializer.html#method.type_annotation
+ /// [`StaticType`]: trait.StaticType.html
+ pub fn static_type_annotation(self) -> Deserializer<'a, StaticAnnot> {
+ Deserializer {
+ annot: StaticAnnot,
+ source: self.source,
+ allow_imports: self.allow_imports,
+ }
+ }
+}
+
+impl<'a, A> Deserializer<'a, A> {
+ /// Sets whether to enable imports.
+ ///
+ /// By default, imports are enabled.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # fn main() -> serde_dhall::Result<()> {
+ /// use serde::Deserialize;
+ /// use serde_dhall::SimpleType;
+ ///
+ /// let data = "12 + ./other_file.dhall : Natural";
+ /// assert!(
+ /// serde_dhall::from_str(data)
+ /// .imports(false)
+ /// .parse::<u64>()
+ /// .is_err()
+ /// );
+ /// # Ok(())
+ /// # }
+ /// ```
+ ///
+ /// [`static_type_annotation`]: struct.Deserializer.html#method.static_type_annotation
+ /// [`StaticType`]: trait.StaticType.html
+ pub fn imports(self, imports: bool) -> Self {
+ Deserializer {
+ allow_imports: imports,
+ ..self
+ }
+ }
+
+ // /// TODO
+ // pub fn remote_imports(&mut self, imports: bool) -> &mut Self {
+ // self.allow_remote_imports = imports;
+ // if imports {
+ // self.allow_imports = true;
+ // }
+ // self
+ // }
+
+ fn _parse<T>(&self) -> dhall::error::Result<Value>
+ where
+ T: HasAnnot<A>,
+ {
+ let parsed = match &self.source {
+ Source::Str(s) => Parsed::parse_str(s)?,
+ Source::File(p) => Parsed::parse_file(p.as_ref())?,
+ };
+ let resolved = if self.allow_imports {
+ parsed.resolve()?
+ } else {
+ parsed.skip_resolve()?
+ };
+ let typed = match &T::get_annot(&self.annot) {
+ None => resolved.typecheck()?,
+ Some(ty) => resolved.typecheck_with(ty.to_value().as_hir())?,
+ };
+ Ok(Value::from_nir(typed.normalize().as_nir()))
+ }
+
+ /// Parses the chosen dhall value with the options provided.
+ ///
+ /// If you enabled static annotations, `T` is required to implement [`StaticType`].
+ ///
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// # fn main() -> serde_dhall::Result<()> {
+ /// let data = serde_dhall::from_str("6 * 7").parse::<u64>()?;
+ /// assert_eq!(data, 42);
+ /// # Ok(())
+ /// # }
+ /// ```
+ /// [`StaticType`]: trait.StaticType.html
+ pub fn parse<T>(&self) -> Result<T>
+ where
+ T: FromDhall + HasAnnot<A>,
+ {
+ let val = self
+ ._parse::<T>()
+ .map_err(ErrorKind::Dhall)
+ .map_err(Error)?;
+ T::from_dhall(&val)
+ }
+}
+
+/// Deserialize a value from a string of Dhall text.
+///
+/// This returns a [`Deserializer`] object. Call the [`parse`] method to get the deserialized
+/// value, or use other [`Deserializer`] methods to control the deserialization process.
+///
+/// # Example
+///
+/// ```rust
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde::Deserialize;
+///
+/// // We use serde's derive feature
+/// #[derive(Deserialize)]
+/// struct Point {
+/// x: u64,
+/// y: u64,
+/// }
+///
+/// // Some Dhall data
+/// let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }";
+///
+/// // Parse the Dhall string as a Point.
+/// let point: Point = serde_dhall::from_str(data).parse()?;
+///
+/// assert_eq!(point.x, 1);
+/// assert_eq!(point.y, 2);
+/// # Ok(())
+/// # }
+/// ```
+///
+/// [`Deserializer`]: struct.Deserializer.html
+/// [`parse`]: struct.Deserializer.html#method.parse
+pub fn from_str(s: &str) -> Deserializer<'_, NoAnnot> {
+ Deserializer::from_str(s)
+}
+
+/// Deserialize a value from a Dhall file.
+///
+/// This returns a [`Deserializer`] object. Call the [`parse`] method to get the deserialized
+/// value, or use other [`Deserializer`] methods to control the deserialization process.
+///
+/// # Example
+///
+/// ```no_run
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde::Deserialize;
+///
+/// // We use serde's derive feature
+/// #[derive(Deserialize)]
+/// struct Point {
+/// x: u64,
+/// y: u64,
+/// }
+///
+/// // Parse the Dhall file as a Point.
+/// let point: Point = serde_dhall::from_file("foo.dhall").parse()?;
+/// # Ok(())
+/// # }
+/// ```
+///
+/// [`Deserializer`]: struct.Deserializer.html
+/// [`parse`]: struct.Deserializer.html#method.parse
+pub fn from_file<'a, P: AsRef<Path>>(path: P) -> Deserializer<'a, NoAnnot> {
+ Deserializer::from_file(path)
+}
+
+// pub fn from_url(url: &str) -> Deserializer<'_, NoAnnot> {
+// Deserializer::from_url(url)
+// }
diff --git a/serde_dhall/src/serde.rs b/serde_dhall/src/serde.rs
deleted file mode 100644
index 4fd7815..0000000
--- a/serde_dhall/src/serde.rs
+++ /dev/null
@@ -1,144 +0,0 @@
-use std::borrow::Cow;
-
-use serde::de::value::{
- MapAccessDeserializer, MapDeserializer, SeqDeserializer,
-};
-
-use dhall::syntax::{ExprKind, LitKind};
-use dhall::NormalizedExpr;
-
-use crate::de::{Deserialize, Error, Result};
-use crate::Value;
-
-impl<'a, T> crate::de::sealed::Sealed for T where T: serde::Deserialize<'a> {}
-
-impl<'a, T> Deserialize for T
-where
- T: serde::Deserialize<'a>,
-{
- fn from_dhall(v: &Value) -> Result<Self> {
- T::deserialize(Deserializer(Cow::Owned(v.to_expr())))
- }
-}
-
-struct Deserializer<'a>(Cow<'a, NormalizedExpr>);
-
-impl<'de: 'a, 'a> serde::de::IntoDeserializer<'de, Error> for Deserializer<'a> {
- type Deserializer = Deserializer<'a>;
- fn into_deserializer(self) -> Self::Deserializer {
- self
- }
-}
-
-impl<'de: 'a, 'a> serde::Deserializer<'de> for Deserializer<'a> {
- type Error = Error;
-
- fn deserialize_any<V>(self, visitor: V) -> Result<V::Value>
- where
- V: serde::de::Visitor<'de>,
- {
- use std::convert::TryInto;
- use ExprKind::*;
- use LitKind::*;
- let expr = self.0.as_ref();
- let not_serde_compatible = || {
- Err(Error::Deserialize(format!(
- "this cannot be deserialized into the serde data model: {}",
- expr
- )))
- };
-
- match expr.kind() {
- Lit(Bool(x)) => visitor.visit_bool(*x),
- Lit(Natural(x)) => {
- if let Ok(x64) = (*x).try_into() {
- visitor.visit_u64(x64)
- } else if let Ok(x32) = (*x).try_into() {
- visitor.visit_u32(x32)
- } else {
- unimplemented!()
- }
- }
- Lit(Integer(x)) => {
- if let Ok(x64) = (*x).try_into() {
- visitor.visit_i64(x64)
- } else if let Ok(x32) = (*x).try_into() {
- visitor.visit_i32(x32)
- } else {
- unimplemented!()
- }
- }
- Lit(Double(x)) => visitor.visit_f64((*x).into()),
- TextLit(x) => {
- // Normal form ensures that the tail is empty.
- assert!(x.tail().is_empty());
- visitor.visit_str(x.head())
- }
- EmptyListLit(..) => {
- visitor.visit_seq(SeqDeserializer::new(None::<()>.into_iter()))
- }
- NEListLit(xs) => visitor.visit_seq(SeqDeserializer::new(
- xs.iter().map(|x| Deserializer(Cow::Borrowed(x))),
- )),
- SomeLit(x) => visitor.visit_some(Deserializer(Cow::Borrowed(x))),
- App(f, x) => match f.kind() {
- Builtin(dhall::syntax::Builtin::OptionalNone) => {
- visitor.visit_none()
- }
- Field(y, name) => match y.kind() {
- UnionType(..) => {
- let name: String = name.into();
- visitor.visit_enum(MapAccessDeserializer::new(
- MapDeserializer::new(
- Some((name, Deserializer(Cow::Borrowed(x))))
- .into_iter(),
- ),
- ))
- }
- _ => not_serde_compatible(),
- },
- _ => not_serde_compatible(),
- },
- RecordLit(m) => visitor
- .visit_map(MapDeserializer::new(m.iter().map(|(k, v)| {
- (k.as_ref(), Deserializer(Cow::Borrowed(v)))
- }))),
- Field(y, name) => match y.kind() {
- UnionType(..) => {
- let name: String = name.into();
- visitor.visit_enum(MapAccessDeserializer::new(
- MapDeserializer::new(Some((name, ())).into_iter()),
- ))
- }
- _ => not_serde_compatible(),
- },
- Const(..) | Var(..) | Lam(..) | Pi(..) | Let(..) | Annot(..)
- | Assert(..) | Builtin(..) | BinOp(..) | BoolIf(..)
- | RecordType(..) | UnionType(..) | Merge(..) | ToMap(..)
- | Projection(..) | ProjectionByExpr(..) | Completion(..)
- | Import(..) => not_serde_compatible(),
- }
- }
-
- fn deserialize_tuple<V>(self, _: usize, visitor: V) -> Result<V::Value>
- where
- V: serde::de::Visitor<'de>,
- {
- use ExprKind::*;
- let expr = self.0.as_ref();
-
- match expr.kind() {
- // Blindly takes keys in sorted order.
- RecordLit(m) => visitor.visit_seq(SeqDeserializer::new(
- m.iter().map(|(_, v)| Deserializer(Cow::Borrowed(v))),
- )),
- _ => 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
- tuple_struct map struct enum identifier ignored_any
- }
-}
diff --git a/serde_dhall/src/static_type.rs b/serde_dhall/src/static_type.rs
index 1eb9150..26c70cd 100644
--- a/serde_dhall/src/static_type.rs
+++ b/serde_dhall/src/static_type.rs
@@ -1,27 +1,92 @@
-use dhall::syntax::Builtin;
-
-use crate::Value;
+use crate::SimpleType;
/// A Rust type that can be represented as a Dhall type.
///
-/// A typical example is `Option<bool>`,
-/// represented by the dhall expression `Optional Bool`.
+/// A typical example is `Option<bool>`, represented by the Dhall expression `Optional Bool`.
+///
+/// This trait can be automatically derived, and this is the recommended way of implementing it.
+///
+/// Some Rust types cannot implement this trait, because there isn't a single Dhall type that
+/// corresponds to them. For example, `HashMap<String, u64>` could correspond to multiple different
+/// Dhall types, e.g. `{ foo: Natural, bar: Natural }` and `{ baz: Natural }`.
+///
+/// # Example
+///
+/// ```rust
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde_dhall::{SimpleType, StaticType};
+///
+/// #[derive(StaticType)]
+/// struct Foo {
+/// x: bool,
+/// y: Vec<u64>,
+/// }
+///
+/// let ty: SimpleType =
+/// serde_dhall::from_str("{ x: Bool, y: List Natural }").parse()?;
+///
+/// assert_eq!(Foo::static_type(), ty);
+/// # Ok(())
+/// # }
+/// ```
+///
+/// # Type correspondence
///
-/// This trait can and should be automatically derived.
+/// The following Dhall types correspond to the following Rust types:
///
-/// The representation needs to be independent of the value.
-/// For this reason, something like `HashMap<String, bool>` cannot implement
-/// [StaticType] because each different value would
-/// have a different Dhall record type.
+/// 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
+/// `T -> U` | unsupported
+/// `Prelude.JSON.Type` | unsupported
+/// `Prelude.Map.Type T U` | unsupported
pub trait StaticType {
- fn static_type() -> Value;
+ /// Return the Dhall type that represents this type.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// # fn main() -> serde_dhall::Result<()> {
+ /// use serde::Deserialize;
+ /// use serde_dhall::{SimpleType, StaticType};
+ ///
+ /// // Using `derive(StaticType)` here would give it the type `{ _1: List Natural }`.
+ /// #[derive(Deserialize)]
+ /// #[serde(transparent)]
+ /// struct Foo(Vec<u64>);
+ ///
+ /// impl StaticType for Foo {
+ /// fn static_type() -> SimpleType {
+ /// SimpleType::List(Box::new(SimpleType::Natural))
+ /// }
+ /// }
+ ///
+ /// let foo = serde_dhall::from_str("[ 1, 2 ]")
+ /// .static_type_annotation()
+ /// .parse::<Foo>()?;
+ ///
+ /// assert_eq!(foo.0, vec![1, 2]);
+ /// # Ok(())
+ /// # }
+ /// ```
+ fn static_type() -> SimpleType;
}
macro_rules! derive_builtin {
- ($ty:ty, $builtin:ident) => {
- impl StaticType for $ty {
- fn static_type() -> Value {
- Value::make_builtin_type(Builtin::$builtin)
+ ($rust_ty:ty, $dhall_ty:ident) => {
+ impl StaticType for $rust_ty {
+ fn static_type() -> SimpleType {
+ SimpleType::$dhall_ty
}
}
};
@@ -43,13 +108,14 @@ where
A: StaticType,
B: StaticType,
{
- fn static_type() -> Value {
- Value::make_record_type(
+ fn static_type() -> SimpleType {
+ SimpleType::Record(
vec![
("_1".to_owned(), A::static_type()),
("_2".to_owned(), B::static_type()),
]
- .into_iter(),
+ .into_iter()
+ .collect(),
)
}
}
@@ -59,13 +125,14 @@ where
T: StaticType,
E: StaticType,
{
- fn static_type() -> Value {
- Value::make_union_type(
+ fn static_type() -> SimpleType {
+ SimpleType::Union(
vec![
("Ok".to_owned(), Some(T::static_type())),
("Err".to_owned(), Some(E::static_type())),
]
- .into_iter(),
+ .into_iter()
+ .collect(),
)
}
}
@@ -74,8 +141,8 @@ impl<T> StaticType for Option<T>
where
T: StaticType,
{
- fn static_type() -> Value {
- Value::make_optional_type(T::static_type())
+ fn static_type() -> SimpleType {
+ SimpleType::Optional(Box::new(T::static_type()))
}
}
@@ -83,8 +150,8 @@ impl<T> StaticType for Vec<T>
where
T: StaticType,
{
- fn static_type() -> Value {
- Value::make_list_type(T::static_type())
+ fn static_type() -> SimpleType {
+ SimpleType::List(Box::new(T::static_type()))
}
}
@@ -92,7 +159,7 @@ impl<'a, T> StaticType for &'a T
where
T: StaticType,
{
- fn static_type() -> Value {
+ fn static_type() -> SimpleType {
T::static_type()
}
}
diff --git a/serde_dhall/src/value.rs b/serde_dhall/src/value.rs
new file mode 100644
index 0000000..d6631da
--- /dev/null
+++ b/serde_dhall/src/value.rs
@@ -0,0 +1,287 @@
+use std::collections::{BTreeMap, HashMap};
+
+use dhall::semantics::{Hir, HirKind, Nir, NirKind};
+use dhall::syntax::{Builtin, Expr, ExprKind, NumKind, Span};
+
+use crate::{Error, ErrorKind, FromDhall, Result, Sealed};
+
+#[doc(hidden)]
+/// An arbitrary Dhall value.
+#[derive(Debug, Clone)]
+pub struct Value {
+ /// Invariant: in normal form
+ hir: Hir,
+ /// Cached conversions because they are annoying to construct from Hir.
+ /// At most one of them will be `Some`.
+ as_simple_val: Option<SimpleValue>,
+ as_simple_ty: Option<SimpleType>,
+}
+
+/// A simple value of the kind that can be decoded with serde
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) enum SimpleValue {
+ Num(NumKind),
+ Text(String),
+ Optional(Option<Box<SimpleValue>>),
+ List(Vec<SimpleValue>),
+ Record(BTreeMap<String, SimpleValue>),
+ Union(String, Option<Box<SimpleValue>>),
+}
+
+/// The type of a value that can be decoded by `serde_dhall`, e.g. `{ x: Bool, y: List Natural }`.
+///
+/// A `SimpleType` is used when deserializing values to ensure they are of the expected type.
+/// Rather than letting `serde` handle potential type mismatches, this uses the type-checking
+/// capabilities of Dhall to catch errors early and cleanly indicate in the user's code where the
+/// mismatch happened.
+///
+/// You would typically not manipulate `SimpleType`s by hand but rather let Rust infer it for your
+/// datatype by deriving the [`StaticType`] trait, and using
+/// [`Deserializer::static_type_annotation`]. If you need to supply a `SimpleType` manually, you
+/// can either deserialize it like any other Dhall value, or construct it manually.
+///
+/// [`StaticType`]: trait.StaticType.html
+/// [`Deserializer::static_type_annotation`]: options/struct.Deserializer.html#method.static_type_annotation
+///
+/// # Examples
+///
+/// ```rust
+/// # fn main() -> serde_dhall::Result<()> {
+/// use serde_dhall::{SimpleType, StaticType};
+///
+/// #[derive(StaticType)]
+/// struct Foo {
+/// x: bool,
+/// y: Vec<u64>,
+/// }
+///
+/// let ty: SimpleType =
+/// serde_dhall::from_str("{ x: Bool, y: List Natural }").parse()?;
+///
+/// assert_eq!(Foo::static_type(), ty);
+/// # Ok(())
+/// # }
+/// ```
+///
+/// ```rust
+/// # fn main() -> serde_dhall::Result<()> {
+/// use std::collections::HashMap;
+/// use serde_dhall::SimpleType;
+///
+/// let ty: SimpleType =
+/// serde_dhall::from_str("{ x: Natural, y: Natural }").parse()?;
+///
+/// let mut map = HashMap::new();
+/// map.insert("x".to_string(), SimpleType::Natural);
+/// map.insert("y".to_string(), SimpleType::Natural);
+/// assert_eq!(ty, SimpleType::Record(map));
+/// # Ok(())
+/// # }
+/// ```
+///
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum SimpleType {
+ /// Corresponds to the Dhall type `Bool`
+ Bool,
+ /// Corresponds to the Dhall type `Natural`
+ Natural,
+ /// Corresponds to the Dhall type `Integer`
+ Integer,
+ /// Corresponds to the Dhall type `Double`
+ Double,
+ /// Corresponds to the Dhall type `Text`
+ Text,
+ /// Corresponds to the Dhall type `Optional T`
+ Optional(Box<SimpleType>),
+ /// Corresponds to the Dhall type `List T`
+ List(Box<SimpleType>),
+ /// Corresponds to the Dhall type `{ x : T, y : U }`
+ Record(HashMap<String, SimpleType>),
+ /// Corresponds to the Dhall type `< x : T | y : U >`
+ Union(HashMap<String, Option<SimpleType>>),
+}
+
+impl Value {
+ pub(crate) fn from_nir(x: &Nir) -> Self {
+ Value {
+ hir: x.to_hir_noenv(),
+ as_simple_val: SimpleValue::from_nir(x),
+ as_simple_ty: SimpleType::from_nir(x),
+ }
+ }
+
+ pub(crate) fn as_hir(&self) -> &Hir {
+ &self.hir
+ }
+
+ /// Converts a Value into a SimpleValue.
+ pub(crate) fn to_simple_value(&self) -> Option<SimpleValue> {
+ self.as_simple_val.clone()
+ }
+
+ /// Converts a Value into a SimpleType.
+ pub(crate) fn to_simple_type(&self) -> Option<SimpleType> {
+ self.as_simple_ty.clone()
+ }
+
+ /// Converts a value back to the corresponding AST expression.
+ pub(crate) fn to_expr(&self) -> Expr {
+ self.hir.to_expr(Default::default())
+ }
+}
+
+impl SimpleValue {
+ pub(crate) fn from_nir(nir: &Nir) -> Option<Self> {
+ Some(match nir.kind() {
+ NirKind::Num(lit) => SimpleValue::Num(lit.clone()),
+ NirKind::TextLit(x) => SimpleValue::Text(
+ x.as_text()
+ .expect("Normal form should ensure the text is a string"),
+ ),
+ NirKind::EmptyOptionalLit(_) => SimpleValue::Optional(None),
+ NirKind::NEOptionalLit(x) => {
+ SimpleValue::Optional(Some(Box::new(Self::from_nir(x)?)))
+ }
+ NirKind::EmptyListLit(_) => SimpleValue::List(vec![]),
+ NirKind::NEListLit(xs) => SimpleValue::List(
+ xs.iter().map(Self::from_nir).collect::<Option<_>>()?,
+ ),
+ NirKind::RecordLit(kvs) => SimpleValue::Record(
+ kvs.iter()
+ .map(|(k, v)| Some((k.into(), Self::from_nir(v)?)))
+ .collect::<Option<_>>()?,
+ ),
+ NirKind::UnionLit(field, x, _) => SimpleValue::Union(
+ field.into(),
+ Some(Box::new(Self::from_nir(x)?)),
+ ),
+ NirKind::UnionConstructor(field, ty)
+ if ty.get(field).map(|f| f.is_some()) == Some(false) =>
+ {
+ SimpleValue::Union(field.into(), None)
+ }
+ _ => return None,
+ })
+ }
+}
+
+impl SimpleType {
+ pub(crate) fn from_nir(nir: &Nir) -> Option<Self> {
+ Some(match nir.kind() {
+ NirKind::BuiltinType(b) => match b {
+ Builtin::Bool => SimpleType::Bool,
+ Builtin::Natural => SimpleType::Natural,
+ Builtin::Integer => SimpleType::Integer,
+ Builtin::Double => SimpleType::Double,
+ Builtin::Text => SimpleType::Text,
+ _ => unreachable!(),
+ },
+ NirKind::OptionalType(t) => {
+ SimpleType::Optional(Box::new(Self::from_nir(t)?))
+ }
+ NirKind::ListType(t) => {
+ SimpleType::List(Box::new(Self::from_nir(t)?))
+ }
+ NirKind::RecordType(kts) => SimpleType::Record(
+ kts.iter()
+ .map(|(k, v)| Some((k.into(), Self::from_nir(v)?)))
+ .collect::<Option<_>>()?,
+ ),
+ NirKind::UnionType(kts) => SimpleType::Union(
+ kts.iter()
+ .map(|(k, v)| {
+ Some((
+ k.into(),
+ v.as_ref()
+ .map(|v| Ok(Self::from_nir(v)?))
+ .transpose()?,
+ ))
+ })
+ .collect::<Option<_>>()?,
+ ),
+ _ => return None,
+ })
+ }
+
+ pub(crate) fn to_value(&self) -> Value {
+ Value {
+ hir: self.to_hir(),
+ as_simple_val: None,
+ as_simple_ty: Some(self.clone()),
+ }
+ }
+ pub(crate) fn to_hir(&self) -> Hir {
+ let hir = |k| Hir::new(HirKind::Expr(k), Span::Artificial);
+ hir(match self {
+ SimpleType::Bool => ExprKind::Builtin(Builtin::Bool),
+ SimpleType::Natural => ExprKind::Builtin(Builtin::Natural),
+ SimpleType::Integer => ExprKind::Builtin(Builtin::Integer),
+ SimpleType::Double => ExprKind::Builtin(Builtin::Double),
+ SimpleType::Text => ExprKind::Builtin(Builtin::Text),
+ SimpleType::Optional(t) => ExprKind::App(
+ hir(ExprKind::Builtin(Builtin::Optional)),
+ t.to_hir(),
+ ),
+ SimpleType::List(t) => {
+ ExprKind::App(hir(ExprKind::Builtin(Builtin::List)), t.to_hir())
+ }
+ SimpleType::Record(kts) => ExprKind::RecordType(
+ kts.iter()
+ .map(|(k, t)| (k.as_str().into(), t.to_hir()))
+ .collect(),
+ ),
+ SimpleType::Union(kts) => ExprKind::UnionType(
+ kts.iter()
+ .map(|(k, t)| {
+ (k.as_str().into(), t.as_ref().map(|t| t.to_hir()))
+ })
+ .collect(),
+ ),
+ })
+ }
+}
+
+impl Sealed for Value {}
+impl Sealed for SimpleValue {}
+impl Sealed for SimpleType {}
+
+impl FromDhall for Value {
+ fn from_dhall(v: &Value) -> Result<Self> {
+ Ok(v.clone())
+ }
+}
+impl FromDhall for SimpleValue {
+ fn from_dhall(v: &Value) -> Result<Self> {
+ v.to_simple_value().ok_or_else(|| {
+ Error(ErrorKind::Deserialize(format!(
+ "this cannot be deserialized into a simple type: {}",
+ v
+ )))
+ })
+ }
+}
+impl FromDhall for SimpleType {
+ fn from_dhall(v: &Value) -> Result<Self> {
+ v.to_simple_type().ok_or_else(|| {
+ Error(ErrorKind::Deserialize(format!(
+ "this cannot be deserialized into a simple type: {}",
+ v
+ )))
+ })
+ }
+}
+
+impl Eq for Value {}
+impl PartialEq for Value {
+ fn eq(&self, other: &Self) -> bool {
+ self.hir == other.hir
+ }
+}
+impl std::fmt::Display for Value {
+ fn fmt(
+ &self,
+ f: &mut std::fmt::Formatter,
+ ) -> std::result::Result<(), std::fmt::Error> {
+ self.to_expr().fmt(f)
+ }
+}
diff --git a/serde_dhall/tests/de.rs b/serde_dhall/tests/de.rs
index 74912dd..a5c42fd 100644
--- a/serde_dhall/tests/de.rs
+++ b/serde_dhall/tests/de.rs
@@ -1,10 +1,10 @@
use serde::Deserialize;
-use serde_dhall::{from_str, from_str_auto_type, StaticType};
+use serde_dhall::{from_str, FromDhall, StaticType};
#[test]
fn test_de_typed() {
- fn parse<T: serde_dhall::de::Deserialize + StaticType>(s: &str) -> T {
- from_str_auto_type(s).unwrap()
+ fn parse<T: FromDhall + StaticType>(s: &str) -> T {
+ from_str(s).static_type_annotation().parse().unwrap()
}
assert_eq!(parse::<bool>("True"), true);
@@ -51,12 +51,17 @@ fn test_de_typed() {
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() {
- fn parse<T: serde_dhall::de::Deserialize>(s: &str) -> T {
- from_str(s).unwrap()
+ fn parse<T: FromDhall>(s: &str) -> T {
+ from_str(s).parse().unwrap()
}
// Test tuples on record of wrong type
@@ -83,6 +88,17 @@ fn test_de_untyped() {
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::<bool>("List/length [True, 42]").is_err());
+ 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/traits.rs b/serde_dhall/tests/traits.rs
index 15a91ed..3c6fbfe 100644
--- a/serde_dhall/tests/traits.rs
+++ b/serde_dhall/tests/traits.rs
@@ -1,9 +1,9 @@
-use serde_dhall::{from_str, StaticType, Value};
+use serde_dhall::{from_str, SimpleType, StaticType};
#[test]
fn test_static_type() {
- fn parse(s: &str) -> Value {
- from_str(s).unwrap()
+ fn parse(s: &str) -> SimpleType {
+ from_str(s).parse().unwrap()
}
assert_eq!(bool::static_type(), parse("Bool"));
diff --git a/serde_dhall/tests/version_numbers.rs b/serde_dhall/tests/version_numbers.rs
new file mode 100644
index 0000000..8307e47
--- /dev/null
+++ b/serde_dhall/tests/version_numbers.rs
@@ -0,0 +1,17 @@
+#[test]
+fn test_readme_deps() {
+ version_sync::assert_markdown_deps_updated!("../README.md");
+}
+
+#[test]
+fn test_html_root_url() {
+ version_sync::assert_html_root_url_updated!("src/lib.rs");
+}
+
+#[test]
+fn test_readme_mentions_version() {
+ version_sync::assert_contains_regex!(
+ "../README.md",
+ "^#### \\[{version}\\]"
+ );
+}