Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ target/

/target
/Cargo.lock
/.idea
/.idea
tagged-core/.idea/vcs.xml
tagged-core/.idea/tagged-core.iml
tagged-core/.idea/modules.xml
38 changes: 9 additions & 29 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rust-tagged"
version = "0.1.0"
version = "0.4.0"
edition = "2024"
authors = ["Codefonsi <info@codefonsi.com>"]
license = "MPL-2.0"
Expand All @@ -17,45 +17,25 @@ include = ["src/**/*", "Cargo.toml", "../../README.md", "LICENSE"]
[workspace]
resolver = "3" # or "3"
members = [
"tagged-chrono",
"tagged-core",
"tagged-macros",
]

#[lib]
#path = "crates/tagged-core/src/lib.rs"

#[workspace.dependencies]
#tagged-core = {path = "crates/tagged-core"}
#

[patch.crates-io]
tagged-core = { path = "tagged-core" }

[dependencies]
tagged-core = { path = "tagged-core", version = "0.1.0", features = ["serde"] }
tagged-core = { path = "tagged-core", version = "0.4.0", features = ["serde"] }

[dev-dependencies]
serde = { version = "1.0.219", features = ["derive"] }
#serde = { version = "1.0", features = ["derive"], optional = true}
serde_json = "1.0.140"
serde_json = {version = "1.0.140"}


[workspace.dependencies]
#tagged-core = { path = "crates/tagged-core", version = "0.1.0" }
#serde = { version = "1.0", optional = true, features = ["derive"] }
#uuid = { version = "1.4", optional = true }
#chrono = { version = "0.4", optional = true }

#[dependencies]
#tagged-core.workspace = true
#serde = { version = "1.0", features = ["derive"] }
#uuid = { version = "1.4" }
#chrono = { version = "0.4" }

# crates/tagged-core/Cargo.toml


[features]
default = []
serde = ["tagged-core/serde"]
full = ["serde"]
#serde = ["dep:serde"]
#serde = ["dep:serde"]
#uuid = ["dep:uuid"]
#chrono = ["dep:chrono"]
full = ["serde"]
162 changes: 105 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# rust-tagged
# rust-tagged (v0.4.0)

> A lightweight, extensible system for creating type-safe IDs, email addresses, and domain-specific values using Rust's type system.

Expand Down Expand Up @@ -26,31 +26,114 @@
* Lightweight `Tagged<T, Tag>` abstraction
* `From<T>` and `Into<T>` implementations for easy use
* Optional `Deref`, `Display`, `Serialize`, and `Deserialize` support
* Custom derive macro `#[derive(Tagged)]`

---

## 🛠 Installation

```toml
[dependencies]
rust-tagged = "0.1"
rust-tagged = "0.4.0"
```

To enable serde support:

```toml
[dependencies.rust-tagged]
version = "0.1"
version = "0.4.0"
features = ["serde"]
```
## 🧪 Example - Debug

```rust
use tagged_core::Tagged;

#[derive(Debug)]
struct UserIdTag {
a: Tagged<u32, Self>,
b: Tagged<u32, Self>,
}

fn main() {
let instance = UserIdTag { a: 1.into(), b: 2.into() };
println!("{}", instance.a);
println!("{:?}", instance.b);
}
```

---

## 🔐 Example - Hash

```rust
use tagged_core::Tagged;
use std::collections::HashSet;

#[derive(Clone, Hash, Debug, PartialEq, Eq)]
struct User {
id: Tagged<String, Self>,
}

fn main() {
let mut s: HashSet<User> = HashSet::new();
let user = User { id: "me@example.com".into() };
s.insert(user.clone());

assert!(s.contains(&user));
}
```

---

## 🔁 Example - Iter

```rust
use tagged_core::Tagged;

#[derive(Debug)]
struct Org;

type EmployeeNames = Tagged<Vec<String>, Org>;

fn main() {
let names: EmployeeNames = Tagged::new(vec!["Alice".into(), "Bob".into()]);

for name in &names {
println!("Name: {name}");
}

for name in names {
println!("Owned: {name}");
}
}
```

---

## ✏️ Example - Mutation

