Skip to content

Commit f1e1d79

Browse files
authored
Merge pull request #7 from akashsoni01/procmacros
Procmacros
2 parents d5848ed + bc4b203 commit f1e1d79

20 files changed

+1887
-90
lines changed

Cargo.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "rust-key-paths"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
edition = "2024"
55
authors = ["Codefonsi <[email protected]>"]
66
license = "MPL-2.0"
@@ -13,18 +13,20 @@ readme = "./README.md"
1313
include = ["src/**/*", "Cargo.toml", "../../README.md", "LICENSE"]
1414

1515
[dependencies]
16-
key-paths-core = { path = "key-paths-core", version = "0.6.0" }
16+
key-paths-core = { path = "key-paths-core", version = "0.7.0" }
17+
key-paths-derive = { path = "key-paths-derive", version = "0.1.0"}
1718

1819

1920
[workspace]
2021
resolver = "3" # or "3"
2122
members = [
22-
"key-paths-core"
23+
"key-paths-core",
24+
"key-paths-derive"
2325
]
2426

2527
[patch.crates-io]
2628
key-paths-core = { path = "key-paths-core" }
27-
29+
key-paths-derive = { path = "key-paths-derive" }
2830

2931
[features]
3032
default = []

README.md

Lines changed: 76 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,92 @@
11
# 🔑 KeyPaths & CasePaths in Rust
22

33
Key paths and case paths provide a **safe, composable way to access and modify nested data** in Rust.
4-
Inspired by **Swift’s KeyPath / CasePath** system, this crate lets you work with **struct fields** and **enum variants** as *first-class values*.
4+
Inspired by **Swift’s KeyPath / CasePath** system, this this feature rich crate lets you work with **struct fields** and **enum variants** as *first-class values*.
55

66
---
77

88
## ✨ Features
99

10-
***ReadableKeyPath** → safely read struct fields.
11-
***WritableKeyPath** → safely read/write struct fields.
12-
***EnumKeyPath (CasePaths)** → extract and embed enum variants.
13-
***Composable** → chain key paths together(Upcoming).
14-
***Iterable** → iterate or mutate values across collections.
15-
***Macros** → concise `readable_keypath!`, `writable_keypath!`, `enum_keypath!`.
10+
-**Readable/Writable keypaths** for struct fields
11+
-**Failable keypaths** for `Option<T>` chains (`_fr`/`_fw`)
12+
-**Enum CasePaths** (readable and writable prisms)
13+
-**Composition** across structs, options and enum cases
14+
-**Iteration helpers** over collections via keypaths
15+
-**Proc-macros**: `#[derive(Keypaths)]` for structs/tuple-structs and enums, `#[derive(Casepaths)]` for enums
1616

1717
---
1818

1919
## 📦 Installation
2020

2121
```toml
2222
[dependencies]
23-
key_paths_core = "0.6"
23+
key-paths-core = "0.6"
24+
key-paths-derive = "0.1"
2425
```
2526

2627
---
2728

28-
## 🚀 Examples - Go to latest examples directory docs will be updated later
29+
## 🚀 Examples
2930

