From b18d1bf0feeb6828dc63b5dd26bddaffbbd81357 Mon Sep 17 00:00:00 2001 From: Eric Rodrigues Pires Date: Mon, 20 Oct 2025 00:17:59 -0300 Subject: [PATCH] Add docs for most modules --- Cargo.lock | 2 + README.md | 12 +- axum-duper/Cargo.toml | 4 + axum-duper/src/lib.rs | 100 ++++++++++- duper-py/python/duper/__init__.py | 2 +- duper-py/python/duper/fastapi.py | 27 +++ duper-py/python/duper/pydantic.py | 27 ++- duper/src/lib.rs | 12 ++ serde-duper-macros/src/lib.rs | 30 +++- serde-duper/src/bytes.rs | 2 + serde-duper/src/de.rs | 98 +++++++++++ serde-duper/src/error.rs | 20 ++- serde-duper/src/lib.rs | 284 +++++++++++++++++++++++++++++- serde-duper/src/ser.rs | 52 ++++-- 14 files changed, 642 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed044b2..cf3424d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,8 +106,10 @@ name = "axum-duper" version = "0.1.0" dependencies = [ "axum", + "serde", "serde-duper", "serde_core", + "uuid", ] [[package]] diff --git a/README.md b/README.md index fb8a1a7..4844662 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # Duper -The format that's super. Duper aims to be a human-friendly extension of JSON with quality-of-life improvements, extra types, and semantic identifiers. +The format that's super. + +Duper aims to be a human-friendly extension of JSON with quality-of-life improvements, extra types, and semantic identifiers. ## A visual introduction in four parts For example, let's assume the following format for some product data in a storefront. -``` +```duper { "product_id": "1dd7b7aa-515e-405f-85a9-8ac812242609", "name": "Wireless Bluetooth Headphones", @@ -37,7 +39,7 @@ Plain ol' JSON. This is a valid Duper object, as well. --- -``` +```duper { product_id: "1dd7b7aa-515e-405f-85a9-8ac812242609", name: "Wireless Bluetooth Headphones", @@ -69,7 +71,7 @@ We can get rid of the quotes for simple keys, use trailing commas, and include c --- -``` +```duper { product_id: "1dd7b7aa-515e-405f-85a9-8ac812242609", name: "Wireless Bluetooth Headphones", @@ -101,7 +103,7 @@ Duper also adds supports for tuples (`(-23.561384, -46.655891)`), bytes (`b"\x1b --- -``` +```duper Product({ product_id: Uuid("1dd7b7aa-515e-405f-85a9-8ac812242609"), name: "Wireless Bluetooth Headphones", diff --git a/axum-duper/Cargo.toml b/axum-duper/Cargo.toml index 9940284..377266f 100644 --- a/axum-duper/Cargo.toml +++ b/axum-duper/Cargo.toml @@ -10,3 +10,7 @@ authors = ["Eric Rodrigues Pires "] axum = { version = "0.8.6", default-features = false } serde_core = "1.0.228" serde-duper = { path = "../serde-duper" } + +[dev-dependencies] +serde = { version = "1.0.228", features = ["derive"] } +uuid = { version = "1.18.1", features = ["serde"] } diff --git a/axum-duper/src/lib.rs b/axum-duper/src/lib.rs index 1c29a2f..582b4c6 100644 --- a/axum-duper/src/lib.rs +++ b/axum-duper/src/lib.rs @@ -1,3 +1,12 @@ +//! # Axum Duper +//! +//! Duper extractor / response for [`axum`]. +//! +//! This crate provides the [`Duper`] struct, which can be used to extract typed +//! information from request's body, or to serialize a structured response. +//! +//! Under the hood, it wraps [`serde_duper`]. + use std::ops::Deref; use axum::{ @@ -8,7 +17,13 @@ use axum::{ use serde_core::{Serialize, de::DeserializeOwned}; use serde_duper::ErrorKind; -#[derive(Debug)] +pub static DUPER_CONTENT_TYPE: &str = "application/duper"; +pub static DUPER_ALT_CONTENT_TYPE: &str = "application/x-duper"; + +/// Rejection used for [`Duper`]. +/// +/// Contains one variant for each way the [`Duper`] extractor can fail. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[non_exhaustive] pub enum DuperRejection { DuperDataError, @@ -17,9 +32,6 @@ pub enum DuperRejection { InternalDuperError, } -pub static DUPER_CONTENT_TYPE: &str = "application/duper"; -pub static DUPER_ALT_CONTENT_TYPE: &str = "application/x-duper"; - impl IntoResponse for DuperRejection { fn into_response(self) -> Response { match self { @@ -38,17 +50,97 @@ 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 +/// rejected (and a [`DuperRejection`] will be returned) if: +/// +/// - The request doesn’t have a `Content-Type: application/duper` or +/// `Content-Type: application/x-duper` header. +/// - The body doesn’t contain a syntactically valid Duper value. +/// - The body contains a syntactically valid Duper value, but it couldn’t be +/// deserialized into the target type. +/// - Buffering the request body fails. +/// +/// Since parsing Duper values requires consuming the request body, the `Duper` +/// extractor must be *last* if there are multiple extractors in a handler. +/// +/// # Extractor example +/// +/// ```rust, no_run +/// use axum::{Router, routing::post}; +/// use axum_duper::Duper; +/// use serde::Deserialize; +/// +/// #[derive(Deserialize)] +/// struct CreateUser { +/// email: String, +/// password: String, +/// } +/// +/// async fn create_user(Duper(payload): Duper) { +/// // payload is a `CreateUser` +/// } +/// +/// let app = Router::new().route("/users", post(create_user)); +/// # let _: Router = app; +/// ``` +/// +/// When used as a response, it can serialize any type that implements +/// [`serde::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 +/// non-string keys is used, a 500 response will be issued, whose body is +/// the error message in UTF-8. +/// +/// # Response example +/// +/// ``` +/// use axum::{Router, routing::get, extract::Path}; +/// use axum_duper::Duper; +/// use serde::Serialize; +/// use uuid::Uuid; +/// +/// #[derive(Serialize)] +/// struct User { +/// id: Uuid, +/// username: String, +/// } +/// +/// async fn get_user(Path(user_id) : Path) -> Duper { +/// let user = find_user(user_id).await; +/// Duper(user) +/// } +/// +/// async fn find_user(user_id: Uuid) -> User { +/// // ... +/// # unimplemented!() +/// } +/// +/// let app = Router::new().route("/users/{id}", get(get_user)); +/// # let _: Router = app; +/// ``` pub struct Duper(pub T); impl Duper where T: DeserializeOwned, { + /// Construct a `Duper` from a byte slice. Most users should prefer to + /// use the `FromRequest` impl, but special cases may require first + /// extracting a `Request` into `Bytes`, then optionally constructing a + /// `Duper`. pub fn from_bytes(bytes: &[u8]) -> Result { let string = str::from_utf8(bytes).map_err(|_| DuperRejection::DuperDataError)?; Self::from_string(string) } + /// Construct a `Duper` from a str slice. Most users should prefer to + /// use the `FromRequest` impl, but special cases may require first + /// extracting a `Request` into `String`, then optionally constructing a + /// `Duper`. pub fn from_string(string: &str) -> Result { match serde_duper::from_string(string) { Ok(value) => Ok(Self(value)), diff --git a/duper-py/python/duper/__init__.py b/duper-py/python/duper/__init__.py index be08892..4d5784e 100644 --- a/duper-py/python/duper/__init__.py +++ b/duper-py/python/duper/__init__.py @@ -1,6 +1,6 @@ r"""Utilities for converting to and from Python types into the Duper format. -:mod:`duper` exposes an API similar to :mod:`json`.""" +:mod:`duper` exposes an API similar to :mod:`json` and :mod:`pickle`.""" from ._duper import ( dumps, diff --git a/duper-py/python/duper/fastapi.py b/duper-py/python/duper/fastapi.py index 3b90414..87d817b 100644 --- a/duper-py/python/duper/fastapi.py +++ b/duper-py/python/duper/fastapi.py @@ -21,6 +21,19 @@ T = TypeVar("T") class DuperResponse(Response): + """ + An HTTP response containing a Duper value. + + >>> import FastAPI + >>> from duper.fastapi import DuperResponse + >>> app = FastAPI() + >>> @app.get("/") + ... async def duper_response() -> DuperResponse: + ... return DuperResponse({ + ... "success": true, + ... }) + """ + media_type = DUPER_CONTENT_TYPE _indent: int | None _strip_identifiers: bool @@ -47,6 +60,20 @@ class DuperResponse(Response): def DuperBody(model_type: type[T]) -> Any: + """ + A dependency providing automatic parsing of an HTTP request containing a Duper value. + + >>> from typing import Any + >>> import FastAPI + >>> from duper.fastapi import DuperBody + >>> app = FastAPI() + >>> @app.post("/") + ... async def duper_body( + ... body: Annotated[dict[str, Any], DuperBody(dict[str, Any])], + ... ): + ... print(body) + """ + async def _get_duper_body(request: Request) -> T: if request.headers.get("Content-Type") not in ( DUPER_CONTENT_TYPE, diff --git a/duper-py/python/duper/pydantic.py b/duper-py/python/duper/pydantic.py index 8e985d5..95afca0 100644 --- a/duper-py/python/duper/pydantic.py +++ b/duper-py/python/duper/pydantic.py @@ -15,7 +15,32 @@ __all__ = [ class BaseModel(PydanticBaseModel): - __doc__ = PydanticBaseModel.__doc__ + """ + A wrapper around Pydantic's BaseModel with added functionality for + serializing/deserializing Duper values. + + In order to serialize an instance of this model: + + >>> from duper.pydantic import BaseModel + >>> class Foo(BaseModel): + ... bar: str + ... + >>> obj = Foo(bar="duper") + >>> s = obj.model_dump(mode="duper") + >>> print(s) + Foo({bar: "duper"}) + + In order to deserialize a string containing a Duper value: + + >>> from duper.pydantic import BaseModel + >>> class Foo(BaseModel): + ... bar: str + ... + >>> s = "Foo({bar: \"duper\"})" + >>> obj = Foo.model_validate_duper(s) + >>> obj + Foo(bar='duper') + """ @model_serializer(mode="wrap") def serialize_model( diff --git a/duper/src/lib.rs b/duper/src/lib.rs index 1450676..22c81a9 100644 --- a/duper/src/lib.rs +++ b/duper/src/lib.rs @@ -1,3 +1,15 @@ +//! # Duper +//! +//! The format that's super. +//! +//! Duper aims to be a human-friendly extension of JSON with quality-of-life improvements, extra types, and semantic identifiers. +//! +//! ## Feature flags +//! +//! - `ansi`: Enables the [`Ansi`] module for printing Duper values to a console. +//! - `serde`: Enables [`serde`] serialization/deserialization for [`DuperValue`]. +//! + mod ast; mod builder; mod escape; diff --git a/serde-duper-macros/src/lib.rs b/serde-duper-macros/src/lib.rs index 29a957c..9a6002b 100644 --- a/serde-duper-macros/src/lib.rs +++ b/serde-duper-macros/src/lib.rs @@ -1,10 +1,37 @@ +//! Macros for [`serde-duper`]. + use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::{Attribute, Fields, Ident, Item, ItemStruct, Meta, parse_macro_input}; #[proc_macro] +/// A proc-macro that automatically generates remote serializers and +/// deserializers for struct fields annotated with `#[duper(...)]`. +/// +/// ``` +/// use serde::{Deserialize, Serialize}; +/// use serde_duper_macros::duper; +/// +/// duper! { +/// #[derive(Serialize, Deserialize)] +/// struct User { +/// #[duper(MyId)] +/// id: u64, +/// #[duper(AliasList)] +/// aliases: Vec, +/// } +/// } +/// +/// let u = User { +/// id: 1234, +/// aliases: vec!["duper".to_string()], +/// }; +/// ``` +/// +/// Upon serializing and deserializing, `id` and `aliases` will be treated as +/// newtype structs. This is useful to add identifiers to Duper values. +/// pub fn duper(input: TokenStream) -> TokenStream { - // We expect an Item (struct) inside the macro invocation let item = parse_macro_input!(input as Item); match item { @@ -15,7 +42,6 @@ pub fn duper(input: TokenStream) -> TokenStream { } } -/// Expand the struct: preserve it, but for fields with #[duper(Name)] generate modules and #[serde(with = "...")] fn expand_struct(mut s: ItemStruct) -> proc_macro2::TokenStream { let struct_ident = s.ident.clone(); let mut modules = Vec::::new(); diff --git a/serde-duper/src/bytes.rs b/serde-duper/src/bytes.rs index f9dedb4..b109496 100644 --- a/serde-duper/src/bytes.rs +++ b/serde-duper/src/bytes.rs @@ -1 +1,3 @@ +//! 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 index 4a6d2c5..283016a 100644 --- a/serde-duper/src/de.rs +++ b/serde-duper/src/de.rs @@ -14,17 +14,55 @@ pub struct Deserializer<'de> { value: Option>, } +/// A structure that deserializes Duper values into Rust values. impl<'de> Deserializer<'de> { + /// Creates a Duper deserializer from a `&str`. pub fn from_string(input: &'de str) -> Result { let value = DuperParser::parse_duper_value(input)?; Ok(Self { value: Some(value) }) } + /// Creates a Duper deserializer from a [`DuperValue`]. pub fn from_value(value: DuperValue<'de>) -> Self { Self { value: Some(value) } } } +/// Deserialize an instance of type `T` from a str slice of Duper text. +/// +/// # Example +/// +/// ``` +/// use serde::Deserialize; +/// +/// #[derive(Deserialize, Debug)] +/// struct User { +/// fingerprint: Vec, +/// location: String, +/// } +/// +/// fn main() { +/// // The type of `j` is `&str` +/// let j = r#" +/// User({ +/// fingerprint: b"\xF9\xBA\x14\x3B\x95\xFF\x6D\x82", +/// location: City("Menlo Park, CA"), +/// })"#; +/// +/// let u: User = serde_duper::from_string(j).unwrap(); +/// println!("{:#?}", u); +/// } +/// ``` +/// +/// # Errors +/// +/// This conversion can fail if the structure of the input does not match the +/// structure expected by `T`, for example if `T` is a struct type but the input +/// contains something other than a Duper object. It can also fail if the +/// structure is correct but `T`'s implementation of [`Deserialize`] decides that +/// something is wrong with the data, for example required struct fields are +/// missing from the Duper object or some number is too big to fit in the +/// expected primitive type. pub fn from_string<'a, T>(input: &'a str) -> Result where T: Deserialize<'a>, @@ -34,6 +72,66 @@ where Ok(t) } +/// Interpret a [`DuperValue`] as an instance of type `T`. +/// +/// # Example +/// +/// ``` +/// use std::borrow::Cow; +/// use serde::Deserialize; +/// use serde_duper::{ +/// DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, +/// DuperString, DuperValue, +/// }; +/// +/// #[derive(Deserialize, Debug)] +/// struct User { +/// fingerprint: Vec, +/// location: String, +/// } +/// +/// fn main() { +/// // The type of `d` is `serde_duper::DuperValue` +/// let d = DuperValue { +/// identifier: Some(DuperIdentifier::try_from(Cow::Borrowed("User")).unwrap()), +/// inner: DuperInner::Object(DuperObject::try_from(vec![ +/// ( +/// DuperKey::from(Cow::Borrowed("fingerprint")), +/// DuperValue { +/// identifier: None, +/// inner: DuperInner::Bytes(DuperBytes::from(Cow::Borrowed( +/// &b"\xF9\xBA\x14\x3B\x95\xFF\x6D\x82"[..], +/// ))), +/// } +/// ), +/// ( +/// DuperKey::from(Cow::Borrowed("location")), +/// DuperValue { +/// identifier: Some( +/// DuperIdentifier::try_from(Cow::Borrowed("City")).unwrap(), +/// ), +/// inner: DuperInner::String(DuperString::from( +/// Cow::Borrowed("Menlo Park, CA"), +/// )), +/// } +/// ), +/// ]).unwrap()), +/// }; +/// +/// let u: User = serde_duper::from_value(d).unwrap(); +/// println!("{:#?}", u); +/// } +/// ``` +/// +/// # Errors +/// +/// This conversion can fail if the structure of the input does not match the +/// structure expected by `T`, for example if `T` is a struct type but the input +/// contains something other than a Duper object. It can also fail if the +/// structure is correct but `T`'s implementation of [`Deserialize`] decides that +/// something is wrong with the data, for example required struct fields are +/// missing from the Duper object or some number is too big to fit in the +/// expected primitive type. pub fn from_value<'a, T>(value: DuperValue<'a>) -> Result where T: Deserialize<'a>, diff --git a/serde-duper/src/error.rs b/serde-duper/src/error.rs index 1ff4e4c..466318d 100644 --- a/serde-duper/src/error.rs +++ b/serde-duper/src/error.rs @@ -3,27 +3,39 @@ use std::fmt::{self, Display}; use duper::{DuperIdentifierTryFromError, DuperObjectTryFromError}; #[derive(Debug, Clone)] +/// The kinds of errors that can happen during serialization and deserialization. pub enum ErrorKind { + /// Parsing failed at the given [`pest`] rule. + /// + /// This error implements `.to_miette()` in order to allow generation of a + /// [`miette`] `Report`. ParseError(Box>), + /// Serialization failed with an unspecified error. SerializationError, + /// Deserialization failed with the given reason. DeserializationError(serde_core::de::value::Error), + /// An invalid value was provided. InvalidValue, + /// Unspecified conditions. Custom, } +/// This type includes the error kind and message associated with the failure. #[derive(Debug, Clone)] pub struct ErrorImpl { pub kind: ErrorKind, pub message: String, } +/// This type represents all possible errors that can occur when serializing or +/// deserializing Duper data. #[derive(Debug, Clone)] pub struct Error { pub inner: Box, } impl Error { - pub fn new(kind: ErrorKind, message: impl Into) -> Self { + pub(crate) fn new(kind: ErrorKind, message: impl Into) -> Self { Self { inner: Box::new(ErrorImpl { kind, @@ -32,15 +44,15 @@ impl Error { } } - pub fn custom(msg: impl Into + Clone) -> Self { + pub(crate) fn custom(msg: impl Into + Clone) -> Self { Self::new(ErrorKind::Custom, msg) } - pub fn serialization(msg: impl Into) -> Self { + pub(crate) fn serialization(msg: impl Into) -> Self { Self::new(ErrorKind::SerializationError, msg) } - pub fn invalid_value(msg: impl Into) -> Self { + pub(crate) fn invalid_value(msg: impl Into) -> Self { Self::new(ErrorKind::InvalidValue, msg) } } diff --git a/serde-duper/src/lib.rs b/serde-duper/src/lib.rs index e7deb0e..018c2a7 100644 --- a/serde-duper/src/lib.rs +++ b/serde-duper/src/lib.rs @@ -1,3 +1,282 @@ +//! # Serde Duper +//! +//! Duper is a format which 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"), +//! }) +//! ``` +//! +//! This crate allows you to convert between Duper's text representation and +//! 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. +//! +//! ``` +//! use serde::{Deserialize, Serialize}; +//! use serde_duper::Result; +//! +//! #[derive(Serialize, Deserialize)] +//! struct Person { +//! name: String, +//! age: u8, +//! phones: Vec, +//! } +//! +//! fn deserialize() -> Result { +//! // Some JSON input data as a &str. Maybe this comes from the user. +//! let data = r#" +//! Person({ +//! name: "John Doe", +//! age: 43, +//! phones: [ +//! ResidentialPhone("+44 1234567"), +//! CellPhone("+44 2345678"), +//! ], +//! })"#; +//! +//! // Parse the string of data into a Person object. This is exactly the +//! // same function as the one that produced serde_json::Value above, but +//! // now we are asking it for a Person as output. +//! let p: Person = serde_duper::from_string(data)?; +//! +//! // Do things just like with any other Rust data structure. +//! println!("Please call {} at the number {}", p.name, p.phones[0]); +//! +//! Ok(p) +//! } +//! +//! fn serialize(p: Person) -> Result<()> { +//! // Serialize the person back into a Duper string. +//! let d = serde_duper::to_string(&p)?; +//! +//! // Print, write to a file, or send to an HTTP server. +//! println!("{}", d); +//! +//! Ok(()) +//! } +//! +//! fn main() { +//! let p = deserialize().unwrap(); +//! serialize(p).unwrap(); +//! } +//! ``` +//! +//! Any type that implements Serde's `Deserialize` trait can be deserialized +//! into a struct like this. This includes built-in Rust standard library type +//! like `Vec` and `HashMap`, as well as any structs or enums annotated +//! with `#[derive(Deserialize)]` in the Rust ecosystem. +//! +//! Conversely, any type that implements Serde's `Serialize` trait can be +//! serialized into a string like this. This includes built-in Rust standard +//! library types like `Vec` and `HashMap`, as well as any structs or +//! enums annotated with `#[derive(Serialize)]` in the Rust ecosystem. +//! +//! # Support for identifiers +//! +//! By default, serialization will attempt to include identifiers for structs +//! and enums, while deserialization will ignore them. It's possible to +//! customize the emitted identifiers with the `#[serde(rename = "...")]` +//! attribute. +//! +//! ``` +//! use serde::{Deserialize, Serialize}; +//! use uuid::Uuid; +//! +//! #[derive(Serialize, Deserialize)] +//! #[serde(rename = "Status")] +//! enum UserStatus { +//! Disabled, +//! PendingApproval, +//! Enabled, +//! } +//! +//! #[derive(Serialize, Deserialize)] +//! struct User { +//! id: Uuid, +//! status: UserStatus, +//! last_known_ips: Vec, +//! } +//! +//! let u = User { +//! id: "314dfe6f-7a76-4c43-80b9-3b0ceb0960c0".parse().unwrap(), +//! status: UserStatus::Enabled, +//! last_known_ips: vec!["2a02:ec80:700:ed1a::1".to_string()], +//! }; +//! let d = serde_duper::to_string(&u).unwrap(); +//! println!("{}", d); +//! // This should print: +//! // User({ +//! // id: "314dfe6f-7a76-4c43-80b9-3b0ceb0960c0", +//! // status: Status("Enabled"), +//! // last_known_ips: ["2a02:ec80:700:ed1a::1"], +//! // }) +//! ``` +//! +//! It's also possible to remove an identifier with `#[serde(rename = "")]`. +//! +//! In order to generate identifiers for fields, there are currently three +//! possibilities: +//! +//! ## 1. Wrapping your field in a newtype +//! +//! ``` +//! use serde::{Deserialize, Serialize}; +//! +//! #[derive(Serialize, Deserialize)] +//! #[serde(rename = "Status")] +//! enum UserStatus { +//! Disabled, +//! PendingApproval, +//! Enabled, +//! } +//! +//! #[derive(Serialize, Deserialize)] +//! struct Uuid(uuid::Uuid); +//! +//! #[derive(Serialize, Deserialize)] +//! struct User { +//! id: Uuid, +//! status: UserStatus, +//! last_known_ips: Vec, +//! } +//! +//! let u = User { +//! id: Uuid("314dfe6f-7a76-4c43-80b9-3b0ceb0960c0".parse().unwrap()), +//! status: UserStatus::Enabled, +//! last_known_ips: vec!["2a02:ec80:700:ed1a::1".to_string()], +//! }; +//! let d = serde_duper::to_string(&u).unwrap(); +//! println!("{}", d); +//! // This should print: +//! // User({ +//! // id: Uuid("314dfe6f-7a76-4c43-80b9-3b0ceb0960c0"), +//! // status: Status("Enabled"), +//! // last_known_ips: ["2a02:ec80:700:ed1a::1"], +//! // }) +//! ``` +//! +//! This offers maximum customizability, but requires an extra layer of +//! indirection in your code. +//! +//! ## 2. Using a remote (de)serializer +//! +//! ``` +//! use serde::{Deserialize, Serialize}; +//! use serde_duper::types::DuperUuid; +//! use uuid::Uuid; +//! +//! #[derive(Serialize, Deserialize)] +//! #[serde(rename = "Status")] +//! enum UserStatus { +//! Disabled, +//! PendingApproval, +//! Enabled, +//! } +//! +//! #[derive(Serialize, Deserialize)] +//! struct User { +//! #[serde(with = "DuperUuid")] +//! id: Uuid, +//! status: UserStatus, +//! last_known_ips: Vec, +//! } +//! +//! let u = User { +//! id: "314dfe6f-7a76-4c43-80b9-3b0ceb0960c0".parse().unwrap(), +//! status: UserStatus::Enabled, +//! last_known_ips: vec!["2a02:ec80:700:ed1a::1".to_string()], +//! }; +//! let d = serde_duper::to_string(&u).unwrap(); +//! println!("{}", d); +//! // This should print: +//! // User({ +//! // id: Uuid("314dfe6f-7a76-4c43-80b9-3b0ceb0960c0"), +//! // status: Status("Enabled"), +//! // last_known_ips: ["2a02:ec80:700:ed1a::1"], +//! // }) +//! ``` +//! +//! The [`serde_duper::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, +//! but allows you to use the original types directly. +//! +//! Currently, modules are provided for `T` and `Option`. +//! +//! ## 3. Using the proc-macro +//! +//! ``` +//! use serde::{Deserialize, Serialize}; +//! use serde_duper::duper; +//! +//! #[derive(Serialize, Deserialize)] +//! #[serde(rename = "Status")] +//! enum UserStatus { +//! Disabled, +//! PendingApproval, +//! Enabled, +//! } +//! +//! duper! { +//! #[derive(Serialize, Deserialize)] +//! struct User { +//! #[duper(MyUuid)] +//! id: uuid::Uuid, +//! status: UserStatus, +//! #[duper(IpList)] +//! last_known_ips: Vec, +//! } +//! } +//! +//! let u = User { +//! id: "314dfe6f-7a76-4c43-80b9-3b0ceb0960c0".parse().unwrap(), +//! status: UserStatus::Enabled, +//! last_known_ips: vec!["2a02:ec80:700:ed1a::1".to_string()], +//! }; +//! let d = serde_duper::to_string(&u).unwrap(); +//! println!("{}", d); +//! // This should print: +//! // User({ +//! // id: MyUuid("314dfe6f-7a76-4c43-80b9-3b0ceb0960c0"), +//! // 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. +//! +//! This functionality requires the `macros` feature flag. +//! + pub mod bytes; mod de; mod error; @@ -5,7 +284,10 @@ mod ser; pub mod types; pub use de::{Deserializer, from_string, from_value}; -pub use duper::{DuperInner, DuperValue}; +pub use duper::{ + DuperArray, DuperBytes, DuperIdentifier, DuperInner, DuperKey, DuperObject, DuperString, + DuperTuple, DuperValue, +}; pub use error::{Error, ErrorImpl, ErrorKind, Result}; pub use ser::{Serializer, to_duper, to_string, to_string_minified, to_string_pretty}; diff --git a/serde-duper/src/ser.rs b/serde-duper/src/ser.rs index 26d76fb..eaaf8c8 100644 --- a/serde-duper/src/ser.rs +++ b/serde-duper/src/ser.rs @@ -8,19 +8,27 @@ use serde_core::{Serialize, ser}; use crate::Error; +/// A structure for serializing Rust values into Duper values. #[derive(Clone)] pub struct Serializer<'a> { - _phantom: PhantomData>, + _marker: PhantomData>, } impl<'a> Serializer<'a> { - fn new() -> Self { + /// Creates a new Duper serializer. + pub fn new() -> Self { Self { - _phantom: Default::default(), + _marker: Default::default(), } } } +/// Serialize the given data structure as a Duper value. +/// +/// # Errors +/// +/// Serialization can fail if `T`'s implementation of [`Serialize`] decides to +/// fail, or if `T` contains a map with non-string keys. pub fn to_duper<'a, T>(value: &'a T) -> Result, Error> where T: Serialize, @@ -29,6 +37,12 @@ where value.serialize(&mut serializer) } +/// Serialize the given data structure as a [`String`] of a Duper value. +/// +/// # Errors +/// +/// Serialization can fail if `T`'s implementation of [`Serialize`] decides to +/// fail, or if `T` contains a map with non-string keys. pub fn to_string(value: &T) -> Result where T: Serialize, @@ -36,6 +50,13 @@ where Ok(DuperSerializer::new(false).serialize(to_duper(value)?)) } +/// Serialize the given data structure as a [`String`] of a Duper value, stripping +/// identifiers from the output. +/// +/// # Errors +/// +/// Serialization can fail if `T`'s implementation of [`Serialize`] decides to +/// fail, or if `T` contains a map with non-string keys. pub fn to_string_minified(value: &T) -> Result where T: Serialize, @@ -43,6 +64,13 @@ where Ok(DuperSerializer::new(true).serialize(to_duper(value)?)) } +/// Serialize the given data structure as a [`String`] of a Duper value, +/// pretty-printing the output. +/// +/// # Errors +/// +/// Serialization can fail if `T`'s implementation of [`Serialize`] decides to +/// fail, or if `T` contains a map with non-string keys. pub fn to_string_pretty(value: &T, indent: &str) -> Result where T: Serialize, @@ -52,18 +80,18 @@ where .pretty_print(to_duper(value)?)) } -impl<'a, 'b> ser::Serializer for &'a mut Serializer<'b> { - type Ok = DuperValue<'b>; +impl<'ser, 'a> ser::Serializer for &'ser mut Serializer<'a> { + type Ok = DuperValue<'a>; type Error = Error; - type SerializeSeq = SerializeSeq<'a, 'b>; - type SerializeTuple = SerializeTuple<'a, 'b>; - type SerializeTupleStruct = SerializeTupleStruct<'a, 'b>; - type SerializeTupleVariant = SerializeTupleVariant<'a, 'b>; - type SerializeMap = SerializeMap<'a, 'b>; - type SerializeStruct = SerializeStruct<'a, 'b>; - type SerializeStructVariant = SerializeStructVariant<'a, 'b>; + type SerializeSeq = SerializeSeq<'ser, 'a>; + type SerializeTuple = SerializeTuple<'ser, 'a>; + type SerializeTupleStruct = SerializeTupleStruct<'ser, 'a>; + type SerializeTupleVariant = SerializeTupleVariant<'ser, 'a>; + type SerializeMap = SerializeMap<'ser, 'a>; + type SerializeStruct = SerializeStruct<'ser, 'a>; + type SerializeStructVariant = SerializeStructVariant<'ser, 'a>; fn serialize_bool(self, v: bool) -> Result { Ok(DuperValue { -- 2.51.2