executor_pattern/
executor_pattern.rs

1//! Executor pattern example demonstrating ApiExecutor for stateful operations.
2//!
3//! This example shows the benefits of using ApiExecutor for managing state
4//! across multiple operations:
5//! - Centralized context management
6//! - Ergonomic API with consistent patterns
7//! - Multi-family API usage (users and products)
8//! - Context sharing and state inspection
9//!
10//! Run with: `cargo run --example executor_pattern`
11
12use apithing::{ApiExecutor, ApiOperation};
13use std::collections::HashMap;
14
15// Simple User API for this example
16#[derive(Debug, Clone)]
17struct CreateUserProps {
18    name: String,
19    email: String,
20}
21
22#[derive(Debug, Clone)]
23struct FindUserProps {
24    user_id: u64,
25}
26
27#[derive(Debug, Clone)]
28struct User {
29    id: u64,
30    name: String,
31    email: String,
32}
33
34#[derive(Debug)]
35enum UserError {
36    InvalidEmail,
37    NotFound,
38}
39
40// Custom context for this example
41#[derive(Debug)]
42struct AppContext {
43    connection_pool: String,
44    transaction_count: u32,
45    cache: HashMap<String, String>,
46}
47
48impl AppContext {
49    fn new(connection: String) -> Self {
50        Self {
51            connection_pool: connection,
52            transaction_count: 0,
53            cache: HashMap::new(),
54        }
55    }
56
57    fn increment_transaction(&mut self) {
58        self.transaction_count += 1;
59    }
60
61    fn transaction_count(&self) -> u32 {
62        self.transaction_count
63    }
64
65    fn connection_pool(&self) -> &str {
66        &self.connection_pool
67    }
68
69    fn cache(&self) -> &HashMap<String, String> {
70        &self.cache
71    }
72
73    fn cache_mut(&mut self) -> &mut HashMap<String, String> {
74        &mut self.cache
75    }
76}
77
78struct CreateUser;
79struct FindUser;
80
81impl ApiOperation<AppContext, CreateUserProps> for CreateUser {
82    type Output = User;
83    type Error = UserError;
84
85    fn execute(context: &mut AppContext, parameters: &CreateUserProps) -> Result<User, UserError> {
86        if !parameters.email.contains('@') {
87            return Err(UserError::InvalidEmail);
88        }
89
90        context.increment_transaction();
91        let user = User {
92            id: context.transaction_count() as u64,
93            name: parameters.name.clone(),
94            email: parameters.email.clone(),
95        };
96
97        let cache_key = format!("user_{}", user.id);
98        let cache_value = format!("{}:{}", user.name, user.email);
99        context.cache_mut().insert(cache_key, cache_value);
100
101        Ok(user)
102    }
103}
104
105impl ApiOperation<AppContext, FindUserProps> for FindUser {
106    type Output = User;
107    type Error = UserError;
108
109    fn execute(context: &mut AppContext, parameters: &FindUserProps) -> Result<User, UserError> {
110        let cache_key = format!("user_{}", parameters.user_id);
111
112        if let Some(cached_data) = context.cache().get(&cache_key) {
113            let parts: Vec<&str> = cached_data.split(':').collect();
114            if parts.len() == 2 {
115                return Ok(User {
116                    id: parameters.user_id,
117                    name: parts[0].to_string(),
118                    email: parts[1].to_string(),
119                });
120            }
121        }
122
123        Err(UserError::NotFound)
124    }
125}
126
127// Simple Product API for this example
128#[derive(Debug, Clone)]
129struct CreateProductProps {
130    name: String,
131    price: f64,
132    category: String,
133}
134
135#[derive(Debug, Clone)]
136struct FindProductProps {
137    product_id: u64,
138}
139
140#[derive(Debug, Clone)]
141struct Product {
142    id: u64,
143    name: String,
144    price: f64,
145    category: String,
146}
147
148#[derive(Debug)]
149enum ProductError {
150    InvalidPrice,
151    NotFound,
152}
153
154struct CreateProduct;
155struct FindProduct;
156
157impl ApiOperation<AppContext, CreateProductProps> for CreateProduct {
158    type Output = Product;
159    type Error = ProductError;
160
161    fn execute(
162        context: &mut AppContext,
163        parameters: &CreateProductProps,
164    ) -> Result<Product, ProductError> {
165        if parameters.price <= 0.0 || parameters.price.is_nan() {
166            return Err(ProductError::InvalidPrice);
167        }
168
169        context.increment_transaction();
170        let product = Product {
171            id: context.transaction_count() as u64,
172            name: parameters.name.clone(),
173            price: parameters.price,
174            category: parameters.category.clone(),
175        };
176
177        let cache_key = format!("product_{}", product.id);
178        let cache_value = format!("{}:{}:{}", product.name, product.price, product.category);
179        context.cache_mut().insert(cache_key, cache_value);
180
181        Ok(product)
182    }
183}
184
185impl ApiOperation<AppContext, FindProductProps> for FindProduct {
186    type Output = Product;
187    type Error = ProductError;
188
189    fn execute(
190        context: &mut AppContext,
191        parameters: &FindProductProps,
192    ) -> Result<Product, ProductError> {
193        let cache_key = format!("product_{}", parameters.product_id);
194
195        if let Some(cached_data) = context.cache().get(&cache_key) {
196            let parts: Vec<&str> = cached_data.split(':').collect();
197            if parts.len() == 3 {
198                if let Ok(price) = parts[1].parse::<f64>() {
199                    return Ok(Product {
200                        id: parameters.product_id,
201                        name: parts[0].to_string(),
202                        price,
203                        category: parts[2].to_string(),
204                    });
205                }
206            }
207        }
208
209        Err(ProductError::NotFound)
210    }
211}
212
213fn main() {
214    println!("šŸ”§ ApiThing Executor Pattern Example");
215    println!("====================================\n");
216
217    // Create an executor that manages the context for us
218    let mut executor = ApiExecutor::new(AppContext::new("production_db".to_string()));
219
220    println!("šŸ—ļø  Created ApiExecutor with context");
221    println!("šŸ“Š Connection: {}", executor.context().connection_pool());
222    println!(
223        "šŸ”¢ Initial transaction count: {}\n",
224        executor.context().transaction_count()
225    );
226
227    // === User Operations ===
228    println!("šŸ‘„ USER OPERATIONS");
229    println!("==================");
230
231    // Create multiple users using the executor
232    let users = vec![
233        ("Alice Johnson", "alice@company.com"),
234        ("Bob Smith", "bob@company.com"),
235        ("Carol Davis", "carol@company.com"),
236    ];
237
238    let mut created_users = Vec::new();
239    for (name, email) in users {
240        match executor.execute(
241            CreateUser,
242            &CreateUserProps {
243                name: name.to_string(),
244                email: email.to_string(),
245            },
246        ) {
247            Ok(user) => {
248                println!("āœ… Created user: {} (ID: {})", user.name, user.id);
249                created_users.push(user);
250            }
251            Err(e) => {
252                println!("āŒ Failed to create user {}: {:?}", name, e);
253                return;
254            }
255        }
256    }
257
258    println!(
259        "šŸ”¢ Transaction count after user creation: {}\n",
260        executor.context().transaction_count()
261    );
262
263    // === Product Operations (Same Context) ===
264    println!("šŸ“¦ PRODUCT OPERATIONS");
265    println!("====================");
266
267    // Create products using the same executor/context
268    let products = vec![
269        ("Laptop Pro", 1299.99, "Electronics"),
270        ("Office Chair", 249.50, "Furniture"),
271        ("Coffee Mug", 12.99, "Kitchen"),
272    ];
273
274    let mut created_products = Vec::new();
275    for (name, price, category) in products {
276        match executor.execute(
277            CreateProduct,
278            &CreateProductProps {
279                name: name.to_string(),
280                price,
281                category: category.to_string(),
282            },
283        ) {
284            Ok(product) => {
285                println!(
286                    "āœ… Created product: {} (ID: {}, Price: ${:.2})",
287                    product.name, product.id, product.price
288                );
289                created_products.push(product);
290            }
291            Err(e) => {
292                println!("āŒ Failed to create product {}: {:?}", name, e);
293                return;
294            }
295        }
296    }
297
298    println!(
299        "šŸ”¢ Transaction count after product creation: {}\n",
300        executor.context().transaction_count()
301    );
302
303    // === Cross-Family Operations ===
304    println!("šŸ”„ CROSS-FAMILY LOOKUPS");
305    println!("=======================");
306
307    // Find users and products using the same context
308    // This demonstrates how the cache works across different API families
309
310    println!("šŸ” Looking up created entities...");
311
312    // Find first user
313    if let Some(user) = created_users.first() {
314        match executor.execute(FindUser, &FindUserProps { user_id: user.id }) {
315            Ok(found_user) => println!("šŸ‘¤ Found user: {} ({})", found_user.name, found_user.email),
316            Err(e) => println!("āŒ Failed to find user: {:?}", e),
317        }
318    }
319
320    // Find first product
321    if let Some(product) = created_products.first() {
322        match executor.execute(
323            FindProduct,
324            &FindProductProps {
325                product_id: product.id,
326            },
327        ) {
328            Ok(found_product) => println!(
329                "šŸ“¦ Found product: {} (${:.2})",
330                found_product.name, found_product.price
331            ),
332            Err(e) => println!("āŒ Failed to find product: {:?}", e),
333        }
334    }
335
336    // === Context Inspection ===
337    println!("\nšŸ“Š CONTEXT INSPECTION");
338    println!("=====================");
339
340    let context = executor.context();
341    println!(
342        "šŸ”¢ Final transaction count: {}",
343        context.transaction_count()
344    );
345    println!("šŸ’¾ Cache entries: {} items", context.cache().len());
346
347    // Show cache contents
348    println!("šŸ“‹ Cached items:");
349    for (key, value) in context.cache().iter() {
350        // Truncate long values for display
351        let display_value = if value.len() > 50 {
352            format!("{}...", &value[..47])
353        } else {
354            value.clone()
355        };
356        println!("   • {}: {}", key, display_value);
357    }
358
359    // === Executor Benefits Demo ===
360    println!("\nšŸ’” EXECUTOR BENEFITS");
361    println!("====================");
362
363    // Show how we can access the context for debugging or inspection
364    let final_context = executor.context();
365    println!("āœ… Centralized state management:");
366    println!(
367        "   • {} total operations executed",
368        final_context.transaction_count()
369    );
370    println!("   • {} entities cached", final_context.cache().len());
371    println!("   • Single context shared across {} API families", 2);
372
373    println!("\nšŸŽ‰ Executor pattern example completed successfully!");
374    println!("šŸ’” Key advantages of ApiExecutor:");
375    println!("   • Ergonomic API: executor.execute(Operation, &parameters)");
376    println!("   • Centralized context management with owned context");
377    println!("   • Consistent patterns across different API families");
378    println!("   • Easy context inspection and debugging");
379    println!("   • Efficient state sharing (cache, connections, etc.)");
380}