diff options
author | Nadrieril | 2020-04-05 17:57:07 +0100 |
---|---|---|
committer | GitHub | 2020-04-05 17:57:07 +0100 |
commit | 7e977f282fb6a0eff0ef45738b9b5c98dc4c6fee (patch) | |
tree | ad4249609707fd8720a44469152105c2f6a67c79 /serde_dhall/src/lib.rs | |
parent | 5a5aa49e64197899006751db72e404f4b2292d4e (diff) | |
parent | 820214615547101f8f2b5de209b5189968bddfee (diff) |
Merge pull request #154 from Nadrieril/cleanup-api
Rewrite serde_dhall API
Diffstat (limited to '')
-rw-r--r-- | serde_dhall/src/lib.rs | 316 |
1 files changed, 80 insertions, 236 deletions
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}; |