```rust
use tagged_core::Tagged;

#[derive(Debug)]
struct Org;

type OrgName = Tagged<String, Org>;

fn main() {
let mut name = OrgName::new("Codefonsi".into());
name.set("New Org Name".into());

println!("Updated Org Name: {}", name.value());
}
```

---

## 📦 Custom `Tagged<T>` API

```rust
use rust_tagged::{Tagged};
use tagged_core::Tagged;

#[derive(Debug)]
struct EmailTag;
Expand All @@ -67,10 +150,12 @@ fn main() {
}
```

---

## 🔰 Getting Started (Easy)

```rust
use rust_tagged::Tagged;
use tagged_core::Tagged;

struct Employee {
id: Tagged<i32, Self>,
Expand All @@ -84,19 +169,18 @@ struct Org {
name: String,
}

fn send_mail_employee(mail_id: &Tagged<String, crate::Employee>, message: &str) {
fn send_mail_employee(mail_id: &Tagged<String, Employee>, message: &str) {
send_mail(mail_id, message);
}

fn send_mail_org(mail_id: &Tagged<String, crate::Org>, message: &str) {
fn send_mail_org(mail_id: &Tagged<String, Org>, 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(),
Expand All @@ -108,48 +192,30 @@ fn main() {
},
};

// here we can clearly define and distinct the mail id of employee and org
// without
// // expected `&Tagged<String, Org>`, but found `&Tagged<String, Employee>`
// 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<String, Employee>`, but found `&Tagged<String, Org>`
// send_mail_employee(&emp.org.org_email_id, "This is supposed to send to user but there is no type safety at compile time");
//
// // after refactoring
// // the trait bound `Tagged<String, Employee>: From<Tagged<String, Org>>` is not satisfied [E0277]
// send_mail_employee(&emp.org.org_email_id.into(), "This is ok");

// We don't need review and refactoring the code for runtime mistakes.
send_mail_org(&emp.org.org_email_id, "This is ok");
send_mail_employee(&emp.employee_email_id, "This is ok");


}
```

### ✅ Output

```
UserId: 42
Email: user@example.com
Mail Sent.This is ok
Mail Sent.This is ok
```

---

## Coming Soon
## 🧱 Medium: Nesting in Domain Models

```rust
use rust_tagged::*;
use rust_tagged_macros::Tagged;
use tagged_core::*;
use uuid::Uuid;

#[derive(Tagged)]
pub struct OrgId(Tagged<Uuid, Org>);
struct Org;

#[derive(Tagged)]
pub struct OrgEmail(Tagged<String, Org>);
type OrgId = Tagged<Uuid, Org>;
type OrgEmail = Tagged<String, Org>;

#[derive(Debug)]
struct Organization {
Expand All @@ -170,20 +236,17 @@ fn main() {

---

## Coming Soon
## Timestamped Resources with `chrono` + `serde`
## 🔒 Hard: Timestamped Resources with `chrono` + `serde`

```rust
use rust_tagged::*;
use rust_tagged_macros::Tagged;
use tagged_core::*;
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};

#[derive(Tagged, Serialize, Deserialize)]
pub struct CreatedAt(Tagged<DateTime<Utc>, Audit>);
struct Audit;

#[derive(Tagged, Serialize, Deserialize)]
pub struct UpdatedAt(Tagged<DateTime<Utc>, Audit>);
type CreatedAt = Tagged<DateTime<Utc>, Audit>;
type UpdatedAt = Tagged<DateTime<Utc>, Audit>;

#[derive(Serialize, Deserialize, Debug)]
struct BlogPost {
Expand All @@ -207,25 +270,10 @@ fn main() {
---


---

## Coming Soon
## 🔌 Serde Integration

When enabled via `features = ["serde"]`, tagged types auto-serialize like their inner types.

```rust
#[derive(Tagged, Serialize, Deserialize)]
pub struct UserId(Tagged<i32, User>);

let id = UserId::from(10);
let json = serde_json::to_string(&id)?; // "10"
```

---

## 📃 License

Licensed under either of
Licensed under either of:

* Mozilla Public License 2.0
2 changes: 0 additions & 2 deletions examples/Iter_example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,4 @@ Name: Alice
Name: Bob
Owned: Alice
Owned: Bob

Process finished with exit code 0
*/
Loading
Loading