1use karpal_std::prelude::*;
9
10#[derive(Debug, Clone, PartialEq)]
13struct Order {
14 id: u32,
15 customer: Customer,
16 items: Vec<Item>,
17 payment: Payment,
18}
19
20#[derive(Debug, Clone, PartialEq)]
21struct Customer {
22 name: String,
23 address: Address,
24}
25
26#[derive(Debug, Clone, PartialEq)]
27struct Address {
28 street: String,
29 city: String,
30 zip: String,
31}
32
33#[derive(Debug, Clone, PartialEq)]
34struct Item {
35 name: String,
36 price_cents: i64,
37 quantity: u32,
38}
39
40#[derive(Debug, Clone, PartialEq)]
41enum Payment {
42 CreditCard {
43 last4: String,
44 exp: String,
45 },
46 BankTransfer {
47 iban: String,
48 },
49 Wallet {
50 provider: String,
51 balance_cents: i64,
52 },
53}
54
55fn customer_lens() -> SimpleLens<Order, Customer> {
58 Lens::new(
59 |o: &Order| o.customer.clone(),
60 |o, customer| Order { customer, ..o },
61 )
62}
63
64fn address_lens() -> SimpleLens<Customer, Address> {
65 Lens::new(
66 |c: &Customer| c.address.clone(),
67 |c, address| Customer { address, ..c },
68 )
69}
70
71fn city_lens() -> SimpleLens<Address, String> {
72 Lens::new(
73 |a: &Address| a.city.clone(),
74 |a, city| Address { city, ..a },
75 )
76}
77
78fn zip_lens() -> SimpleLens<Address, String> {
79 Lens::new(|a: &Address| a.zip.clone(), |a, zip| Address { zip, ..a })
80}
81
82fn name_lens() -> SimpleLens<Customer, String> {
83 Lens::new(
84 |c: &Customer| c.name.clone(),
85 |c, name| Customer { name, ..c },
86 )
87}
88
89fn credit_card_prism() -> SimplePrism<Payment, (String, String)> {
92 Prism::new(
93 |p| match p {
94 Payment::CreditCard { last4, exp } => Ok((last4, exp)),
95 other => Err(other),
96 },
97 |(last4, exp)| Payment::CreditCard { last4, exp },
98 )
99}
100
101fn wallet_prism() -> SimplePrism<Payment, (String, i64)> {
102 Prism::new(
103 |p| match p {
104 Payment::Wallet {
105 provider,
106 balance_cents,
107 } => Ok((provider, balance_cents)),
108 other => Err(other),
109 },
110 |(provider, balance_cents)| Payment::Wallet {
111 provider,
112 balance_cents,
113 },
114 )
115}
116
117fn bank_transfer_prism() -> SimplePrism<Payment, String> {
118 Prism::new(
119 |p| match p {
120 Payment::BankTransfer { iban } => Ok(iban),
121 other => Err(other),
122 },
123 |iban| Payment::BankTransfer { iban },
124 )
125}
126
127fn sample_order() -> Order {
130 Order {
131 id: 1001,
132 customer: Customer {
133 name: "Alice Smith".into(),
134 address: Address {
135 street: "123 Main St".into(),
136 city: "Springfield".into(),
137 zip: "62701".into(),
138 },
139 },
140 items: vec![
141 Item {
142 name: "Keyboard".into(),
143 price_cents: 7999,
144 quantity: 1,
145 },
146 Item {
147 name: "USB Cable".into(),
148 price_cents: 899,
149 quantity: 3,
150 },
151 ],
152 payment: Payment::CreditCard {
153 last4: "4242".into(),
154 exp: "12/25".into(),
155 },
156 }
157}
158
159fn main() {
160 println!("=== Domain Model with Optics Example ===\n");
161
162 let order = sample_order();
163
164 println!("--- Lens: basic get/set/over ---");
166 let customer = customer_lens();
167 let address = address_lens();
168 let city = city_lens();
169
170 println!(" Customer name: {}", name_lens().get(&order.customer));
171 println!(" City: {}", city.get(&address.get(&customer.get(&order))));
172
173 println!("\n--- ComposedLens: deep access with .then() ---");
175 let order_city = customer_lens().then(address_lens()).then(city_lens());
176 let order_zip = customer_lens().then(address_lens()).then(zip_lens());
177
178 println!(" Order city: {}", order_city.get(&order));
179 println!(" Order zip: {}", order_zip.get(&order));
180
181 let updated = order_city.set(order.clone(), "Shelbyville".into());
183 println!(" After city update: {}", order_city.get(&updated));
184 println!(" Original unchanged: {}", order_city.get(&order));
185
186 let uppercased = order_city.over(order.clone(), |c| c.to_uppercase());
188 println!(" Uppercased city: {}", order_city.get(&uppercased));
189
190 println!("\n--- Lens::transform — reusable update function ---");
192 let normalize_city: Box<dyn Fn(String) -> String> = Box::new(|c| c.trim().to_uppercase());
193 let normalize_order_city = city_lens().transform::<FnP>(normalize_city);
194 let addr = Address {
195 street: "456 Oak Ave".into(),
196 city: " new york ".into(),
197 zip: "10001".into(),
198 };
199 let normalized = normalize_order_city(addr);
200 println!(" Normalized city: {:?}", normalized.city);
201
202 println!("\n--- Prism: sum type focus ---");
204 let cc = credit_card_prism();
205 let wallet = wallet_prism();
206 let bank = bank_transfer_prism();
207
208 println!(" Credit card last4: {:?}", cc.preview(&order.payment));
209 println!(" Wallet preview: {:?}", wallet.preview(&order.payment));
210
211 let new_payment = wallet.review(("PayPal".into(), 5000));
213 println!(" New wallet payment: {:?}", new_payment);
214
215 let updated_payment = cc.over(order.payment.clone(), |(last4, _exp)| {
217 (last4, "01/28".into())
218 });
219 println!(" Updated CC expiry: {:?}", updated_payment);
220
221 let unchanged = wallet.over(order.payment.clone(), |(prov, bal)| (prov, bal + 1000));
223 println!(" Wallet over on CC (unchanged): {:?}", unchanged);
224
225 println!("\n--- Prism::transform — reusable variant modifier ---");
227 let add_balance: Box<dyn Fn((String, i64)) -> (String, i64)> =
228 Box::new(|(prov, bal)| (prov, bal + 2500));
229 let add_wallet_balance = wallet.transform::<FnP>(add_balance);
230
231 let wallet_payment = Payment::Wallet {
232 provider: "PayPal".into(),
233 balance_cents: 10000,
234 };
235 let topped_up = add_wallet_balance(wallet_payment);
236 println!(" Topped up: {:?}", topped_up);
237
238 let still_cc = add_wallet_balance(order.payment.clone());
240 println!(" CC unchanged: {:?}", still_cc);
241
242 println!("\n--- Combining lenses and prisms ---");
244 let orders = vec![
245 sample_order(),
246 Order {
247 id: 1002,
248 customer: Customer {
249 name: "Bob Jones".into(),
250 address: Address {
251 street: "789 Elm St".into(),
252 city: "Capital City".into(),
253 zip: "12345".into(),
254 },
255 },
256 items: vec![Item {
257 name: "Mouse".into(),
258 price_cents: 2999,
259 quantity: 1,
260 }],
261 payment: Payment::Wallet {
262 provider: "Stripe".into(),
263 balance_cents: 50000,
264 },
265 },
266 Order {
267 id: 1003,
268 customer: Customer {
269 name: "Carol White".into(),
270 address: Address {
271 street: "321 Pine Rd".into(),
272 city: "Springfield".into(),
273 zip: "62702".into(),
274 },
275 },
276 items: vec![],
277 payment: Payment::BankTransfer {
278 iban: "DE89370400440532013000".into(),
279 },
280 },
281 ];
282
283 let order_city_lens = customer_lens().then(address_lens()).then(city_lens());
284 println!(" All cities:");
285 for o in &orders {
286 let payment_type = match &o.payment {
287 Payment::CreditCard { .. } => "CC",
288 Payment::BankTransfer { .. } => "Bank",
289 Payment::Wallet { .. } => "Wallet",
290 };
291 println!(
292 " Order #{}: {} ({}, pays via {})",
293 o.id,
294 order_city_lens.get(o),
295 name_lens().get(&o.customer),
296 payment_type
297 );
298 }
299
300 println!("\n Bank IBANs:");
302 for o in &orders {
303 if let Some(iban) = bank.preview(&o.payment) {
304 println!(" Order #{}: {}", o.id, iban);
305 }
306 }
307}