30-
### 1. CasePaths with Enums
31+
See `examples/` for many runnable samples. Below are a few highlights.
32+
33+
### 1) Structs with #[derive(Keypaths)]
34+
35+
```rust
36+
use key_paths_core::KeyPaths;
37+
use key_paths_derive::Keypaths;
38+
39+
#[derive(Debug, Keypaths)]
40+
struct Size { width: u32, height: u32 }
41+
42+
#[derive(Debug, Keypaths)]
43+
struct Rectangle { size: Size, name: String }
44+
45+
fn main() {
46+
let mut rect = Rectangle { size: Size { width: 30, height: 50 }, name: "MyRect".into() };
47+
48+
// Readable/writable
49+
println!("width = {:?}", Size::width_r().get(&rect.size));
50+
if let Some(w) = Size::width_w().get_mut(&mut rect.size) { *w += 10; }
51+
52+
// Compose: Rectangle -> Size -> width
53+
let rect_width = Rectangle::size_r().compose(Size::width_r());
54+
println!("rect.width = {:?}", rect_width.get(&rect));
55+
}
56+
```
57+
58+
### 2) Optional chaining (failable keypaths)
59+
60+
```rust
61+
use key_paths_core::KeyPaths;
62+
use key_paths_derive::Keypaths;
63+
64+
#[derive(Debug, Keypaths)]
65+
struct Engine { horsepower: u32 }
66+
#[derive(Debug, Keypaths)]
67+
struct Car { engine: Option<Engine> }
68+
#[derive(Debug, Keypaths)]
69+
struct Garage { car: Option<Car> }
70+
71+
fn main() {
72+
let mut g = Garage { car: Some(Car { engine: Some(Engine { horsepower: 120 }) }) };
73+
74+
// Read horsepower if present
75+
let hp = Garage::car_fr()
76+
.compose(Car::engine_fr())
77+
.compose(Engine::horsepower_r());
78+
println!("hp = {:?}", hp.get(&g));
79+
80+
// Mutate horsepower if present
81+
if let Some(car) = Garage::car_fw().get_mut(&mut g) {
82+
if let Some(engine) = Car::engine_fw().get_mut(car) {
83+
if let Some(hp) = Engine::horsepower_w().get_mut(engine) { *hp += 30; }
84+
}
85+
}
86+
}
87+
```
88+
89+
### 3) Enum CasePaths (readable/writable prisms)
3190

3291
```rust
3392
use key_paths_core::KeyPaths;
@@ -38,7 +97,7 @@ enum Payment {
3897
}
3998

4099
fn main() {
41-
let kp = KeyPaths::writable_enum(
100+
let kp = KeyPaths::writable_enum(
42101
|v| Payment::Cash { amount: v },
43102
|p: &Payment| match p {
44103
Payment::Cash { amount } => Some(amount),
@@ -64,74 +123,7 @@ fn main() {
64123

65124
---
66125

67-
### 2. Readable KeyPaths - helper macros wip
68-
69-
```rust
70-
use key_paths_core::KeyPaths;
71-
72-
#[derive(Debug)]
73-
struct Size {
74-
width: u32,
75-
height: u32,
76-
}
77-
78-
#[derive(Debug)]
79-
struct Rectangle {
80-
size: Size,
81-
name: String,
82-
}
83-
84-
fn main() {
85-
let mut rect = Rectangle {
86-
size: Size {
87-
width: 30,
88-
height: 50,
89-
},
90-
name: "MyRect".into(),
91-
};
92-
93-
let width_direct = KeyPaths::readable(|r: &Rectangle| &r.size.width);
94-
println!("Width: {:?}", width_direct.get(&rect));
95-
}
96-
```
97-
98-
---
99-
100-
### 3. Writable KeyPaths - helper macros wip
101-
102-
```rust
103-
use key_paths_core::KeyPaths;
104-
105-
#[derive(Debug)]
106-
struct Size {
107-
width: u32,
108-
height: u32,
109-
}
110-
#[derive(Debug)]
111-
struct Rectangle {
112-
size: Size,
113-
name: String,
114-
}
115-
fn main() {
116-
let mut rect = Rectangle {
117-
size: Size {
118-
width: 30,
119-
height: 50,
120-
},
121-
name: "MyRect".into(),
122-
};
123-
let width_mut = KeyPaths::writable(
124-
|r: &mut Rectangle| &mut r.size.width,
125-
);
126-
// Mutable
127-
if let Some(hp_mut) = width_mut.get_mut(&mut rect) {
128-
*hp_mut += 50;
129-
}
130-
println!("Updated rectangle: {:?}", rect);
131-
}
132-
```
133-
134-
### 4. Composability and failablity
126+
### 4) Compose enum prisms with struct fields
135127
```rust
136128
use key_paths_core::KeyPaths;
137129

