From d6b52ad8bdb8743855a4147aeebd7ea73eee365b Mon Sep 17 00:00:00 2001 From: Lorenzo Delgado Date: Fri, 4 Apr 2025 18:02:10 +0200 Subject: [PATCH] feat: add `from_reader` and `to_writer` functions - Implemented `from_reader` to deserialize environment variables from a reader. - Added `to_writer` to serialize data to a writer. - Updated `Prefixed` struct to include methods for reading from a reader and writing to a writer. - Enhanced documentation with examples for both new functions. Signed-off-by: Lorenzo Delgado --- src/de.rs | 133 +++++++++----- src/lib.rs | 15 +- src/prefixed.rs | 199 ++++++++++++++++---- src/ser.rs | 471 +++++++++++++++++++++++++++++------------------- src/value.rs | 13 +- 5 files changed, 548 insertions(+), 283 deletions(-) diff --git a/src/de.rs b/src/de.rs index 8d0a0ba..201ac82 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,12 +1,10 @@ use std::path::Path; -use serde::de; - -use crate::{Error, error::Result}; +use super::error::{Error, Result}; pub fn from_iter(iter: Iter) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, Iter: IntoIterator, { from_iter_inner::(None, iter) @@ -14,18 +12,17 @@ where pub fn from_iter_inner(prefix: Option<&str>, iter: Iter) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, Iter: IntoIterator, { match prefix { - Some(pref) => { - envy::prefixed(pref.to_uppercase()).from_iter::<_, T>(iter) - } + Some(pref) => envy::prefixed(pref.to_uppercase()).from_iter::<_, T>(iter), None => { // No prefix provided, use default behavior envy::from_iter::<_, T>(iter) } - }.map_err(Error::new) + } + .map_err(Error::new) } /// Deserialize program-available environment variables into an instance of type `T`. @@ -45,32 +42,26 @@ where /// user: String, /// } /// -/// fn from_env_example() -> Result<(), Error> { -/// let t: Test = from_env()?; -/// println!("{:?}", t); -/// -/// Ok(()) -/// } +/// let value = from_env::().expect("Failed to deserialize from environment"); +/// +/// println!("{:?}", value); /// ``` pub fn from_env() -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, { from_env_inner(None) } pub fn from_env_inner(prefix: Option<&str>) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, { match prefix { - Some(pref) => { - envy::prefixed(pref.to_uppercase()).from_env::() - } - None => { - envy::from_env::() - } - }.map_err(Error::new) + Some(pref) => envy::prefixed(pref.to_uppercase()).from_env::(), + None => envy::from_env::(), + } + .map_err(Error::new) } /// Deserialize environment variables from a string into an instance of type `T`. @@ -80,24 +71,21 @@ where /// ``` /// use serde_envfile::{from_str, Value, Error}; /// -/// fn from_str_example() -> Result<(), Error> { -/// let e = "HELLO=world"; -/// let v: Value = from_str(e)?; -/// println!("{:?}", v); -/// -/// Ok(()) -/// } +/// let env = "HELLO=world"; +/// let value = from_str::(env).expect("Failed to deserialize from string"); +/// +/// println!("{:?}", value); /// ``` pub fn from_str(input: &str) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, { from_str_inner::(None, input) } pub fn from_str_inner<'a, T>(prefix: Option<&'a str>, input: &'a str) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, { let mut env = Vec::new(); @@ -109,37 +97,69 @@ where from_iter_inner::(prefix, env) } -/// Deserialize an environment variable file into an instance of type `T`. +/// Deserialize environment variables from a reader into an instance of type `T`. /// /// # Example /// /// ``` -/// use std::path::PathBuf; -/// use serde_envfile::{from_file, Value, Error}; +/// use std::io::Cursor; +/// use serde_envfile::{from_reader, Value, Error}; /// -/// fn from_file_example() -> Result<(), Error> { -/// let v: Value = from_file(&PathBuf::from(".env"))?; -/// println!("{:?}", v); +/// let input = Cursor::new("HELLO=world"); +/// let value = from_reader::<_, Value>(input).expect("Envfile deserialization failed"); /// -/// Ok(()) -/// } +/// println!("{:?}", value); +/// ``` +pub fn from_reader(reader: R) -> Result +where + R: std::io::Read, + T: serde::de::DeserializeOwned, +{ + from_reader_inner(None, reader) +} + +pub(crate) fn from_reader_inner(prefix: Option<&str>, reader: R) -> Result +where + R: std::io::Read, + T: serde::de::DeserializeOwned, +{ + let mut env = Vec::new(); + + for pair in dotenvy::from_read_iter(reader) { + let (key, value) = pair.map_err(Error::new)?; + env.push((key, value)); + } + + from_iter_inner::(prefix, env) +} + +/// Deserialize an environment variable file into an instance of type `T`. +/// +/// # Example +/// +/// ```no_run +/// use std::path::PathBuf; +/// use serde_envfile::{Value, from_file}; +/// +/// let path = PathBuf::from(".env"); +/// let value = from_file::(&path).expect("Failed to deserialize from file"); +/// +/// println!("{:?}", value); /// ``` pub fn from_file(path: &Path) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, { from_file_inner(None, path) } pub fn from_file_inner(prefix: Option<&str>, path: &Path) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, { let mut env = Vec::new(); - for pair in dotenvy::from_filename_iter(path) - .map_err(Error::new)? - { + for pair in dotenvy::from_filename_iter(path).map_err(Error::new)? { let (key, value) = pair.map_err(Error::new)?; env.push((key, value)); } @@ -152,12 +172,12 @@ mod tests { use std::{ env::{set_var, vars}, fs::write, - io::{SeekFrom, prelude::*}, + io::{Cursor, Seek, SeekFrom}, }; use tempfile::NamedTempFile; - use super::*; + use super::{from_env, from_file, from_reader, from_str}; use crate::Value; #[test] @@ -184,6 +204,21 @@ mod tests { assert_eq!("world", env.get("hello").unwrap()); } + #[test] + fn deserialize_from_reader() { + //* Given + let input = "HELLO=world"; + + let reader = Cursor::new(input); + + //* When + let env: Value = from_reader(reader).expect("Failed to deserialize from reader"); + + //* Then + assert_eq!(env.len(), 1); + assert_eq!("world", env.get("hello").unwrap()); + } + #[test] fn from_file_test() { let input = "HELLO=world"; @@ -191,7 +226,7 @@ mod tests { write(file.path(), input).unwrap(); file.seek(SeekFrom::Start(0)).unwrap(); - let env: Value = from_file(file.path()).unwrap(); + let env: Value = from_file(file.path()).expect("Failed to deserialize from file"); assert_eq!(env.len(), 1); assert_eq!("world", env.get("hello").unwrap()); diff --git a/src/lib.rs b/src/lib.rs index 12875ef..641aae2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,21 +51,12 @@ pub(crate) mod prefixed; pub(crate) mod ser; pub(crate) mod value; +pub use de::{from_env, from_file, from_reader, from_str}; pub use error::Error; - -pub use de::from_env; -pub use de::from_file; -pub use de::from_str; - -pub use ser::Serializer; -pub use ser::to_file; -pub use ser::to_string; - +pub use prefixed::{Prefixed, prefixed}; +pub use ser::{Serializer, to_file, to_string, to_writer}; pub use value::Value; -pub use prefixed::Prefixed; -pub use prefixed::prefixed; - #[cfg(test)] mod tests { use crate::{Value, from_str, to_string}; diff --git a/src/prefixed.rs b/src/prefixed.rs index 6c36d3d..6525868 100644 --- a/src/prefixed.rs +++ b/src/prefixed.rs @@ -1,85 +1,216 @@ use std::path::Path; -use crate::de::{from_env_inner, from_file_inner, from_str_inner}; -use crate::error::Result; -use crate::ser::{to_file_inner, to_string_inner}; -use serde::{de, ser}; +use super::{ + de::{from_env_inner, from_file_inner, from_reader_inner, from_str_inner}, + error::Result, + ser::{to_file_inner, to_string_inner, to_writer_inner}, +}; -/// Helper structure to work with prefixes more efficiently. -/// Instantiable through [prefixed]. +/// Instantiates [`Prefixed`] from which values can be both serialized and deserialized with a prefix. +/// +/// The prefix is added to all keys during serialization and is expected to be present during deserialization. +/// This is useful for namespacing environment variables to avoid conflicts. +/// +/// # Examples +/// +/// ## Serializing with a prefix +/// +/// ``` +/// use serde::{Serialize}; +/// use serde_envfile::{prefixed, Error}; +/// +/// #[derive(Serialize)] +/// struct Config { +/// database_url: String, +/// port: u16, +/// } +/// +/// fn main() -> Result<(), Error> { +/// let config = Config { +/// database_url: "postgres://localhost/mydb".to_string(), +/// port: 8080, +/// }; +/// +/// // Serialize with "APP_" prefix +/// let env_string = prefixed("APP_").to_string(&config)?; +/// // Results in: APP_DATABASE_URL="postgres://localhost/mydb"\nAPP_PORT="8080" +/// +/// println!("{}", env_string); +/// Ok(()) +/// } +/// ``` +/// +/// ## Deserializing with a prefix +/// +/// ``` +/// use serde::{Deserialize}; +/// use serde_envfile::{prefixed, Error}; +/// +/// #[derive(Deserialize, Debug)] +/// struct Config { +/// database_url: String, +/// port: u16, +/// } +/// +/// fn main() -> Result<(), Error> { +/// let env_string = "APP_DATABASE_URL=\"postgres://localhost/mydb\"\nAPP_PORT=\"8080\""; +/// +/// // Deserialize with "APP_" prefix +/// let config: Config = prefixed("APP_").from_str(env_string)?; +/// +/// assert_eq!(config.database_url, "postgres://localhost/mydb"); +/// assert_eq!(config.port, 8080); +/// +/// Ok(()) +/// } +/// ``` +pub fn prefixed(prefix: &str) -> Prefixed { + Prefixed(prefix) +} + +/// Helper structure to work with prefixed environment variables more efficiently. +/// +/// This struct provides methods for serializing and deserializing data with a consistent prefix. +/// Use the [`prefixed`] function to create an instance of this struct. pub struct Prefixed<'a>(&'a str); impl<'a> Prefixed<'a> { pub fn from_env(&self) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, { from_env_inner::(Some(self.0)) } pub fn from_str(&self, input: &'a str) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, { from_str_inner::(Some(self.0), input) } + pub fn from_reader(&self, reader: R) -> Result + where + R: std::io::Read, + T: serde::de::DeserializeOwned, + { + from_reader_inner::(Some(self.0), reader) + } + pub fn from_file(&self, path: &Path) -> Result where - T: de::DeserializeOwned, + T: serde::de::DeserializeOwned, { from_file_inner::(Some(self.0), path) } pub fn to_string(&self, v: &T) -> Result where - T: ser::Serialize, + T: serde::ser::Serialize, { to_string_inner(Some(self.0), v) } - pub fn to_file(&self, path: &Path, v: &T) -> Result<()> + pub fn to_writer(&self, writer: W, v: &T) -> Result<()> where - T: ser::Serialize, + W: std::io::Write, + T: serde::ser::Serialize, { - to_file_inner(Some(self.0), path, v) + to_writer_inner(Some(self.0), writer, v) } -} -/// Instantiates [Prefixed] from which values can be both serialized and deserialized. -pub fn prefixed(prefix: &str) -> Prefixed { - Prefixed(prefix) + pub fn to_file(&self, path: P, v: &T) -> Result<()> + where + P: AsRef, + T: serde::ser::Serialize, + { + to_file_inner(Some(self.0), path, v) + } } #[cfg(test)] mod tests { - use serde::Deserialize; - - use super::*; + use std::io::Cursor; + use super::prefixed; use crate::Value; #[test] - fn ser_prefix_test() { - let mut v = Value::new(); - v.insert("hello".into(), "world".into()); + fn serialize_to_string_with_prefix() { + //* Given + let value = Value::from_iter([("hello", "world")]); - let s = prefixed("serde_envfile_").to_string(&v).unwrap(); - let expected = "SERDE_ENVFILE_HELLO=\"world\""; - assert_eq!(expected, s); + //* When + let output = prefixed("serde_envfile_") + .to_string(&value) + .expect("Failed to serialize"); + + //* Then + let expected_output = "SERDE_ENVFILE_HELLO=\"world\""; + assert_eq!(output, expected_output); + } + + #[test] + fn deserilize_from_str_with_prefix() { + //* Given + #[derive(Debug, PartialEq, serde::Deserialize)] + struct Config { + hello: String, + } + + let env = "SERDE_ENVFILE_HELLO=\"world\""; + + //* When + let output = prefixed("serde_envfile_") + .from_str::(env) + .expect("Failed to deserialize"); + + //* Then + let expected_output = Config { + hello: String::from("world"), + }; + assert_eq!(output, expected_output); } - #[derive(Deserialize, Debug, PartialEq)] - struct Config { - hello: String, + #[test] + fn serialize_to_writer_with_prefix() { + //* Given + let value = Value::from_iter([("hello", "world")]); + + let mut buffer = Vec::new(); + + //* When + prefixed("serde_envfile_") + .to_writer(&mut buffer, &value) + .expect("Failed to serialize to writer"); + + //* Then + let expected_output = "SERDE_ENVFILE_HELLO=\"world\""; + let output = String::from_utf8(buffer).expect("Invalid UTF-8 sequence"); + assert_eq!(output, expected_output); } #[test] - fn de_prefix_test() { + fn deserialize_from_reader_with_prefix() { + //* Given + #[derive(Debug, PartialEq, serde::Deserialize)] + struct Config { + hello: String, + } + let env = "SERDE_ENVFILE_HELLO=\"world\""; - let v = prefixed("serde_envfile_").from_str::(env).unwrap(); - let mut expected = Value::new(); - expected.insert("hello".into(), "world".into()); - assert_eq!(expected, v); + + let reader = Cursor::new(env); + + //* When + let output = prefixed("serde_envfile_") + .from_reader::<_, Config>(reader) + .expect("Failed to deserialize from reader"); + + //* Then + let expected_output = Config { + hello: String::from("world"), + }; + assert_eq!(output, expected_output); } } diff --git a/src/ser.rs b/src/ser.rs index 771b057..a3e24e2 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,12 +1,18 @@ -use crate::error::{Error, Result}; -#[cfg(feature = "debug")] -use log::debug; -use serde::{Serialize, ser}; -use std::{fs::write, path::Path}; - -#[cfg(not(feature = "debug"))] -macro_rules! debug { - ($fmt:expr $(, $arg:expr)*) => {}; +use std::{fs::File, path::Path}; + +use serde::ser::Serialize as _; + +use super::error::{Error, Result}; + +cfg_if::cfg_if! { + if #[cfg(feature = "debug")] { + use log::debug; + } else { + #[allow(unused_macros)] + macro_rules! debug { + ($fmt:expr $(, $arg:expr)*) => {}; + } + } } /// A serializer to transform Rust data into environment variables. @@ -43,28 +49,23 @@ impl Serializer { /// # Example /// /// ``` -/// use serde_envfile::{Error, Value, to_string}; +/// use serde_envfile::{Value, to_string}; /// -/// fn to_string_example() -> Result<(), Error> { -/// let mut value = Value::new(); -/// value.insert("KEY".into(), "VALUE".into()); -/// -/// let value: String = to_string(&value)?; -/// println!("{}", value); +/// let value = Value::from_iter([("KEY", "VALUE")]); /// -/// Ok(()) -/// } +/// let value = to_string(&value).expect("Failed to serialize to string"); +/// println!("{}", value); /// ``` pub fn to_string(v: &T) -> Result where - T: ser::Serialize, + T: serde::ser::Serialize, { to_string_inner(None, v) } pub fn to_string_inner(prefix: Option<&str>, v: &T) -> Result where - T: ser::Serialize, + T: serde::ser::Serialize, { let mut serializer = Serializer::new(prefix); v.serialize(&mut serializer)?; @@ -72,38 +73,71 @@ where Ok(serializer.output) } -/// Serialize data into an environment variable file. +/// Serialize data to a writer that implements `std::io::Write`. /// /// # Example /// /// ``` +/// use std::io::Cursor; +/// use serde_envfile::{Value, to_writer}; +/// +/// let value = Value::from_iter([("KEY", "VALUE")]); +/// +/// let mut writer = Cursor::new(Vec::new()); +/// to_writer(&mut writer, &value).expect("Failed to serialize to writer"); +/// +/// let output = String::from_utf8(writer.into_inner()).expect("Invalid UTF-8 sequence"); +/// println!("{}", output); +/// ``` +pub fn to_writer(writer: W, v: &T) -> Result<()> +where + W: std::io::Write, + T: serde::ser::Serialize, +{ + to_writer_inner(None, writer, v) +} + +pub(crate) fn to_writer_inner(prefix: Option<&str>, mut writer: W, v: &T) -> Result<()> +where + W: std::io::Write, + T: serde::ser::Serialize, +{ + writer + .write_all(to_string_inner(prefix, v)?.as_bytes()) + .map_err(Error::new) +} + +/// Serialize data into an environment variable file. +/// +/// # Example +/// +/// ```no_run /// use std::path::PathBuf; -/// use serde_envfile::{Error, Value, to_file}; +/// use serde_envfile::{Value, to_file}; /// -/// fn to_string_example() -> Result<(), Error> { -/// let mut value = Value::new(); -/// value.insert("KEY".into(), "VALUE".into()); -/// -/// to_file(&PathBuf::from(".env"), &value)?; +/// let value = Value::from_iter([("KEY", "VALUE")]); +/// let path = PathBuf::from(".env"); /// -/// Ok(()) -/// } +/// to_file(&path, &value).expect("Failed to serialize to file"); /// ``` -pub fn to_file(p: &Path, v: &T) -> Result<()> +pub fn to_file(path: P, v: &T) -> Result<()> where - T: ser::Serialize, + P: AsRef, + T: serde::ser::Serialize, { - to_file_inner(None, p, v) + to_file_inner(None, path, v) } -pub fn to_file_inner(prefix: Option<&str>, p: &Path, v: &T) -> Result<()> +pub fn to_file_inner(prefix: Option<&str>, path: P, v: &T) -> Result<()> where - T: ser::Serialize, + P: AsRef, + T: serde::ser::Serialize, { - write(p, to_string_inner(prefix, v)?).map_err(|e| Error::Message(e.to_string())) + let file = File::create(path).map_err(Error::new)?; + to_writer_inner(prefix, file, v) } -impl ser::Serializer for &mut Serializer { +impl serde::ser::Serializer for &mut Serializer { type Ok = (); type Error = Error; @@ -218,7 +252,7 @@ impl ser::Serializer for &mut Serializer { fn serialize_some(self, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serialize some"); value.serialize(self) @@ -229,8 +263,8 @@ impl ser::Serializer for &mut Serializer { Ok(()) } - fn serialize_unit_struct(self, name: &'static str) -> Result<()> { - debug!("serialize unit struct: {}", name); + fn serialize_unit_struct(self, _name: &'static str) -> Result<()> { + debug!("serialize unit struct: {}", _name); self.serialize_unit() } @@ -244,11 +278,11 @@ impl ser::Serializer for &mut Serializer { self.serialize_str(variant) } - fn serialize_newtype_struct(self, name: &'static str, value: &T) -> Result<()> + fn serialize_newtype_struct(self, _name: &'static str, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { - debug!("serialize newtype struct: {}", name); + debug!("serialize newtype struct: {}", _name); value.serialize(self) } @@ -260,7 +294,7 @@ impl ser::Serializer for &mut Serializer { value: &T, ) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serialize newtype struct variant: {}", variant); if self.sequence { @@ -318,30 +352,30 @@ impl ser::Serializer for &mut Serializer { Ok(self) } - fn serialize_struct(self, name: &'static str, len: usize) -> Result { - debug!("serialize struct: {}", name); + fn serialize_struct(self, _name: &'static str, len: usize) -> Result { + debug!("serialize struct: {}", _name); self.serialize_map(Some(len)) } fn serialize_struct_variant( self, - name: &'static str, + _name: &'static str, _variant_index: u32, - variant: &'static str, + _variant: &'static str, len: usize, ) -> Result { - debug!("serialize struct variant: {}/{}", name, variant); + debug!("serialize struct variant: {}/{}", _name, _variant); self.serialize_map(Some(len)) } } -impl ser::SerializeSeq for &mut Serializer { +impl serde::ser::SerializeSeq for &mut Serializer { type Ok = (); type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serializing sequence element"); let r = value.serialize(&mut **self); @@ -358,13 +392,13 @@ impl ser::SerializeSeq for &mut Serializer { } } -impl ser::SerializeTuple for &mut Serializer { +impl serde::ser::SerializeTuple for &mut Serializer { type Ok = (); type Error = Error; fn serialize_element(&mut self, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serialize tuple element"); let r = value.serialize(&mut **self); @@ -381,13 +415,13 @@ impl ser::SerializeTuple for &mut Serializer { } } -impl ser::SerializeTupleStruct for &mut Serializer { +impl serde::ser::SerializeTupleStruct for &mut Serializer { type Ok = (); type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serialize tuple struct field"); let r = value.serialize(&mut **self); @@ -403,13 +437,13 @@ impl ser::SerializeTupleStruct for &mut Serializer { } } -impl ser::SerializeTupleVariant for &mut Serializer { +impl serde::ser::SerializeTupleVariant for &mut Serializer { type Ok = (); type Error = Error; fn serialize_field(&mut self, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serialize tuple variant field"); let r = value.serialize(&mut **self); @@ -425,13 +459,13 @@ impl ser::SerializeTupleVariant for &mut Serializer { } } -impl ser::SerializeMap for &mut Serializer { +impl serde::ser::SerializeMap for &mut Serializer { type Ok = (); type Error = Error; fn serialize_key(&mut self, key: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serialize map key"); serialize_map_struct_key(self, key) @@ -439,7 +473,7 @@ impl ser::SerializeMap for &mut Serializer { fn serialize_value(&mut self, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serialize map value"); serialize_map_struct_value(self, value) @@ -452,13 +486,13 @@ impl ser::SerializeMap for &mut Serializer { } } -impl ser::SerializeStruct for &mut Serializer { +impl serde::ser::SerializeStruct for &mut Serializer { type Ok = (); type Error = Error; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serializing struct field"); @@ -472,13 +506,13 @@ impl ser::SerializeStruct for &mut Serializer { } } -impl ser::SerializeStructVariant for &mut Serializer { +impl serde::ser::SerializeStructVariant for &mut Serializer { type Ok = (); type Error = Error; fn serialize_field(&mut self, key: &'static str, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { debug!("serializing struct variant field"); @@ -494,7 +528,7 @@ impl ser::SerializeStructVariant for &mut Serializer { fn serialize_field(ser: &'_ mut &'_ mut Serializer, key: &'static str, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { serialize_map_struct_key(ser, key)?; serialize_map_struct_value::(ser, value)?; @@ -503,7 +537,7 @@ where fn serialize_map_struct_key(ser: &'_ mut &'_ mut Serializer, key: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { if ser.sequence { return Err(Error::UnsupportedStructureInSeq); @@ -524,7 +558,7 @@ where fn serialize_map_struct_value(ser: &'_ mut &'_ mut Serializer, value: &T) -> Result<()> where - T: ?Sized + ser::Serialize, + T: ?Sized + serde::ser::Serialize, { if ser.sequence { return Err(Error::UnsupportedStructureInSeq); @@ -540,119 +574,130 @@ where #[cfg(test)] mod tests { - use super::*; - use serde::Deserialize; - use std::collections::HashMap; - use std::fs::read_to_string; - use tempfile::NamedTempFile; + use std::{collections::HashMap, io::Cursor}; + use super::{to_file, to_string, to_writer}; use crate::{Value, from_str}; #[test] - fn to_string_test() { - let mut env = Value::new(); - env.insert("HELLO".into(), "WORLD".into()); + fn serialize_to_string_value() { + //* Given + // TODO: Review this test case, the key is "hello" but the expected is "HELLO" + let env = Value::from_iter([("hello", "WORLD")]); - let s = to_string(&env).unwrap(); + //* When + let output = to_string(&env).expect("Failed to serialize to string"); - assert_eq!("HELLO=\"WORLD\"", s); - } - - #[test] - fn to_file_test() { - let mut env = Value::new(); - env.insert("HELLO".into(), "WORLD".into()); - - let file = NamedTempFile::new().unwrap(); - to_file(file.path(), &env).unwrap(); - let s = read_to_string(file.path()).unwrap(); + //* Then + let expected = "HELLO=\"WORLD\""; + assert_eq!(expected, &output); - assert_eq!("HELLO=\"WORLD\"", s); + // Assert the deserialized value is equal to the original value + let deserialized = from_str::(&output).expect("Failed to deserialize to value"); + assert_eq!(deserialized, env); } - #[derive(Debug, Serialize)] - struct StructTestNested { - c: u8, - } + #[test] + fn serialize_to_string_struct() { + //* Given + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct StructTestNested { + c: u8, + } - #[derive(Debug, Serialize)] - struct StructTest { - a: u8, - b: StructTestNested, - } + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct StructTest { + a: u8, + b: StructTestNested, + } - #[test] - fn struct_test() { let env = StructTest { a: 1, b: StructTestNested { c: 2 }, }; - let s = to_string(&env).unwrap(); - - assert_eq!("A=1\nB_C=2", s); - } + //* When + let output = to_string(&env).expect("Failed to serialize to string"); - #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] - struct SeqTest { - a: Vec, - b: String, + //* Then + assert_eq!("A=1\nB_C=2", output); } #[test] - fn seq_test() { + fn serialize_to_string_sequence() { + //* Given + + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct SeqTest { + a: Vec, + b: String, + } + let env = SeqTest { a: vec!["HELLO".into(), "WORLD".into()], b: "control value".into(), }; - let s = to_string(&env).unwrap(); - let expected = "A=\"HELLO\",\"WORLD\"\nB=\"control value\""; - assert_eq!(expected, &s); - assert_eq!(from_str::(expected).unwrap(), env); - } + //* When + let output = to_string(&env).expect("Failed to serialize to string"); - #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] - #[allow(clippy::upper_case_acronyms)] - enum EnumTestEnum { - HELLO, - WORLD, - } + //* Then + let expected = "A=\"HELLO\",\"WORLD\"\nB=\"control value\""; + assert_eq!(expected, &output); - #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] - struct EnumTest { - a: EnumTestEnum, + // Assert the deserialized value is equal to the original value + let deserialized = from_str::(&output).expect("Failed to deserialize to struct"); + assert_eq!(deserialized, env); } #[test] - fn enum_test() { + fn serialize_to_string_enum() { + //* Given + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + enum EnumTestEnum { + HELLO, + WORLD, + } + + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct EnumTest { + a: EnumTestEnum, + } + let env = EnumTest { a: EnumTestEnum::HELLO, }; - let s = to_string(&env).unwrap(); + //* When + let output = to_string(&env).expect("Failed to serialize to string"); + + //* Then let expected = "A=\"HELLO\""; - assert_eq!(expected, &s); - assert_eq!(from_str::(&s).unwrap(), env); - } + assert_eq!(expected, &output); - #[derive(Debug, Serialize, Deserialize, PartialEq)] - struct NumberTest { - u8: u8, - u16: u16, - u32: u32, - u64: u64, - i8: i8, - i16: i16, - i32: i32, - i64: i64, - f32: f32, - f64: f64, - usize: usize, + // Assert the deserialized value is equal to the original value + let deserialized = from_str::(&output).expect("Failed to deserialize to struct"); + assert_eq!(deserialized, env); } #[test] - fn number_test() { + fn serialize_to_string_numbers() { + //* Given + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct NumberTest { + u8: u8, + u16: u16, + u32: u32, + u64: u64, + i8: i8, + i16: i16, + i32: i32, + i64: i64, + f32: f32, + f64: f64, + usize: usize, + } + let env = NumberTest { u8: 255, u16: 65535, @@ -667,82 +712,144 @@ mod tests { usize: 18446744073709551615, }; - let s = to_string(&env).unwrap(); + //* When + let output = to_string(&env).expect("Failed to serialize to string"); + + //* Then let expected = "U8=255\nU16=65535\nU32=4294967295\nU64=18446744073709551615\nI8=-128\nI16=-32768\nI32=-2147483648\nI64=-9223372036854775808\nF32=-3.5\nF64=3.5\nUSIZE=18446744073709551615"; - assert_eq!(expected, &s); - assert_eq!(from_str::(&s).unwrap(), env); - } + assert_eq!(expected, &output); - #[derive(Debug, Serialize, Deserialize, PartialEq)] - struct BoolTest { - a: bool, - b: bool, + // Assert the deserialized value is equal to the original value + let deserialized = from_str::(&output).expect("Failed to deserialize to struct"); + assert_eq!(deserialized, env); } #[test] - fn bool_test() { + fn serialize_to_string_bool() { + //* Given + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct BoolTest { + a: bool, + b: bool, + } + let env = BoolTest { a: true, b: false }; - let s = to_string(&env).unwrap(); + //* When + let output = to_string(&env).expect("Failed to serialize to string"); + + //* Then let expected = "A=true\nB=false"; - assert_eq!(expected, &s); - assert_eq!(from_str::(&s).unwrap(), env); - } + assert_eq!(expected, &output); - #[derive(Debug, Serialize, Deserialize, PartialEq)] - struct OptionTest { - a: Option, - b: Option, + // Assert the deserialized value is equal to the original value + let deserialized = from_str::(&output).expect("Failed to deserialize to struct"); + assert_eq!(deserialized, env); } #[test] - fn option_test() { + fn serialize_to_string_option() { + //* Given + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct OptionTest { + a: Option, + b: Option, + } + let env = OptionTest { a: Some("HELLO".into()), b: None, }; - let s = to_string(&env).unwrap(); - let expected = "A=\"HELLO\"\nB="; - assert_eq!(expected, &s); - assert_eq!(from_str::(&s).unwrap().a, env.a); + //* When + let output = to_string(&env).expect("Failed to serialize to string"); - // envy deserializes "" with Some() - } + //* Then + let expected = "A=\"HELLO\"\nB="; + assert_eq!(expected, &output); - #[derive(Debug, Serialize, Deserialize, PartialEq)] - struct MapTest { - #[serde(flatten)] - inner: HashMap, + // Assert the deserialized value is equal to the original value + // NOTE: envy deserializes "" with Some() + let deserialized = from_str::(&output).expect("Failed to deserialize to struct"); + assert_eq!(deserialized.a, env.a); } #[test] - fn map_test() { + fn serialize_to_string_hashmap() { + //* Given + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct MapTest { + #[serde(flatten)] + inner: HashMap, + } + let mut env = MapTest { inner: HashMap::new(), }; env.inner.insert("hello".into(), "WORLD".into()); - let s = to_string(&env).unwrap(); + //* When + let output = to_string(&env).expect("Failed to serialize to string"); + + //* Then let expected = "HELLO=\"WORLD\""; - assert_eq!(expected, &s); - assert_eq!(from_str::(&s).unwrap(), env); - } + assert_eq!(expected, &output); - #[derive(Debug, Serialize, Deserialize, PartialEq)] - struct NestedMapTest { - inner: HashMap, + // Assert the deserialized value is equal to the original value + let deserialized = from_str::(&output).expect("Failed to deserialize to struct"); + assert_eq!(deserialized, env); } #[test] - fn nested_map_test() { - let mut env = NestedMapTest { - inner: HashMap::new(), + fn serialize_to_string_nested_hashmap() { + //* Given + #[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)] + struct NestedMapTest { + inner: HashMap, + } + + let env = NestedMapTest { + inner: HashMap::from([("HELLO".into(), "WORLD".into())]), }; - env.inner.insert("hello".into(), "WORLD".into()); - let s = to_string(&env).unwrap(); + //* When + let output = to_string(&env).expect("Failed to serialize to string"); + + //* Then let expected = "INNER_HELLO=\"WORLD\""; - assert_eq!(expected, &s); + assert_eq!(expected, &output); + } + + #[test] + fn serialize_to_writer() { + //* Given + let env = Value::from_iter([("HELLO", "WORLD")]); + + let mut writer = Cursor::new(Vec::new()); + + //* When + to_writer(&mut writer, &env).expect("Failed to serialize to writer"); + + //* Then + let expected_output = "HELLO=\"WORLD\""; + let output = String::from_utf8(writer.into_inner()).expect("Invalid UTF-8 sequence"); + assert_eq!(expected_output, output); + } + + #[test] + fn serialize_to_file() { + //* Given + let env = Value::from_iter([("HELLO", "WORLD")]); + + // Create a temp file in the system's temp directory, so if the test fails, the file does not clutter the current directory + let file = tempfile::NamedTempFile::new_in(std::env::temp_dir()).expect("Failed to create temp file"); + + //* When + to_file(&file.path(), &env).expect("Failed to serialize to file"); + + //* Then + let expected_output = "HELLO=\"WORLD\""; + let output = std::fs::read_to_string(file.path()).expect("Failed to read file"); + assert_eq!(expected_output, output); } } diff --git a/src/value.rs b/src/value.rs index 9ead8fb..3130a30 100644 --- a/src/value.rs +++ b/src/value.rs @@ -57,7 +57,7 @@ impl std::ops::DerefMut for Value { } } -impl FromIterator<(K,V)> for Value +impl FromIterator<(K, V)> for Value where K: Into, V: Into, @@ -68,14 +68,14 @@ where /// /// ```rust /// use serde_envfile::Value; - /// + /// /// let env = Value::from_iter([("KEY1", "VALUE1"), ("KEY2", "VALUE2")]); /// # assert_eq!(env.get("KEY1").unwrap(), "VALUE1"); /// # assert_eq!(env.get("KEY2").unwrap(), "VALUE2"); /// ``` /// - fn from_iter>(iter: T) -> Self { - let iter = iter.into_iter().map(|(k,v)| (k.into(), v.into())); + fn from_iter>(iter: T) -> Self { + let iter = iter.into_iter().map(|(k, v)| (k.into(), v.into())); Self(FromIterator::from_iter(iter)) } } @@ -92,7 +92,8 @@ mod tests { //* When let value_serialized = to_string(&env).expect("Failed to convert Value to String"); - let value_deserialized = from_str::(&value_serialized).expect("Failed to deserialize Value"); + let value_deserialized = + from_str::(&value_serialized).expect("Failed to deserialize Value"); //* Then // Assert that both expected lines are present @@ -102,7 +103,7 @@ mod tests { // Assert the deserialize output follows the order of the original input // when the `preserve_order` feature is enabled - #[cfg(feature = "preserve_order")] + #[cfg(feature = "preserve_order")] { let expected_serialized = "KEY1=\"VALUE1\"\nKEY2=\"VALUE2\""; assert_eq!(value_serialized, expected_serialized);