diff --git a/Cargo.toml b/Cargo.toml index 5cff318..e4e4f88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rust-tagged" -version = "0.4.0" +version = "0.5.0" edition = "2024" authors = ["Codefonsi "] license = "MPL-2.0" @@ -18,17 +18,16 @@ include = ["src/**/*", "Cargo.toml", "../../README.md", "LICENSE"] resolver = "3" # or "3" members = [ "tagged-core", - "tagged-macros", ] [patch.crates-io] tagged-core = { path = "tagged-core" } [dependencies] -tagged-core = { path = "tagged-core", version = "0.4.0", features = ["serde"] } +tagged-core = { path = "tagged-core", version = "0.5.0", features = ["serde"] } [dev-dependencies] -serde = { version = "1.0.219", features = ["derive"] } +serde = { version = "1.0.210", features = ["derive", "rc"] } serde_json = {version = "1.0.140"} diff --git a/examples/Into_iterators.rs b/examples/Into_iterators.rs new file mode 100644 index 0000000..97a5c09 --- /dev/null +++ b/examples/Into_iterators.rs @@ -0,0 +1,16 @@ +use tagged_core::Tagged; + +#[derive(Debug)] +struct Org; + +type EmployeeNames = Tagged, Org>; + +fn main() { + let names: EmployeeNames = Tagged::new(vec!["Alice".into(), "Bob".into()]); + names.into_iter().for_each(|name| println!("Name: {}", name)); +} + +/* +Name: Alice +Name: Bob +*/ \ No newline at end of file diff --git a/examples/Iter_example.rs b/examples/Iter_example.rs index f6c2f1c..aa4d2eb 100644 --- a/examples/Iter_example.rs +++ b/examples/Iter_example.rs @@ -7,20 +7,10 @@ type EmployeeNames = Tagged, Org>; fn main() { let names: EmployeeNames = Tagged::new(vec!["Alice".into(), "Bob".into()]); - - for name in &names { - println!("Name: {name}"); - } - - // Consuming iterator - for name in names { - println!("Owned: {name}"); - } + names.iter().for_each(|name| println!("Name: {}", name)); } /* Name: Alice Name: Bob -Owned: Alice -Owned: Bob */ \ No newline at end of file diff --git a/examples/RC_example.rs b/examples/RC_example.rs new file mode 100644 index 0000000..ae47cd3 --- /dev/null +++ b/examples/RC_example.rs @@ -0,0 +1,26 @@ +use rust_tagged::Tagged; +use std::rc::Rc; +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] +struct Org; + +type OrgRef = Tagged, Org>; + +#[derive(Debug, Serialize, Deserialize)] +struct Project { + name: String, + org: OrgRef, +} + +fn main() { + let shared = Rc::new("codefonsi.com".to_string()); + + let project = Project { + name: "Open Source".into(), + org: shared.clone().into(), + }; + + let json = serde_json::to_string(&project).unwrap(); + println!("Serialized: {json}"); +} \ No newline at end of file diff --git a/examples/Serde_example.rs b/examples/Serde_example.rs index 98c87dc..7c5ad30 100644 --- a/examples/Serde_example.rs +++ b/examples/Serde_example.rs @@ -1,24 +1,34 @@ use serde::{Deserialize, Serialize}; +use tagged_core::Tagged; + +#[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] +struct SomeCustomType { + some_id: String +} +#[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] +struct SomeCustomType2(String); +#[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] +struct User { + id: Tagged, + id2: SomeCustomType, + id3: SomeCustomType2, +} -fn main() { - use tagged_core::Tagged; - #[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] - struct SomeCustomType { - some_id: String - } - #[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] - struct SomeCustomType2(String); - #[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] - struct User { - id: Tagged, - id2: SomeCustomType, - id3: SomeCustomType2, - } +fn main() { let user = User { id: "1".into() , id2: SomeCustomType { some_id: "2".into() }, id3: SomeCustomType2("3".into())}; let j = serde_json::to_string(&user).unwrap(); + let converted_user = serde_json::from_str::(&j).unwrap(); println!("{}", j); + println!("{:?}", converted_user); } +/* + Running `target/debug/examples/Serde_example` +{"id":"1","id2":{"some_id":"2"},"id3":"3"} +User { id: "1", id2: SomeCustomType { some_id: "2" }, id3: SomeCustomType2("3") } + +Process finished with exit code 0 +*/ /* Problem with normal types diff --git a/examples/for_loop_example.rs b/examples/for_loop_example.rs new file mode 100644 index 0000000..f6c2f1c --- /dev/null +++ b/examples/for_loop_example.rs @@ -0,0 +1,26 @@ +use tagged_core::Tagged; + +#[derive(Debug)] +struct Org; + +type EmployeeNames = Tagged, Org>; + +fn main() { + let names: EmployeeNames = Tagged::new(vec!["Alice".into(), "Bob".into()]); + + for name in &names { + println!("Name: {name}"); + } + + // Consuming iterator + for name in names { + println!("Owned: {name}"); + } +} + +/* +Name: Alice +Name: Bob +Owned: Alice +Owned: Bob +*/ \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 2cf777b..6c23e83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,113 +1 @@ -/// rust-tagged provides a simple way to define strongly typed wrappers over primitive types like String, i32, Uuid, chrono::DateTime, etc. It helps eliminate bugs caused by misusing raw primitives for conceptually distinct fields such as UserId, Email, ProductId, and more. -/// -/// Eliminate accidental mixups between similar types (e.g. OrgId vs UserId) -/// Enforce domain modeling in code via the type system -/// Ergonomic .into() support for primitive conversions -/// -/// # Example - Simple -/// -/// ``` -/// use tagged_core::{Tagged}; -/// -/// #[derive(Debug)] -/// struct EmailTag; -/// -/// type Email = Tagged; -/// -/// fn main() { -/// let email: Email = "test@example.com".into(); -/// println!("Email inner value: {}", email.value()); -/// -/// // Convert back to String -/// let raw: String = email.into(); -/// println!("Raw String: {raw}"); -/// } -/// ``` -/// -/// # Example - Debug -/// ``` -/// use tagged_core::Tagged; -/// -/// -/// #[derive(Debug)] -/// struct UserIdTag { -/// a: Tagged, -/// b: Tagged, -/// } -/// -/// -/// fn main() { -/// let instance = UserIdTag{a: 1.into(), b: 2.into()}; -/// -/// println!("{}", instance.a); -/// println!("{:?}", instance.b); -/// } -/// ``` -/// -/// # Example - Hash -/// ``` -/// fn main() { -/// use tagged_core::Tagged; -/// use std::collections::HashSet; -/// -/// #[derive(Clone, Hash, Debug, PartialEq, Eq)] -/// struct User { -/// id: Tagged -/// } -/// let mut s: HashSet = HashSet::new(); -/// let user = User{id: "me@example.com".into()}; -/// s.insert(user.clone()); -/// -/// assert!(s.contains(&user)); -/// } -/// ``` -/// -/// # Example - Iter -/// ``` -/// use tagged_core::Tagged; -/// -/// #[derive(Debug)] -/// struct Org; -/// -/// type EmployeeNames = Tagged, Org>; -/// -/// fn main() { -/// let names: EmployeeNames = Tagged::new(vec!["Alice".into(), "Bob".into()]); -/// -/// for name in &names { -/// println!("Name: {name}"); -/// } -/// -/// // Consuming iterator -/// for name in names { -/// println!("Owned: {name}"); -/// } -/// } -/// -/// /* -/// Name: Alice -/// Name: Bob -/// Owned: Alice -/// Owned: Bob -/// */ -/// ``` -/// -/// # Example - Mutation -/// ``` -/// use tagged_core::Tagged; -/// -/// #[derive(Debug)] -/// struct Org; -/// -/// type OrgName = Tagged; -/// -/// fn main() { -/// let mut name = OrgName::new("Codefonsi".into()); -/// -/// name.set("New Org Name".into()); -/// -/// println!("Updated Org Name: {}", name.value()); -/// } -/// ``` - pub use tagged_core::*; diff --git a/tagged-core/Cargo.toml b/tagged-core/Cargo.toml index 5c2fe15..ba283ee 100644 --- a/tagged-core/Cargo.toml +++ b/tagged-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tagged-core" -version = "0.4.0" +version = "0.5.0" edition = "2024" description = "A lightweight tagged type abstraction for type-safe IDs, etc." license = "MPL-2.0" @@ -14,9 +14,10 @@ readme = "../README.md" include = ["src/**/*", "Cargo.toml", "../../README.md", "LICENSE"] [dependencies] -serde = { version = "1.0.219", features = ["derive"], optional = true } +serde = { version = "1.0.210", features = ["derive", "rc"], optional = true } [dev-dependencies] +serde_json = "1.0.140" uuid = { version = "1.6" , features = ["v4"]} chrono = "0.4.41" diff --git a/tagged-core/src/lib.rs b/tagged-core/src/lib.rs index 5f5e331..9edb563 100644 --- a/tagged-core/src/lib.rs +++ b/tagged-core/src/lib.rs @@ -29,91 +29,6 @@ use std::hash::{Hash, Hasher}; /// } /// ``` /// -/// # Example - Debug -/// ``` -/// use tagged_core::Tagged; -/// -/// -/// #[derive(Debug)] -/// struct UserIdTag { -/// a: Tagged, -/// b: Tagged, -/// } -/// -/// -/// fn main() { -/// let instance = UserIdTag{a: 1.into(), b: 2.into()}; -/// -/// println!("{}", instance.a); -/// println!("{:?}", instance.b); -/// } -/// ``` -/// -/// # Example - Hash -/// ``` -/// fn main() { -/// use tagged_core::Tagged; -/// use std::collections::HashSet; -/// -/// #[derive(Clone, Hash, Debug, PartialEq, Eq)] -/// struct User { -/// id: Tagged -/// } -/// let mut s: HashSet = HashSet::new(); -/// let user = User{id: "me@example.com".into()}; -/// s.insert(user.clone()); -/// -/// assert!(s.contains(&user)); -/// } -/// ``` -/// -/// # Example - Iter -/// ``` -/// use tagged_core::Tagged; -/// -/// #[derive(Debug)] -/// struct Org; -/// -/// type EmployeeNames = Tagged, Org>; -/// -/// fn main() { -/// let names: EmployeeNames = Tagged::new(vec!["Alice".into(), "Bob".into()]); -/// -/// for name in &names { -/// println!("Name: {name}"); -/// } -/// -/// // Consuming iterator -/// for name in names { -/// println!("Owned: {name}"); -/// } -/// } -/// -/// /* -/// Name: Alice -/// Name: Bob -/// Owned: Alice -/// Owned: Bob -/// */ -/// ``` -/// -/// # Example - Mutation -/// ``` -/// use tagged_core::Tagged; -/// -/// #[derive(Debug)] -/// struct Org; -/// -/// type OrgName = Tagged; -/// -/// fn main() { -/// let mut name = OrgName::new("Codefonsi".into()); -/// -/// name.set("New Org Name".into()); -/// -/// println!("Updated Org Name: {}", name.value()); -/// } -/// ``` pub struct Tagged { value: T, _marker: std::marker::PhantomData, @@ -181,6 +96,26 @@ impl Ord for Tagged { } } +/// # Example - Debug +/// ``` +/// use tagged_core::Tagged; +/// +/// +/// #[derive(Debug)] +/// struct UserIdTag { +/// a: Tagged, +/// b: Tagged, +/// } +/// +/// +/// fn main() { +/// let instance = UserIdTag{a: 1.into(), b: 2.into()}; +/// +/// println!("{}", instance.a); +/// println!("{:?}", instance.b); +/// } +/// ``` +/// impl fmt::Debug for Tagged { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.value.fmt(f) @@ -202,6 +137,24 @@ impl Clone for Tagged { } } +/// # Example - Hash +/// ``` +/// fn main() { +/// use tagged_core::Tagged; +/// use std::collections::HashSet; +/// +/// #[derive(Clone, Hash, Debug, PartialEq, Eq)] +/// struct User { +/// id: Tagged +/// } +/// let mut s: HashSet = HashSet::new(); +/// let user = User{id: "me@example.com".into()}; +/// s.insert(user.clone()); +/// +/// assert!(s.contains(&user)); +/// } +/// ``` +/// impl Hash for Tagged { fn hash(&self, state: &mut H) { self.value.hash(state) @@ -212,6 +165,42 @@ impl Hash for Tagged { #[cfg(feature = "serde")] use serde::{Serialize, Deserialize, Serializer, Deserializer}; + +/// Example - Serialize +/// ``` +/// use serde::{Deserialize, Serialize}; +/// use tagged_core::Tagged; +/// +/// #[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// struct SomeCustomType { +/// some_id: String +/// } +/// #[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// struct SomeCustomType2(String); +/// #[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// struct User { +/// id: Tagged, +/// id2: SomeCustomType, +/// id3: SomeCustomType2, +/// } +/// +/// +/// fn main() { +/// let user = User { id: "1".into() , id2: SomeCustomType { some_id: "2".into() }, id3: SomeCustomType2("3".into())}; +/// let j = serde_json::to_string(&user).unwrap(); +/// println!("{}", j); +/// } +/// +/// /* +/// Problem with normal types +/// {"id":"1","id2":{"some_id":"2"}} +/// +/// // rust is powerful enough to solve it using touple +/// {"id":"1","id2":{"some_id":"2"},"id3":"3"} +/// +/// // or we can use a new type called tagged that don't need a new name. +/// */ +/// ``` #[cfg(feature = "serde")] impl Serialize for Tagged { fn serialize(&self, serializer: S) -> Result { @@ -219,6 +208,50 @@ impl Serialize for Tagged { } } + +/// ``` +/// use serde::{Deserialize, Serialize}; +/// use tagged_core::Tagged; +/// +/// #[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// struct SomeCustomType { +/// some_id: String +/// } +/// #[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// struct SomeCustomType2(String); +/// #[derive(Clone, Hash, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// struct User { +/// id: Tagged, +/// id2: SomeCustomType, +/// id3: SomeCustomType2, +/// } +/// +/// +/// fn main() { +/// let user = User { id: "1".into() , id2: SomeCustomType { some_id: "2".into() }, id3: SomeCustomType2("3".into())}; +/// let j = serde_json::to_string(&user).unwrap(); +/// let converted_user = serde_json::from_str::(&j).unwrap(); +/// println!("{}", j); +/// println!("{:?}", converted_user); +/// } +/// /* +/// Running `target/debug/examples/Serde_example` +/// {"id":"1","id2":{"some_id":"2"},"id3":"3"} +/// User { id: "1", id2: SomeCustomType { some_id: "2" }, id3: SomeCustomType2("3") } +/// +/// Process finished with exit code 0 +/// */ +/// +/// /* +/// Problem with normal types +/// {"id":"1","id2":{"some_id":"2"}} +/// +/// // rust is powerful enough to solve it using touple +/// {"id":"1","id2":{"some_id":"2"},"id3":"3"} +/// +/// // or we can use a new type called tagged that don't need a new name. +/// */ +/// ``` #[cfg(feature = "serde")] impl<'de, T: Deserialize<'de>, Tag> Deserialize<'de> for Tagged { fn deserialize>(deserializer: D) -> Result { @@ -226,6 +259,24 @@ impl<'de, T: Deserialize<'de>, Tag> Deserialize<'de> for Tagged { } } +/// ``` +/// use tagged_core::Tagged; +/// +/// #[derive(Debug)] +/// struct Org; +/// +/// type EmployeeNames = Tagged, Org>; +/// +/// fn main() { +/// let names: EmployeeNames = Tagged::new(vec!["Alice".into(), "Bob".into()]); +/// names.into_iter().for_each(|name| println!("Name: {}", name)); +/// } +/// +/// /* +/// Name: Alice +/// Name: Bob +/// */ +/// ``` impl IntoIterator for Tagged, Tag> { type Item = T; type IntoIter = std::vec::IntoIter; @@ -235,6 +286,24 @@ impl IntoIterator for Tagged, Tag> { } } +/// ``` +/// use tagged_core::Tagged; +/// +/// #[derive(Debug)] +/// struct Org; +/// +/// type EmployeeNames = Tagged, Org>; +/// +/// fn main() { +/// let names: EmployeeNames = Tagged::new(vec!["Alice".into(), "Bob".into()]); +/// names.iter().for_each(|name| println!("Name: {}", name)); +/// } +/// +/// /* +/// Name: Alice +/// Name: Bob +/// */ +/// ``` impl<'a, T, Tag> IntoIterator for &'a Tagged, Tag> { type Item = &'a T; type IntoIter = std::slice::Iter<'a, T>; @@ -245,6 +314,23 @@ impl<'a, T, Tag> IntoIterator for &'a Tagged, Tag> { } +/// # Example - Mutation +/// ``` +/// use tagged_core::Tagged; +/// +/// #[derive(Debug)] +/// struct Org; +/// +/// type OrgName = Tagged; +/// +/// fn main() { +/// let mut name = OrgName::new("Codefonsi".into()); +/// +/// name.set("New Org Name".into()); +/// +/// println!("Updated Org Name: {}", name.value()); +/// } +/// ``` impl Tagged { /// Not allowed feature - Get a mutable reference to the internal value // pub fn value_mut(&mut self) -> &mut T { diff --git a/tagged-macros/.DS_Store b/tagged-macros/.DS_Store deleted file mode 100644 index 1271304..0000000 Binary files a/tagged-macros/.DS_Store and /dev/null differ diff --git a/tagged-macros/Cargo.toml b/tagged-macros/Cargo.toml deleted file mode 100644 index e986620..0000000 --- a/tagged-macros/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "tagged-macros" -version = "0.1.0" -edition = "2024" -description = "Proc macros for rust-tagged crate" -license = "Mozilla Public License 2.0" -readme = "../../README.md" -include = ["src/**/*", "Cargo.toml", "../../README.md", "LICENSE"] - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1" -syn = { version = "2", features = ["full"] } -quote = "1" -#tagged-core = { version = "0.1.0", path = "../tagged-core" } diff --git a/tagged-macros/examples/basic.rs b/tagged-macros/examples/basic.rs deleted file mode 100644 index 489499c..0000000 --- a/tagged-macros/examples/basic.rs +++ /dev/null @@ -1,59 +0,0 @@ -use tagged_core::*; -use tagged_macros::Tagged; - -#[derive(Tagged)] -struct EmployeeId(i32); - -struct Employee { - id: EmployeeId, - employee_email_id: Tagged, - name: String, - org: Org, -} - -struct Org { - org_email_id: Tagged, - name: String, -} - -fn send_mail_employee(mail_id: &Tagged, message: &str) { - send_mail(mail_id, message); -} - -fn send_mail_org(mail_id: &Tagged, message: &str) { - send_mail(mail_id, message); -} - -fn send_mail(mail_id: &str, message: &str) { - println!("Mail Sent.{}", message); -} - - -fn main() { - let emp = Employee { - id: 12.into(), - employee_email_id: "akash@gmail.com".into(), - name: "Akash".into(), - org: Org { - org_email_id: "info@codefonsi.com".into(), - name: "Codefonsi".into(), - }, - }; - - // here we can clearly define and distinct the mail id of employee and org - // without - // // expected `&Tagged`, but found `&Tagged` - // send_mail_org(&emp.employee_email_id, "This is supposed to send to user but there is no type safety at compile time"); - // - // // expected `&Tagged`, but found `&Tagged` - // send_mail_employee(&emp.org.org_email_id, "This is supposed to send to user but there is no type safety at compile time"); - // - // // the trait bound `Tagged: From>` is not satisfied [E0277] - // send_mail_employee(&emp.org.org_email_id.into(), "This is ok"); - - // after refactoring - send_mail_org(&emp.org.org_email_id, "This is ok"); - send_mail_employee(&emp.employee_email_id, "This is ok"); - - -} \ No newline at end of file diff --git a/tagged-macros/src/lib.rs b/tagged-macros/src/lib.rs deleted file mode 100644 index 79786cc..0000000 --- a/tagged-macros/src/lib.rs +++ /dev/null @@ -1,57 +0,0 @@ -// `proc-macro` crates can only export functions with a `#[proc_macro]`, `#[proc_macro_derive]`, or `#[proc_macro_attribute]` attribute -// pub fn add(left: u64, right: u64) -> u64 { -// left + right -// } - -extern crate proc_macro; -use proc_macro::TokenStream; -use quote::{quote, format_ident}; -use syn::{parse_macro_input, DeriveInput}; - -#[proc_macro_derive(Tagged)] -pub fn derive_tagged(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let struct_name = input.ident; - - // Assume: tuple struct like `struct Email(Tagged);` - let g = quote! { - impl std::convert::From for #struct_name { - fn from(val: i32) -> Self { - Self(tagged_core::Tagged::new(val)) - } - } - - impl From<#struct_name> for i32 { - fn from(tagged: #struct_name) -> i32 { - tagged.0.into_inner() - } - } - - impl std::ops::Deref for #struct_name { - type Target = i32; - fn deref(&self) -> &Self::Target { - &self.0.value() - } - } - - impl std::fmt::Display for #struct_name { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0.value()) - } - } - }; - - g.into() -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn it_works() { - // let result = add(2, 2); - // assert_eq!(result, 4); - } -}