@@ -170,7 +162,7 @@ fn main() {
170162
println!("{garage:?}");
171163
}
172164
```
173-
### 4. Mutablity
165+
### 5) Iteration via keypaths
174166
```rust
175167
use key_paths_core::KeyPaths;
176168

@@ -264,10 +256,10 @@ ABox { name: "A box", size: Size { width: 10, height: 20 }, color: Other(RGBU8(0
264256

265257
## 🛠 Roadmap
266258

267-
* [ ] `compose` support for combining multiple key paths.
268-
* [ ] Derive macros for automatic KeyPath generation (Upcoming).
269-
* [ ] Nested struct & enum traversal.
270-
* [ ] Optional chaining (`User?.profile?.name`) with failable.
259+
- [x] Compose across structs, options and enum cases
260+
- [x] Derive macros for automatic keypath generation
261+
- [x] Optional chaining with failable keypaths
262+
- [ ] Derive macros for complex multi-field enum variants
271263
---
272264

273265
## 📜 License

examples/basics_macros.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use key_paths_core::KeyPaths;
2+
use key_paths_derive::Keypaths;
3+
4+
#[derive(Debug, Keypaths)]
5+
struct Size {
6+
width: u32,
7+
height: u32,
8+
}
9+
10+
#[derive(Debug, Keypaths)]
11+
struct Rectangle {
12+
size: Size,
13+
name: String,
14+
}
15+
16+
fn main() {
17+
let mut rect = Rectangle {
18+
size: Size {
19+
width: 30,
20+
height: 50,
21+
},
22+
name: "MyRect".into(),
23+
};
24+
25+
// Define readable and writable keypaths.
26+
let size_kp: KeyPaths<Rectangle, Size> = KeyPaths::readable(|r: &Rectangle| &r.size);
27+
let width_kp: KeyPaths<Size, u32> = KeyPaths::readable(|s: &Size| &s.width);
28+
29+
// Compose nested paths (assuming composition is supported).
30+
// e.g., rect[&size_kp.then(&width_kp)] — hypothetical chaining
31+
32+
// Alternatively, define them directly:
33+
let width_direct: KeyPaths<Rectangle, u32> = KeyPaths::readable(|r: &Rectangle| &r.size.width);
34+
println!("Width: {:?}", width_direct.get(&rect));
35+
36+
// Writable keypath for modifying fields:
37+
let width_mut: KeyPaths<Rectangle, u32> = KeyPaths::writable(
38+
// |r: &Rectangle| &r.size.width,
39+
|r: &mut Rectangle| &mut r.size.width,
40+
);
41+
// Mutable
42+
if let Some(hp_mut) = width_mut.get_mut(&mut rect) {
43+
*hp_mut += 50;
44+
}
45+
println!("Updated rectangle: {:?}", rect);
46+
47+
// Keypaths from derive-generated methods
48+
let rect_size_fw = Rectangle::size_fw();
49+
let rect_name_fw = Rectangle::name_fw();
50+
let size_width_fw = Size::width_fw();
51+
let size_height_fw = Size::height_fw();
52+
53+
let name_readable = Rectangle::name_r();
54+
println!("Name (readable): {:?}", name_readable.get(&rect));
55+
56+
let size_writable = Rectangle::size_w();
57+
if let Some(s) = size_writable.get_mut(&mut rect) {
58+
s.width += 1;
59+
}
60+
61+
// Use them
62+
if let Some(s) = rect_size_fw.get_mut(&mut rect) {
63+
if let Some(w) = size_width_fw.get_mut(s) {
64+
*w += 5;
65+
}
66+
if let Some(h) = size_height_fw.get_mut(s) {
67+
*h += 10;
68+
}
69+
}
70+
if let Some(name) = rect_name_fw.get_mut(&mut rect) {
71+
name.push_str("_fw");
72+
}
73+
println!("After failable updates: {:?}", rect);
74+
}

0 commit comments

Comments
 (0)