diff --git a/Cargo.lock b/Cargo.lock index cf3424d..8e6259e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,13 +102,13 @@ dependencies = [ ] [[package]] -name = "axum-duper" +name = "axum_duper" version = "0.1.0" dependencies = [ "axum", "serde", - "serde-duper", "serde_core", + "serde_duper", "uuid", ] @@ -1035,34 +1035,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-duper" -version = "0.1.0" -dependencies = [ - "bytes", - "chrono", - "duper", - "ipnet", - "pest", - "regex", - "rust_decimal", - "serde", - "serde-duper-macros", - "serde_bytes", - "serde_core", - "uuid", -] - -[[package]] -name = "serde-duper-macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.106", -] - [[package]] name = "serde_bytes" version = "0.11.19" @@ -1093,6 +1065,34 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "serde_duper" +version = "0.1.0" +dependencies = [ + "bytes", + "chrono", + "duper", + "ipnet", + "pest", + "regex", + "rust_decimal", + "serde", + "serde_bytes", + "serde_core", + "serde_duper_macros", + "uuid", +] + +[[package]] +name = "serde_duper_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.106", +] + [[package]] name = "serde_json" version = "1.0.145" diff --git a/Cargo.toml b/Cargo.toml index 1240380..79b1136 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [workspace] resolver = "3" members = [ - "axum-duper", + "axum_duper", "duper", "duper-py", - "serde-duper", - "serde-duper-macros", + "serde_duper", + "serde_duper_macros", ] diff --git a/axum-duper/Cargo.toml b/axum_duper/Cargo.toml similarity index 68% rename from axum-duper/Cargo.toml rename to axum_duper/Cargo.toml index 377266f..26917f9 100644 --- a/axum-duper/Cargo.toml +++ b/axum_duper/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "axum-duper" +name = "axum_duper" description = "Axum extractor/response for Duper." version = "0.1.0" edition = "2024" @@ -7,9 +7,9 @@ license = "MIT" authors = ["Eric Rodrigues Pires "] [dependencies] -axum = { version = "0.8.6", default-features = false } -serde_core = "1.0.228" -serde-duper = { path = "../serde-duper" } +axum = { version = "0.8", default-features = false } +serde_core = "1" +serde_duper = { path = "../serde_duper" } [dev-dependencies] serde = { version = "1.0.228", features = ["derive"] } diff --git a/axum-duper/LICENSE b/axum_duper/LICENSE similarity index 100% rename from axum-duper/LICENSE rename to axum_duper/LICENSE diff --git a/axum-duper/src/lib.rs b/axum_duper/src/lib.rs similarity index 98% rename from axum-duper/src/lib.rs rename to axum_duper/src/lib.rs index 582b4c6..2e9dcd6 100644 --- a/axum-duper/src/lib.rs +++ b/axum_duper/src/lib.rs @@ -53,7 +53,7 @@ impl IntoResponse for DuperRejection { /// Duper extractor / response. /// /// When used as an extractor, it can deserialize request bodies into some type -/// that implements [`serde::de::DeserializeOwned`]. The request will be +/// that implements [`DeserializeOwned`]. The request will be /// rejected (and a [`DuperRejection`] will be returned) if: /// /// - The request doesn’t have a `Content-Type: application/duper` or @@ -88,7 +88,7 @@ impl IntoResponse for DuperRejection { /// ``` /// /// When used as a response, it can serialize any type that implements -/// [`serde::Serialize`] to `Duper`, and will automatically set the +/// [`Serialize`] to `Duper`, and will automatically set the /// `Content-Type: application/duper` header. /// /// If the [`Serialize`] implementation decides to fail, or if a map with diff --git a/duper.code-workspace b/duper.code-workspace index f0a0165..3341f3b 100644 --- a/duper.code-workspace +++ b/duper.code-workspace @@ -1,22 +1,28 @@ { "folders": [ { - "path": "duper-py" + "name": "axum_duper", + "path": "axum_duper" }, { - "path": "axum-duper" + "name": "duper", + "path": "duper" }, { - "path": "duper" + "name": "duper-py", + "path": "duper-py" }, { + "name": "duper-vs-code", "path": "duper-vs-code" }, { - "path": "serde-duper" + "name": "serde_duper", + "path": "serde_duper" }, { - "path": "serde-duper-macros" + "name": "serde_duper_macros", + "path": "serde_duper_macros" } ], "settings": { diff --git a/duper/Cargo.toml b/duper/Cargo.toml index d8cb556..27ab6ea 100644 --- a/duper/Cargo.toml +++ b/duper/Cargo.toml @@ -16,7 +16,7 @@ owo-colors = { version = "4.2.3", optional = true } pest = { version = "2.8.3", features = ["miette-error"] } pest_derive = "2.8.3" ryu = "1.0.20" -serde_core = { version = "1.0.228", optional = true } +serde_core = { version = "1", optional = true } unicode-general-category = "1.1.0" [dev-dependencies] diff --git a/duper/src/ast.rs b/duper/src/ast.rs index cd93bdd..c8c45b1 100644 --- a/duper/src/ast.rs +++ b/duper/src/ast.rs @@ -1,3 +1,5 @@ +//! Types for interacting with Duper's abstract syntax tree. + use std::{ borrow::Cow, collections::HashMap, @@ -6,64 +8,91 @@ use std::{ use crate::{DuperParser, DuperRule, visitor::DuperVisitor}; +/// A Duper identifier: `MyIdentifier(...)` #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct DuperIdentifier<'a>(pub(crate) Cow<'a, str>); +/// A Duper value. #[derive(Debug, Clone)] pub struct DuperValue<'a> { + /// The identifier of this value. pub identifier: Option>, + /// The actual value contained here. pub inner: DuperInner<'a>, } +/// The value contained within a [`DuperValue`]. #[derive(Debug, Clone, PartialEq)] pub enum DuperInner<'a> { + /// A Duper object: `{...}` Object(DuperObject<'a>), + /// A Duper array: `[...]` Array(DuperArray<'a>), + /// A Duper tuple: `(...)` Tuple(DuperTuple<'a>), + /// A Duper string: `"..."` String(DuperString<'a>), + /// A Duper bytestring: `b"..."` Bytes(DuperBytes<'a>), + /// A Duper integer. Integer(i64), + /// A Duper float. Float(f64), + /// A Duper boolean. Boolean(bool), + /// A Duper null. Null, } +/// A key in a [`DuperObject`]. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct DuperKey<'a>(pub(crate) Cow<'a, str>); +/// An object (or map) from [`DuperKey`]s to [`DuperValue`]s. #[derive(Debug, Clone)] pub struct DuperObject<'a>(pub(crate) Vec<(DuperKey<'a>, DuperValue<'a>)>); +/// An array (or list) of [`DuperValue`]s. #[derive(Debug, Clone, PartialEq)] pub struct DuperArray<'a>(pub(crate) Vec>); +/// An tuple of [`DuperValue`]s. #[derive(Debug, Clone, PartialEq)] pub struct DuperTuple<'a>(pub(crate) Vec>); +/// A string, which may be borrowed or owned. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct DuperString<'a>(pub(crate) Cow<'a, str>); +/// A byte sequence, which may be borrowed or owned. #[derive(Debug, Clone, Hash, PartialEq, Eq)] pub struct DuperBytes<'a>(pub(crate) Cow<'a, [u8]>); +/// Possible errors generated by [`DuperIdentifier::try_from()`]. #[derive(Debug, Clone)] pub enum DuperIdentifierTryFromError<'a> { + /// The identifier was empty. EmptyIdentifier, + /// The identifier contained an invalid character. InvalidChar(Cow<'a, str>, usize), } +/// Possible errors generated by [`DuperObject::try_from()`]. #[derive(Debug, Clone)] pub enum DuperObjectTryFromError<'a> { + /// The key was duplicated. DuplicateKey(Cow<'a, str>), } impl<'a> DuperIdentifier<'a> { + /// Consume this identifier and return the underlying [`Cow<'_, str>`]. pub fn into_inner(self) -> Cow<'a, str> { self.0 } + /// Create a valid identifier from the provided [`Cow<'_, str>`], discarding + /// any invalid characters if necessary. pub fn try_from_lossy(value: Cow<'a, str>) -> Result> { - #[allow(unused_assignments)] let mut new_value = None; let mut chars = value.char_indices(); match chars.next() { @@ -123,6 +152,8 @@ impl<'a> AsRef for DuperIdentifier<'a> { impl<'a> TryFrom> for DuperIdentifier<'a> { type Error = DuperIdentifierTryFromError<'a>; + /// Create a valid identifier from the provided [`Cow<'_, str>`], returning + /// an error if there are invalid characters. fn try_from(value: Cow<'a, str>) -> Result { let mut chars = value.char_indices(); match chars.next() { @@ -166,6 +197,7 @@ impl Display for DuperIdentifierTryFromError<'_> { impl std::error::Error for DuperIdentifierTryFromError<'_> {} impl<'a> DuperKey<'a> { + /// Consume this key and return the underlying [`Cow<'_, str>`]. pub fn into_inner(self) -> Cow<'a, str> { self.0 } @@ -184,6 +216,7 @@ impl<'a> From> for DuperKey<'a> { } impl<'a> DuperValue<'a> { + /// Accepts a [`DuperVisitor`] and visits it with the current value. pub fn accept(&self, visitor: &mut V) -> V::Value { match &self.inner { DuperInner::Object(object) => visitor.visit_object(self.identifier.as_ref(), object), @@ -218,18 +251,23 @@ impl<'a> PartialEq for DuperValue<'a> { } impl<'a> DuperObject<'a> { + /// Consume this object and return the underlying [`Vec<(DuperKey<'_>, DuperValue<'_>)>`]. pub fn into_inner(self) -> Vec<(DuperKey<'a>, DuperValue<'a>)> { self.0 } + /// Returns `true` if the object contains no elements. pub fn is_empty(&self) -> bool { self.0.is_empty() } + /// Returns the amount of elements in this object. pub fn len(&self) -> usize { self.0.len() } + /// Returns an iterator over references to the (key, value) pairs in this + /// object. pub fn iter(&self) -> impl Iterator, DuperValue<'a>)> { self.0.iter() } @@ -238,6 +276,8 @@ impl<'a> DuperObject<'a> { impl<'a> TryFrom, DuperValue<'a>)>> for DuperObject<'a> { type Error = DuperObjectTryFromError<'a>; + /// Create a valid object from the provided [`Vec`], returning an error if + /// a duplicate key is found. fn try_from(value: Vec<(DuperKey<'a>, DuperValue<'a>)>) -> Result { let mut keys = std::collections::HashSet::with_capacity(value.len()); for (key, _) in value.iter() { @@ -283,22 +323,27 @@ impl Display for DuperObjectTryFromError<'_> { impl std::error::Error for DuperObjectTryFromError<'_> {} impl<'a> DuperArray<'a> { + /// Consume this array and return the underlying [`Vec>`]. pub fn into_inner(self) -> Vec> { self.0 } + /// Returns `true` if the array contains no elements. pub fn is_empty(&self) -> bool { self.0.is_empty() } + /// Returns the amount of elements in this array. pub fn len(&self) -> usize { self.0.len() } + /// Returns an iterator over references to the values in this array. pub fn iter(&self) -> impl Iterator> { self.0.iter() } + /// Returns a reference to the value at the given position. pub fn get(&self, index: usize) -> Option<&DuperValue<'a>> { self.0.get(index) } @@ -311,22 +356,27 @@ impl<'a> From>> for DuperArray<'a> { } impl<'a> DuperTuple<'a> { + /// Consume this tuple and return the underlying [`Vec>`]. pub fn into_inner(self) -> Vec> { self.0 } + /// Returns `true` if the tuple contains no elements. pub fn is_empty(&self) -> bool { self.0.is_empty() } + /// Returns the amount of elements in this tuple. pub fn len(&self) -> usize { self.0.len() } + /// Returns an iterator over references to the values in this tuple. pub fn iter(&self) -> impl Iterator> { self.0.iter() } + /// Returns a reference to the value at the given position. pub fn get(&self, index: usize) -> Option<&DuperValue<'a>> { self.0.get(index) } @@ -339,6 +389,7 @@ impl<'a> From>> for DuperTuple<'a> { } impl<'a> DuperString<'a> { + /// Consume this string and return the underlying [`Cow<'_, str>`]. pub fn into_inner(self) -> Cow<'a, str> { self.0 } @@ -357,6 +408,7 @@ impl<'a> AsRef for DuperString<'a> { } impl<'a> DuperBytes<'a> { + /// Consume these bytes and return the underlying [`Cow<'_, [u8]>`]. pub fn into_inner(self) -> Cow<'a, [u8]> { self.0 } diff --git a/duper/src/format.rs b/duper/src/format.rs index 2fd8c4c..ff2fd97 100644 --- a/duper/src/format.rs +++ b/duper/src/format.rs @@ -44,7 +44,7 @@ fn format_cow_str<'a>(string: &Cow<'a, str>) -> Cow<'a, str> { '#' if was_quotes => { was_hashtag = true; was_quotes = false; - curr_hashtags = 1; + curr_hashtags = 2; max_hashtags = max_hashtags.max(curr_hashtags); } ' ' => { @@ -104,7 +104,7 @@ pub(crate) fn format_duper_bytes<'a>(bytes: &'a DuperBytes<'a>) -> Cow<'a, str> b'#' if was_quotes => { was_hashtag = true; was_quotes = false; - curr_hashtags = 1; + curr_hashtags = 2; max_hashtags = max_hashtags.max(curr_hashtags); } b' ' => { diff --git a/duper/src/lib.rs b/duper/src/lib.rs index 22c81a9..ef9827c 100644 --- a/duper/src/lib.rs +++ b/duper/src/lib.rs @@ -4,20 +4,47 @@ //! //! Duper aims to be a human-friendly extension of JSON with quality-of-life improvements, extra types, and semantic identifiers. //! +//! ```duper +//! Product({ +//! product_id: Uuid("1dd7b7aa-515e-405f-85a9-8ac812242609"), +//! name: "Wireless Bluetooth Headphones", +//! brand: "AudioTech", +//! price: Decimal("129.99"), +//! dimensions: (18.5, 15.2, 7.8), // In centimeters +//! weight: Kilograms(0.285), +//! in_stock: true, +//! specifications: { +//! battery_life: Duration("30h"), +//! noise_cancellation: true, +//! connectivity: ["Bluetooth 5.0", "3.5mm Jack"], +//! }, +//! image_thumbnail: Png(b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x64"), +//! tags: ["electronics", "audio", "wireless"], +//! release_date: Date("2023-11-15"), +//! /* Warranty is optional */ +//! warranty_period: null, +//! customer_ratings: { +//! latest_review: r#"Absolutely ""astounding""!! 😎"#, +//! average: 4.5, +//! count: 127, +//! }, +//! created_at: DateTime("2023-11-17T21:50:43+00:00"), +//! }) +//! ``` +//! //! ## Feature flags //! //! - `ansi`: Enables the [`Ansi`] module for printing Duper values to a console. -//! - `serde`: Enables [`serde`] serialization/deserialization for [`DuperValue`]. +//! - `serde`: Enables `serde` serialization/deserialization for [`DuperValue`]. //! -mod ast; +pub mod ast; mod builder; mod escape; mod format; mod parser; #[cfg(feature = "serde")] mod serde; -pub mod types; pub mod visitor; pub use ast::{ diff --git a/duper/src/parser.rs b/duper/src/parser.rs index 3aec884..7688efb 100644 --- a/duper/src/parser.rs +++ b/duper/src/parser.rs @@ -4,14 +4,41 @@ use crate::{ast::DuperValue, builder::DuperBuilder}; #[derive(pest_derive::Parser)] #[grammar = "grammar.pest"] +/// The [`pest`]-based parser for Duper. pub struct DuperParser; impl DuperParser { + /// Parse a Duper trunk, i.e. only an array or object at the top level. + /// + /// You can map the error into a formatted `miette::Error` as follows: + /// + /// ``` + /// use duper::DuperParser; + /// + /// # let input = "{}"; + /// let duper = match DuperParser::parse_duper_trunk(input) { + /// Ok(duper) => duper, + /// Err(error) => panic!("{:?}", miette::Error::new(error.into_miette())), + /// }; + /// ``` pub fn parse_duper_trunk(input: &'_ str) -> Result, Box>> { let mut pairs = Self::parse(Rule::duper, input)?; DuperBuilder::build_duper_trunk(pairs.next().unwrap()) } + /// Parse a Duper value. + /// + /// You can map the error into a formatted `miette::Error` as follows: + /// + /// ``` + /// use duper::DuperParser; + /// + /// # let input = "{}"; + /// let duper = match DuperParser::parse_duper_value(input) { + /// Ok(duper) => duper, + /// Err(error) => panic!("{:?}", miette::Error::new(error.into_miette())), + /// }; + /// ``` pub fn parse_duper_value(input: &'_ str) -> Result, Box>> { let mut pairs = Self::parse(Rule::duper_value, input)?; DuperBuilder::build_duper_value(pairs.next().unwrap()) diff --git a/duper/src/serde.rs b/duper/src/serde.rs index 670c7e9..3e13111 100644 --- a/duper/src/serde.rs +++ b/duper/src/serde.rs @@ -5,6 +5,8 @@ use crate::{ use serde_core::de::{VariantAccess, Visitor}; use std::borrow::Cow; +struct DuperValueDeserializerVisitor; + impl<'a> serde_core::Serialize for DuperValue<'a> { fn serialize(&self, serializer: S) -> Result where @@ -54,8 +56,6 @@ impl<'a> serde_core::Serialize for DuperInner<'a> { } } -struct DuperValueDeserializerVisitor; - impl<'de> Visitor<'de> for DuperValueDeserializerVisitor { type Value = DuperValue<'de>; diff --git a/duper/src/visitor/ansi.rs b/duper/src/visitor/ansi.rs index d763ae7..8f7a4d7 100644 --- a/duper/src/visitor/ansi.rs +++ b/duper/src/visitor/ansi.rs @@ -1,3 +1,5 @@ +//! Utilities for generating ANSI sequences from Duper values. + use std::io::{Error, Write}; use crate::{ @@ -12,25 +14,44 @@ use crate::{ }; use owo_colors::{AnsiColors, DynColors, OwoColorize}; +/// A Duper visitor which generates colored ANSI escaping. pub struct Ansi<'ansi> { strip_identifiers: bool, theme: &'ansi AnsiTheme<'ansi>, bracket_depth: usize, } +/// A struct representing a theme from whose colors the [`Ansi`] visitor will +/// use. #[derive(Debug, Clone)] pub struct AnsiTheme<'theme> { - identifier: DynColors, - key: DynColors, - string: DynColors, - bytes: DynColors, - integer: DynColors, - float: DynColors, - boolean: DynColors, - null: DynColors, - brackets: &'theme [DynColors], + /// Duper identifiers: `Identifier(...)` + pub identifier: DynColors, + /// Duper keys: `{foo: ..., "bar": ...}` + pub key: DynColors, + /// Duper strings: `("Hello", r"#world")` + pub string: DynColors, + /// Duper bytes: `(b"Hello", br"#world")` + pub bytes: DynColors, + /// Duper integers: `(42, 0xdeadbeef)` + pub integer: DynColors, + /// Duper floats: `(2.17, 3.5e50)` + pub float: DynColors, + /// Duper booleans: `(true, false)` + pub boolean: DynColors, + /// Duper null: `null` + pub null: DynColors, + /// Brackets (for identifiers, arrays, tuples, and objects): `Id([({...})])` + /// + /// By default, [`Ansi`] will iterate over brackets, matching their colors + /// and looping around when the slice is exhausted. + /// + /// An empty slice will disable coloring of brackets. + pub brackets: &'theme [DynColors], } +/// A theme using standard ANSI colors. This is the theme used in +/// [`Default::default()`]. pub static ANSI_THEME: &AnsiTheme = &AnsiTheme { identifier: DynColors::Ansi(AnsiColors::BrightBlue), key: DynColors::Ansi(AnsiColors::BrightCyan), @@ -47,6 +68,7 @@ pub static ANSI_THEME: &AnsiTheme = &AnsiTheme { ], }; +/// A theme using the colors for VSCode's Dark+ theme. pub static VSCODE_DARK_PLUS_THEME: &AnsiTheme = &AnsiTheme { identifier: DynColors::Rgb(0x4E, 0xC9, 0xB0), key: DynColors::Rgb(0x9C, 0xDC, 0xFE), @@ -76,6 +98,8 @@ impl Default for Ansi<'static> { } impl<'ansi> Ansi<'ansi> { + /// Create a new [`Ansi`] visitor with the provided option and desired + /// theme. pub fn new(strip_identifiers: bool, theme: &'ansi AnsiTheme) -> Self { Self { strip_identifiers, @@ -84,6 +108,7 @@ impl<'ansi> Ansi<'ansi> { } } + /// Convert the [`DuperValue`] into a [`Vec`] of bytes. pub fn to_ansi<'a>(&mut self, value: DuperValue<'a>) -> Result, Error> { value.accept(self) } diff --git a/duper/src/visitor/mod.rs b/duper/src/visitor/mod.rs index 5ae0a9c..6b17288 100644 --- a/duper/src/visitor/mod.rs +++ b/duper/src/visitor/mod.rs @@ -1,3 +1,5 @@ +//! Utilities for using and implementing your own [`DuperVisitor`]. + #[cfg(feature = "ansi")] pub mod ansi; pub mod pretty_printer; @@ -5,56 +7,154 @@ pub mod serializer; use crate::ast::{DuperArray, DuperBytes, DuperIdentifier, DuperObject, DuperString, DuperTuple}; +/// A trait for implementing a Duper visitor. You can visit a `DuperValue` +/// with `value.accept(&mut visitor)`. +/// +/// # Example +/// +/// ``` +/// use duper::{ +/// DuperArray, DuperBytes, DuperIdentifier, DuperObject, DuperString, +/// DuperTuple, visitor::DuperVisitor, +/// }; +/// +/// struct MyVisitor; +/// +/// impl DuperVisitor for MyVisitor { +/// type Value = (); +/// +/// fn visit_object<'a>( +/// &mut self, +/// identifier: Option<&DuperIdentifier<'a>>, +/// object: &DuperObject<'a>, +/// ) -> Self::Value { +/// println!("object with identifier: {:?}", identifier); +/// for (key, value) in object.iter() { +/// print!("-> {:?}: ", key); +/// value.accept(self); +/// } +/// } +/// +/// fn visit_array<'a>( +/// &mut self, +/// identifier: Option<&DuperIdentifier<'a>>, +/// array: &DuperArray<'a>, +/// ) -> Self::Value { +/// println!("array with identifier: {:?}", identifier); +/// for value in array.iter() { +/// print!("-> "); +/// value.accept(self); +/// } +/// } +/// +/// // ... Same for the remaining methods ... +/// # +/// # fn visit_tuple<'a>( +/// # &mut self, +/// # identifier: Option<&DuperIdentifier<'a>>, +/// # tuple: &DuperTuple<'a>, +/// # ) -> Self::Value {} +/// # +/// # fn visit_string<'a>( +/// # &mut self, +/// # identifier: Option<&DuperIdentifier<'a>>, +/// # string: &DuperString<'a>, +/// # ) -> Self::Value {} +/// # +/// # fn visit_bytes<'a>( +/// # &mut self, +/// # identifier: Option<&DuperIdentifier<'a>>, +/// # bytes: &DuperBytes<'a>, +/// # ) -> Self::Value {} +/// # +/// # fn visit_integer<'a>( +/// # &mut self, +/// # identifier: Option<&DuperIdentifier<'a>>, +/// # integer: i64, +/// # ) -> Self::Value {} +/// # +/// # fn visit_float<'a>( +/// # &mut self, +/// # identifier: Option<&DuperIdentifier<'a>>, +/// # float: f64, +/// # ) -> Self::Value {} +/// # +/// # fn visit_boolean<'a>( +/// # &mut self, +/// # identifier: Option<&DuperIdentifier<'a>>, +/// # boolean: bool, +/// # ) -> Self::Value {} +/// # +/// # fn visit_null<'a>( +/// # &mut self, +/// # identifier: Option<&DuperIdentifier<'a>> +/// # ) -> Self::Value {} +/// } +/// ``` pub trait DuperVisitor { type Value; + /// Visits an object. You can access an iterator of `(key, value)` pairs by + /// calling `object.iter()`. fn visit_object<'a>( &mut self, identifier: Option<&DuperIdentifier<'a>>, object: &DuperObject<'a>, ) -> Self::Value; + /// Visits an array. You can access an iterator of values by calling + /// `array.iter()`. fn visit_array<'a>( &mut self, identifier: Option<&DuperIdentifier<'a>>, array: &DuperArray<'a>, ) -> Self::Value; + /// Visits a tuple. You can access an iterator of values by calling + /// `tuple.iter()`. fn visit_tuple<'a>( &mut self, identifier: Option<&DuperIdentifier<'a>>, tuple: &DuperTuple<'a>, ) -> Self::Value; + /// Visits a string. You can access a `Cow` of a str slice by calling + /// `string.as_ref()`. fn visit_string<'a>( &mut self, identifier: Option<&DuperIdentifier<'a>>, string: &DuperString<'a>, ) -> Self::Value; + /// Visits bytes. You can access a `Cow` of a byte slice by calling + /// `bytes.as_ref()`. fn visit_bytes<'a>( &mut self, identifier: Option<&DuperIdentifier<'a>>, bytes: &DuperBytes<'a>, ) -> Self::Value; + /// Visits an integer. fn visit_integer<'a>( &mut self, identifier: Option<&DuperIdentifier<'a>>, integer: i64, ) -> Self::Value; + /// Visits a floating point number. fn visit_float<'a>( &mut self, identifier: Option<&DuperIdentifier<'a>>, float: f64, ) -> Self::Value; + /// Visits a boolean. fn visit_boolean<'a>( &mut self, identifier: Option<&DuperIdentifier<'a>>, boolean: bool, ) -> Self::Value; + /// Visits null. fn visit_null<'a>(&mut self, identifier: Option<&DuperIdentifier<'a>>) -> Self::Value; } diff --git a/duper/src/visitor/pretty_printer.rs b/duper/src/visitor/pretty_printer.rs index 8bd3b40..bcba605 100644 --- a/duper/src/visitor/pretty_printer.rs +++ b/duper/src/visitor/pretty_printer.rs @@ -1,3 +1,5 @@ +//! Utilities for pretty-printing Duper values. + use crate::{ ast::{ DuperArray, DuperBytes, DuperIdentifier, DuperObject, DuperString, DuperTuple, DuperValue, @@ -9,6 +11,8 @@ use crate::{ visitor::DuperVisitor, }; +/// A Duper visitor which pretty-prints the provided [`DuperValue`] with +/// line breaks, indentation, and trailing commas. pub struct PrettyPrinter<'pp> { strip_identifiers: bool, curr_indent: usize, @@ -26,12 +30,14 @@ impl Default for PrettyPrinter<'static> { } impl<'pp> PrettyPrinter<'pp> { + /// Create a new [`PrettyPrinter`] visitor with the provided option and + /// desired indentation. pub fn new(strip_identifiers: bool, indent: &'pp str) -> Result { if indent.is_empty() { return Err("Indentation cannot be empty"); } if indent.chars().any(|char| char != ' ' && char != '\t') { - return Err("Indentation may only consist of spaces or tabs"); + return Err("Indentation may only consist of spaces and tabs"); } Ok(Self { strip_identifiers, @@ -40,6 +46,7 @@ impl<'pp> PrettyPrinter<'pp> { }) } + /// Convert the [`DuperValue`] into a pretty-printed [`String`]. pub fn pretty_print<'a>(&mut self, value: DuperValue<'a>) -> String { value.accept(self) } diff --git a/duper/src/visitor/serializer.rs b/duper/src/visitor/serializer.rs index 3c4a1e8..80ec1ee 100644 --- a/duper/src/visitor/serializer.rs +++ b/duper/src/visitor/serializer.rs @@ -1,3 +1,5 @@ +//! Utilities for serializing Duper values. + use crate::{ ast::{ DuperArray, DuperBytes, DuperIdentifier, DuperObject, DuperString, DuperTuple, DuperValue, @@ -9,16 +11,19 @@ use crate::{ visitor::DuperVisitor, }; +/// A Duper visitor which serializes the provided [`DuperValue`]. #[derive(Default)] pub struct Serializer { strip_identifiers: bool, } impl Serializer { + /// Create a new [`Serializer`] visitor with the provided option. pub fn new(strip_identifiers: bool) -> Self { Self { strip_identifiers } } + /// Convert the [`DuperValue`] into a serialized [`String`]. pub fn serialize<'a>(&mut self, value: DuperValue<'a>) -> String { value.accept(self) } diff --git a/serde-duper/Cargo.toml b/serde_duper/Cargo.toml similarity index 84% rename from serde-duper/Cargo.toml rename to serde_duper/Cargo.toml index 09f6f19..41b592f 100644 --- a/serde-duper/Cargo.toml +++ b/serde_duper/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "serde-duper" +name = "serde_duper" description = "Serde support for Duper." version = "0.1.0" edition = "2024" @@ -10,7 +10,7 @@ authors = ["Eric Rodrigues Pires "] default = [] # Macros -macros = ["dep:serde-duper-macros"] +macros = ["dep:serde_duper_macros"] # Type dependencies bytes = ["dep:bytes"] @@ -24,11 +24,11 @@ uuid = ["dep:uuid"] # Core dependencies duper = { path = "../duper", features = ["serde"] } pest = "2.8.3" -serde_bytes = "0.11.19" -serde_core = { version = "1.0.228" } +serde_bytes = "0.11" +serde_core = "1" # Macros -serde-duper-macros = { path = "../serde-duper-macros", optional = true } +serde_duper_macros = { path = "../serde_duper_macros", optional = true } # Type dependencies bytes = { version = "1", features = ["serde"], optional = true } diff --git a/serde-duper-macros/LICENSE b/serde_duper/LICENSE similarity index 100% rename from serde-duper-macros/LICENSE rename to serde_duper/LICENSE diff --git a/serde-duper/src/bytes.rs b/serde_duper/src/bytes.rs similarity index 89% rename from serde-duper/src/bytes.rs rename to serde_duper/src/bytes.rs index b109496..68c36b7 100644 --- a/serde-duper/src/bytes.rs +++ b/serde_duper/src/bytes.rs @@ -1,3 +1,3 @@ -//! Re-export of [`serde_bytes`] for better support of bytes in [`serde-duper`]. +//! Re-export of [`serde_bytes`] for better support of bytes in `serde_duper`. pub use serde_bytes::{ByteArray, ByteBuf, Bytes, deserialize, serialize}; diff --git a/serde-duper/src/de.rs b/serde_duper/src/de.rs similarity index 99% rename from serde-duper/src/de.rs rename to serde_duper/src/de.rs index 283016a..616fe27 100644 --- a/serde-duper/src/de.rs +++ b/serde_duper/src/de.rs @@ -10,6 +10,7 @@ use serde_core::{ use crate::Error; +/// Implementation of a deserializer from a [`DuperValue`]. pub struct Deserializer<'de> { value: Option>, } diff --git a/serde-duper/src/error.rs b/serde_duper/src/error.rs similarity index 96% rename from serde-duper/src/error.rs rename to serde_duper/src/error.rs index 466318d..436da29 100644 --- a/serde-duper/src/error.rs +++ b/serde_duper/src/error.rs @@ -2,13 +2,13 @@ use std::fmt::{self, Display}; use duper::{DuperIdentifierTryFromError, DuperObjectTryFromError}; -#[derive(Debug, Clone)] /// The kinds of errors that can happen during serialization and deserialization. +#[derive(Debug, Clone)] pub enum ErrorKind { /// Parsing failed at the given [`pest`] rule. /// - /// This error implements `.to_miette()` in order to allow generation of a - /// [`miette`] `Report`. + /// This error implements `.to_miette()`, in order to allow generation of a + /// `miette` `Report`. ParseError(Box>), /// Serialization failed with an unspecified error. SerializationError, diff --git a/serde-duper/src/lib.rs b/serde_duper/src/lib.rs similarity index 95% rename from serde-duper/src/lib.rs rename to serde_duper/src/lib.rs index 018c2a7..0793371 100644 --- a/serde-duper/src/lib.rs +++ b/serde_duper/src/lib.rs @@ -32,7 +32,7 @@ //! ``` //! //! This crate allows you to convert between Duper's text representation and -//! Rust's native data types, thanks to the [`serde`] framework. +//! Rust's native data types, thanks to the `serde` framework. //! //! Serde provides a powerful way of mapping Duper data to and from Rust data //! structures largely automatically. @@ -222,7 +222,7 @@ //! // }) //! ``` //! -//! The [`serde_duper::types`] module provides a simple and quick plug-and-play +//! The [`types`] module provides a simple and quick plug-and-play //! way of annotating types from [`std`] (as well as a few popular third-party //! crates behind feature flags) with Duper identifiers. It works by providing //! remote modules that will handle (de)serialization. This is less flexible, @@ -235,6 +235,7 @@ //! ``` //! use serde::{Deserialize, Serialize}; //! use serde_duper::duper; +//! use uuid::Uuid; //! //! #[derive(Serialize, Deserialize)] //! #[serde(rename = "Status")] @@ -248,13 +249,14 @@ //! #[derive(Serialize, Deserialize)] //! struct User { //! #[duper(MyUuid)] -//! id: uuid::Uuid, +//! id: Uuid, //! status: UserStatus, //! #[duper(IpList)] //! last_known_ips: Vec, //! } //! } //! +//! # fn main() { //! let u = User { //! id: "314dfe6f-7a76-4c43-80b9-3b0ceb0960c0".parse().unwrap(), //! status: UserStatus::Enabled, @@ -268,13 +270,14 @@ //! // status: Status("Enabled"), //! // last_known_ips: IpList(["2a02:ec80:700:ed1a::1"]), //! // }) +//! # } //! ``` //! //! This will automatically generate the modules for any type that implements -//! [`serde::Serialize`] and/or [`serde::Deserialize`], not being restricted -//! only to those with a remote (de)serializer module. +//! [`serde_core::Serialize`] and/or [`serde_core::Deserialize`], not being +//! restricted only to those with a remote (de)serializer module. //! -//! This functionality requires the `macros` feature flag. +//! This requires the `macros` feature flag. //! pub mod bytes; diff --git a/serde-duper/src/ser.rs b/serde_duper/src/ser.rs similarity index 100% rename from serde-duper/src/ser.rs rename to serde_duper/src/ser.rs diff --git a/serde-duper/src/types/ffi.rs b/serde_duper/src/types/ffi.rs similarity index 100% rename from serde-duper/src/types/ffi.rs rename to serde_duper/src/types/ffi.rs diff --git a/serde-duper/src/types/mod.rs b/serde_duper/src/types/mod.rs similarity index 99% rename from serde-duper/src/types/mod.rs rename to serde_duper/src/types/mod.rs index e0bc63f..6ffd8f7 100644 --- a/serde-duper/src/types/mod.rs +++ b/serde_duper/src/types/mod.rs @@ -1,8 +1,8 @@ #![allow(non_snake_case)] use serde_core::{Deserialize, Deserializer, Serialize, Serializer, de}; -// -- Helper macro -- - +/// A helper macro to generate boilerplate for most serializable/deserializable +/// types. #[macro_export] macro_rules! duper_serde_module { ( diff --git a/serde-duper/tests/basic.rs b/serde_duper/tests/basic.rs similarity index 100% rename from serde-duper/tests/basic.rs rename to serde_duper/tests/basic.rs diff --git a/serde-duper/tests/macros.rs b/serde_duper/tests/macros.rs similarity index 100% rename from serde-duper/tests/macros.rs rename to serde_duper/tests/macros.rs diff --git a/serde-duper/tests/types_option_none.rs b/serde_duper/tests/types_option_none.rs similarity index 100% rename from serde-duper/tests/types_option_none.rs rename to serde_duper/tests/types_option_none.rs diff --git a/serde-duper/tests/types_option_some.rs b/serde_duper/tests/types_option_some.rs similarity index 100% rename from serde-duper/tests/types_option_some.rs rename to serde_duper/tests/types_option_some.rs diff --git a/serde-duper/tests/types_plain.rs b/serde_duper/tests/types_plain.rs similarity index 100% rename from serde-duper/tests/types_plain.rs rename to serde_duper/tests/types_plain.rs diff --git a/serde-duper-macros/Cargo.toml b/serde_duper_macros/Cargo.toml similarity index 59% rename from serde-duper-macros/Cargo.toml rename to serde_duper_macros/Cargo.toml index e6514b4..a5fa52e 100644 --- a/serde-duper-macros/Cargo.toml +++ b/serde_duper_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "serde-duper-macros" -description = "Macros for serde-duper." +name = "serde_duper_macros" +description = "Macros for serde_duper." version = "0.1.0" edition = "2024" license = "MIT" @@ -10,9 +10,9 @@ authors = ["Eric Rodrigues Pires "] proc-macro = true [dependencies] -proc-macro2 = "1.0.101" -quote = "1.0.41" -syn = { version = "2.0.106", features = ["full"] } +proc-macro2 = "1" +quote = "1" +syn = { version = "2", features = ["full"] } [dev-dependencies] serde = { version = "1.0.228", features = ["derive"] } diff --git a/serde-duper/LICENSE b/serde_duper_macros/LICENSE similarity index 100% rename from serde-duper/LICENSE rename to serde_duper_macros/LICENSE diff --git a/serde-duper-macros/src/lib.rs b/serde_duper_macros/src/lib.rs similarity index 98% rename from serde-duper-macros/src/lib.rs rename to serde_duper_macros/src/lib.rs index 9a6002b..d43c411 100644 --- a/serde-duper-macros/src/lib.rs +++ b/serde_duper_macros/src/lib.rs @@ -1,4 +1,4 @@ -//! Macros for [`serde-duper`]. +//! Macros for `serde_duper`. use proc_macro::TokenStream; use quote::{format_ident, quote}; @@ -29,7 +29,7 @@ use syn::{Attribute, Fields, Ident, Item, ItemStruct, Meta, parse_macro_input}; /// ``` /// /// Upon serializing and deserializing, `id` and `aliases` will be treated as -/// newtype structs. This is useful to add identifiers to Duper values. +/// newtype structs. This is useful for adding identifiers to Duper values. /// pub fn duper(input: TokenStream) -> TokenStream { let item = parse_macro_input!(input as Item);