From e070270c3f1f10d46281ed7751ff95e15092e7f4 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 10 May 2020 22:09:43 +0100 Subject: Implement serialization --- serde_dhall/src/deserialize.rs | 16 +- serde_dhall/src/error.rs | 11 ++ serde_dhall/src/lib.rs | 6 +- serde_dhall/src/options.rs | 372 ----------------------------------------- serde_dhall/src/options/de.rs | 372 +++++++++++++++++++++++++++++++++++++++++ serde_dhall/src/options/mod.rs | 2 + serde_dhall/src/options/ser.rs | 67 ++++++++ serde_dhall/src/serialize.rs | 312 ++++++++++++++++++++++++++++++++++ serde_dhall/src/static_type.rs | 60 +++++++ serde_dhall/src/value.rs | 106 +++++++++++- serde_dhall/tests/de.rs | 59 +------ serde_dhall/tests/serde.rs | 145 ++++++++++++++++ 12 files changed, 1090 insertions(+), 438 deletions(-) delete mode 100644 serde_dhall/src/options.rs create mode 100644 serde_dhall/src/options/de.rs create mode 100644 serde_dhall/src/options/mod.rs create mode 100644 serde_dhall/src/options/ser.rs create mode 100644 serde_dhall/src/serialize.rs create mode 100644 serde_dhall/tests/serde.rs (limited to 'serde_dhall') 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 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(self, visitor: V) -> crate::Result + 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 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(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..e236e37 100644 --- a/serde_dhall/src/lib.rs +++ b/serde_dhall/src/lib.rs @@ -169,6 +169,7 @@ mod test_readme { mod deserialize; mod error; mod options; +mod serialize; mod static_type; /// Dhall values mod value; @@ -176,10 +177,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.rs deleted file mode 100644 index 7b27114..0000000 --- a/serde_dhall/src/options.rs +++ /dev/null @@ -1,372 +0,0 @@ -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 { - fn get_annot(a: &A) -> Option; -} -impl HasAnnot for T { - fn get_annot(_: &NoAnnot) -> Option { - None - } -} -impl<'ty, T> HasAnnot> for T { - fn get_annot(a: &ManualAnnot<'ty>) -> Option { - Some(a.0.clone()) - } -} -impl HasAnnot for T { - fn get_annot(_: &StaticAnnot) -> Option { - 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`] 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`]: 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::()?; -/// # 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::>()?; -/// # 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>(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::()?; - /// - /// // Parse some Dhall data. - /// let data = "{ x = 1, y = 1 + 1 }"; - /// let point = from_str(data) - /// .type_annotation(&ty) - /// .parse::>()?; - /// 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::>() - /// .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, - /// } - /// - /// // 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::()?; - /// 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::() - /// .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::() - /// .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(&self) -> dhall::error::Result - where - T: HasAnnot, - { - 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::()?; - /// assert_eq!(data, 42); - /// # Ok(()) - /// # } - /// ``` - /// [`StaticType`]: trait.StaticType.html - pub fn parse(&self) -> Result - where - T: FromDhall + HasAnnot, - { - let val = self - ._parse::() - .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. -/// -/// Imports will be resolved relative to the current directory. -/// -/// # 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. -/// -/// Imports will be resolved relative to the provided file's path. -/// -/// # 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: 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/options/de.rs b/serde_dhall/src/options/de.rs new file mode 100644 index 0000000..8ff794d --- /dev/null +++ b/serde_dhall/src/options/de.rs @@ -0,0 +1,372 @@ +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 OptionalAnnot { + fn get_annot(a: &A) -> Option; +} +impl OptionalAnnot for T { + fn get_annot(_: &NoAnnot) -> Option { + None + } +} +impl<'ty, T> OptionalAnnot> for T { + fn get_annot(a: &ManualAnnot<'ty>) -> Option { + Some(a.0.clone()) + } +} +impl OptionalAnnot for T { + fn get_annot(_: &StaticAnnot) -> Option { + 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`] 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`]: 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::()?; +/// # 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::>()?; +/// # 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>(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::()?; + /// + /// // Parse some Dhall data. + /// let data = "{ x = 1, y = 1 + 1 }"; + /// let point = from_str(data) + /// .type_annotation(&ty) + /// .parse::>()?; + /// 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::>() + /// .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, + /// } + /// + /// // 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::()?; + /// 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::() + /// .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::() + /// .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(&self) -> dhall::error::Result + where + T: OptionalAnnot, + { + 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::()?; + /// assert_eq!(data, 42); + /// # Ok(()) + /// # } + /// ``` + /// [`StaticType`]: trait.StaticType.html + pub fn parse(&self) -> Result + where + T: FromDhall + OptionalAnnot, + { + let val = self + ._parse::() + .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. +/// +/// Imports will be resolved relative to the current directory. +/// +/// # 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. +/// +/// Imports will be resolved relative to the provided file's path. +/// +/// # 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: 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/options/mod.rs b/serde_dhall/src/options/mod.rs new file mode 100644 index 0000000..384f318 --- /dev/null +++ b/serde_dhall/src/options/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod de; +pub(crate) mod ser; diff --git a/serde_dhall/src/options/ser.rs b/serde_dhall/src/options/ser.rs new file mode 100644 index 0000000..026dd21 --- /dev/null +++ b/serde_dhall/src/options/ser.rs @@ -0,0 +1,67 @@ +use crate::{Result, SimpleType, StaticType, ToDhall}; + +#[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 RequiredAnnot { + fn get_annot(a: &A) -> SimpleType; +} +impl<'ty, T> RequiredAnnot> for T { + fn get_annot(a: &ManualAnnot<'ty>) -> SimpleType { + a.0.clone() + } +} +impl RequiredAnnot for T { + fn get_annot(_: &StaticAnnot) -> SimpleType { + T::static_type() + } +} + +#[derive(Debug, Clone)] +pub struct Serializer<'a, T, A> { + data: &'a T, + annot: A, +} + +impl<'a, T> Serializer<'a, T, NoAnnot> { + pub fn type_annotation<'ty>( + self, + ty: &'ty SimpleType, + ) -> Serializer<'a, T, ManualAnnot<'ty>> { + Serializer { + annot: ManualAnnot(ty), + data: self.data, + } + } + + pub fn static_type_annotation(self) -> Serializer<'a, T, StaticAnnot> { + Serializer { + annot: StaticAnnot, + data: self.data, + } + } +} + +impl<'a, T, A> Serializer<'a, T, A> { + pub fn to_string(&self) -> Result + where + T: ToDhall + RequiredAnnot, + { + let val = self.data.to_dhall(&T::get_annot(&self.annot))?; + Ok(val.to_string()) + } +} + +pub fn serialize<'a, T>(data: &'a T) -> Serializer<'a, 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..dd3e426 --- /dev/null +++ b/serde_dhall/src/serialize.rs @@ -0,0 +1,312 @@ +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 {} + +pub trait ToDhall: Sealed { + #[doc(hidden)] + fn to_dhall(&self, ty: &SimpleType) -> Result; +} + +impl Sealed for T where T: ser::Serialize {} + +impl ToDhall for T +where + T: ser::Serialize, +{ + fn to_dhall(&self, ty: &SimpleType) -> Result { + 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; + type SerializeTupleVariant = ser::Impossible; + type SerializeMap = MapSerializer; + type SerializeStruct = StructSerializer; + type SerializeStructVariant = ser::Impossible; + + fn serialize_bool(self, v: bool) -> Result { + Ok(Num(NumKind::Bool(v))) + } + + fn serialize_i8(self, v: i8) -> Result { + self.serialize_i64(i64::from(v)) + } + fn serialize_i16(self, v: i16) -> Result { + self.serialize_i64(i64::from(v)) + } + fn serialize_i32(self, v: i32) -> Result { + self.serialize_i64(i64::from(v)) + } + fn serialize_i64(self, v: i64) -> Result { + Ok(Num(NumKind::Integer(v))) + } + + fn serialize_u8(self, v: u8) -> Result { + self.serialize_u64(u64::from(v)) + } + fn serialize_u16(self, v: u16) -> Result { + self.serialize_u64(u64::from(v)) + } + fn serialize_u32(self, v: u32) -> Result { + self.serialize_u64(u64::from(v)) + } + fn serialize_u64(self, v: u64) -> Result { + Ok(Num(NumKind::Natural(v))) + } + + fn serialize_f32(self, v: f32) -> Result { + self.serialize_f64(f64::from(v)) + } + fn serialize_f64(self, v: f64) -> Result { + Ok(Num(NumKind::Double(v.into()))) + } + + fn serialize_char(self, v: char) -> Result { + self.serialize_str(&v.to_string()) + } + fn serialize_str(self, v: &str) -> Result { + Ok(Text(v.to_owned())) + } + + fn serialize_bytes(self, _v: &[u8]) -> Result { + Err(ErrorKind::Serialize( + "Unsupported data for serialization: byte array".to_owned(), + ) + .into()) + } + + fn serialize_none(self) -> Result { + Ok(Optional(None)) + } + fn serialize_some(self, v: &T) -> Result + where + T: ?Sized + ser::Serialize, + { + Ok(Optional(Some(Box::new(v.serialize(self)?)))) + } + + fn serialize_unit(self) -> Result { + Ok(Record(Default::default())) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.serialize_unit() + } + fn serialize_newtype_struct( + self, + _name: &'static str, + _value: &T, + ) -> Result + 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 { + Ok(StructSerializer::default()) + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + Ok(Union(variant.to_owned(), None)) + } + fn serialize_newtype_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + 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 { + 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 { + Err(ErrorKind::Serialize( + "Unsupported data for serialization: struct variant".to_owned(), + ) + .into()) + } + + fn serialize_tuple(self, _len: usize) -> Result { + Ok(TupleSerializer::default()) + } + fn serialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + ) -> Result { + Err(ErrorKind::Serialize( + "Unsupported data for serialization: tuple struct".to_owned(), + ) + .into()) + } + + fn serialize_seq(self, _len: Option) -> Result { + Ok(SeqSerializer::default()) + } + + fn serialize_map(self, _len: Option) -> Result { + Ok(MapSerializer::default()) + } +} + +#[derive(Default)] +struct SeqSerializer(Vec); + +impl ser::SerializeSeq for SeqSerializer { + type Ok = SimpleValue; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + ser::Serialize, + { + self.0.push(value.serialize(Serializer)?); + Ok(()) + } + + fn end(self) -> Result { + Ok(List(self.0)) + } +} + +#[derive(Default)] +struct TupleSerializer(Vec); + +impl ser::SerializeTuple for TupleSerializer { + type Ok = SimpleValue; + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<()> + where + T: ?Sized + ser::Serialize, + { + self.0.push(value.serialize(Serializer)?); + Ok(()) + } + + fn end(self) -> Result { + Ok(Record( + self.0 + .into_iter() + .enumerate() + .map(|(i, x)| (format!("_{}", i + 1), x)) + .collect(), + )) + } +} + +#[derive(Default)] +struct MapSerializer { + map: BTreeMap, + key: Option, + val: Option, +} + +impl ser::SerializeMap for MapSerializer { + type Ok = SimpleValue; + type Error = Error; + + fn serialize_key(&mut self, key: &T) -> Result<()> + where + T: ?Sized + ser::Serialize, + { + let key = match key.serialize(Serializer)? { + Text(key) => key, + _ => return Err(::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(&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 { + Ok(Record(self.map)) + } +} + +#[derive(Default)] +struct StructSerializer(BTreeMap); + +impl ser::SerializeStruct for StructSerializer { + type Ok = SimpleValue; + type Error = Error; + + fn serialize_field(&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 { + Ok(Record(self.0)) + } +} diff --git a/serde_dhall/src/static_type.rs b/serde_dhall/src/static_type.rs index 3c5da18..7e6c27c 100644 --- a/serde_dhall/src/static_type.rs +++ b/serde_dhall/src/static_type.rs @@ -103,6 +103,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 StaticType for (A,) +where + A: StaticType, +{ + fn static_type() -> SimpleType { + SimpleType::Record( + vec![("_1".to_owned(), A::static_type())] + .into_iter() + .collect(), + ) + } +} impl StaticType for (A, B) where @@ -121,6 +141,46 @@ where } } +impl 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 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 StaticType for std::result::Result where T: StaticType, diff --git a/serde_dhall/src/value.rs b/serde_dhall/src/value.rs index d7116d9..ca05658 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. @@ -292,6 +292,94 @@ impl SimpleValue { _ => return None, }) } + + fn to_hir(&self, ty: &SimpleType) -> Result { + use SimpleType as T; + use SimpleValue as V; + let hir = |k| Hir::new(HirKind::Expr(k), Span::Artificial); + let type_error = |msg| Error(ErrorKind::Serialize(msg)); + let kind = match (self, ty) { + (V::Num(num @ NumKind::Bool(_)), T::Bool) + | (V::Num(num @ NumKind::Natural(_)), T::Natural) + | (V::Num(num @ NumKind::Integer(_)), T::Integer) + | (V::Num(num @ NumKind::Double(_)), T::Double) => { + ExprKind::Num(num.clone()) + } + (V::Text(v), T::Text) => ExprKind::TextLit(v.clone().into()), + (V::Optional(None), T::Optional(t)) => ExprKind::Op(OpKind::App( + hir(ExprKind::Builtin(Builtin::OptionalNone)), + t.to_hir(), + )), + (V::Optional(Some(v)), T::Optional(t)) => { + ExprKind::SomeLit(v.to_hir(t)?) + } + (V::List(v), 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), T::List(t)) => ExprKind::NEListLit( + v.iter().map(|v| v.to_hir(t)).collect::>()?, + ), + (V::Record(v), T::Record(t)) => ExprKind::RecordLit( + v.iter() + .map(|(k, v)| match t.get(k) { + Some(t) => Ok((k.clone().into(), v.to_hir(t)?)), + None => Err(type_error(format!( + "expected a value of type {}, found {:?}", + ty.to_hir().to_expr(Default::default()), + self + ))), + }) + .collect::>()?, + ), + (V::Union(variant, Some(v)), T::Union(t)) => match t.get(variant) { + Some(Some(variant_t)) => ExprKind::Op(OpKind::App( + hir(ExprKind::Op(OpKind::Field( + ty.to_hir(), + variant.clone().into(), + ))), + v.to_hir(variant_t)?, + )), + _ => { + return Err(type_error(format!( + "expected a value of type {}, found {:?}", + ty.to_hir().to_expr(Default::default()), + self + ))) + } + }, + (V::Union(variant, None), T::Union(t)) => match t.get(variant) { + Some(None) => ExprKind::Op(OpKind::Field( + ty.to_hir(), + variant.clone().into(), + )), + _ => { + return Err(type_error(format!( + "expected a value of type {}, found {:?}", + ty.to_hir().to_expr(Default::default()), + self + ))) + } + }, + _ => { + return Err(type_error(format!( + "expected a value of type {}, found {:?}", + ty.to_hir().to_expr(Default::default()), + self + ))) + } + }; + Ok(hir(kind)) + } + pub(crate) fn into_value(self, ty: &SimpleType) -> Result { + Ok(Value { + hir: self.to_hir(ty)?, + as_simple_val: Some(self), + as_simple_ty: None, + }) + } } impl SimpleType { @@ -376,8 +464,10 @@ 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 crate::serialize::Sealed for SimpleValue {} impl FromDhall for Value { fn from_dhall(v: &Value) -> Result { @@ -394,6 +484,16 @@ impl FromDhall for SimpleType { }) } } +impl ToDhall for Value { + fn to_dhall(&self, _ty: &SimpleType) -> Result { + Ok(self.clone()) + } +} +impl ToDhall for SimpleValue { + fn to_dhall(&self, ty: &SimpleType) -> Result { + self.clone().into_value(ty) + } +} impl Eq for Value {} impl PartialEq for Value { diff --git a/serde_dhall/tests/de.rs b/serde_dhall/tests/de.rs index 3abaad2..0b43de2 100644 --- a/serde_dhall/tests/de.rs +++ b/serde_dhall/tests/de.rs @@ -1,62 +1,5 @@ use serde::Deserialize; -use serde_dhall::{from_str, FromDhall, NumKind, SimpleValue, StaticType}; - -#[test] -fn test_de_typed() { - fn parse(s: &str) -> T { - from_str(s).static_type_annotation().parse().unwrap() - } - - assert_eq!(parse::("True"), true); - - assert_eq!(parse::("1"), 1); - assert_eq!(parse::("1"), 1); - assert_eq!(parse::("1"), 1); - - assert_eq!(parse::("+1"), 1); - assert_eq!(parse::("+1"), 1); - assert_eq!(parse::("+1"), 1); - - assert_eq!(parse::("1.0"), 1.0); - assert_eq!(parse::("1.0"), 1.0); - - assert_eq!(parse::(r#""foo""#), "foo".to_owned()); - assert_eq!(parse::>("[] : List Natural"), >::new()); - assert_eq!(parse::>("[1, 2]"), vec![1, 2]); - assert_eq!(parse::>("None Natural"), None); - assert_eq!(parse::>("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::("{ x = 1, y = -2 }"), Foo { x: 1, y: -2 }); - - #[derive(Debug, PartialEq, Eq, Deserialize, StaticType)] - enum Bar { - X(u64), - Y(i64), - } - assert_eq!(parse::("< X: Natural | Y: Integer >.X 1"), Bar::X(1)); - - #[derive(Debug, PartialEq, Eq, Deserialize, StaticType)] - enum Baz { - X, - Y(i64), - } - assert_eq!(parse::("< X | Y: Integer >.X"), Baz::X); - - assert!(from_str("< X | Y: Integer >.Y") - .static_type_annotation() - .parse::() - .is_err()); -} +use serde_dhall::{from_str, FromDhall, NumKind, SimpleValue}; #[test] fn test_de_untyped() { diff --git a/serde_dhall/tests/serde.rs b/serde_dhall/tests/serde.rs new file mode 100644 index 0000000..39f2f79 --- /dev/null +++ b/serde_dhall/tests/serde.rs @@ -0,0 +1,145 @@ +mod serde { + use serde::{Deserialize, Serialize}; + use serde_dhall::{from_str, serialize, FromDhall, StaticType, ToDhall}; + + fn assert_de(s: &str, x: T) + where + T: FromDhall + StaticType + PartialEq + std::fmt::Debug, + { + assert_eq!( + from_str(s) + .static_type_annotation() + .parse::() + .map_err(|e| e.to_string()), + Ok(x) + ); + } + fn assert_ser(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(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", >::new()); + assert_serde("[] : List Text", >::new()); + assert_serde( + r#"["foo", "bar"]"#, + vec!["foo".to_owned(), "bar".to_owned()], + ); + assert_ser(r#"["foo", "bar"]"#, vec!["foo", "bar"]); + assert_serde::>("[1, 2]", vec![1, 2]); + } + + #[test] + fn optional() { + assert_serde::>("None Natural", None); + assert_serde::>("None Text", None); + assert_serde("Some 1", Some(1u64)); + } + + #[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); + + // #[derive( + // Debug, Clone, PartialEq, Eq, Deserialize, Serialize, StaticType, + // )] + // struct Bar(u64); + // assert_serde::("{ _1 = 1 }", Bar (1)); + + #[derive( + Debug, Clone, PartialEq, Eq, Deserialize, Serialize, StaticType, + )] + struct Baz { + x: u64, + y: i64, + } + assert_serde::("{ 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::("< X: Natural | Y: Integer >.X 1", Foo::X(1)); + + #[derive( + Debug, Clone, PartialEq, Eq, Deserialize, Serialize, StaticType, + )] + enum Bar { + X, + Y(i64), + } + assert_serde::("< X | Y: Integer >.X", Bar::X); + + assert!(from_str("< X | Y: Integer >.Y") + .static_type_annotation() + .parse::() + .is_err()); + } +} -- cgit v1.2.3