# Explanation: How Routing Works in this-rs
## 🎯 Question
> How does this-rs achieve automatic route generation?
## 📝 Answer
this-rs uses a **two-tier routing system**: entity-specific routes (declared per entity) and generic link routes (fully automatic). Here's how it works.
---
## 🏗️ Two Types of Routes
### 1. Entity CRUD Routes (Entity-Specific)
Each entity declares its own CRUD routes via its `EntityDescriptor`:
```rust
// entities/order/descriptor.rs
impl EntityDescriptor for OrderDescriptor {
fn build_routes(&self) -> Router {
Router::new()
.route("/orders", get(list_orders).post(create_order))
.route("/orders/{id}",
get(get_order)
.put(update_order)
.delete(delete_order))
.with_state(state)
}
}
```
**Why entity-specific?**
- ✅ **Type Safety**: Each entity has strongly-typed handlers
- ✅ **Flexibility**: Easy to customize behavior per entity
- ✅ **Performance**: No runtime dispatch, direct function calls
- ✅ **Clarity**: See exactly what routes each entity provides
### 2. Link Routes (Fully Generic)
Link routes are **completely generic** and work for all entities:
```rust
// src/server/router.rs
pub fn build_link_routes(state: AppState) -> Router {
Router::new()
.route("/links/{link_id}", get(get_link))
.route(
"/{entity_type}/{entity_id}/{route_name}",
get(list_links).post(create_linked_entity),
)
.route(
"/{source_type}/{source_id}/{route_name}/{target_id}",
get(get_link_by_route)
.post(create_link)
.put(update_link)
.delete(delete_link),
)
.route(
"/{entity_type}/{entity_id}/links",
get(list_available_links),
)
.with_state(state)
}
```
**Why generic?**
- ✅ **Universal**: Same routes work for all entity combinations
- ✅ **Configuration-driven**: Behavior defined in YAML
- ✅ **Zero boilerplate**: No code needed per entity
- ✅ **Dynamic resolution**: Routes resolved at runtime via registry
---
## 🔄 How It Works
### ServerBuilder Assembly
```rust
// ServerBuilder.build()
pub fn build(self) -> Result<Router> {
// 1. Build entity-specific routes
let entity_routes = self.entity_registry.build_routes();
// Calls OrderDescriptor.build_routes()
// Calls InvoiceDescriptor.build_routes()
// Calls PaymentDescriptor.build_routes()
// 2. Build generic link routes
let link_routes = build_link_routes(link_state);
// Single set of routes for ALL entities
// 3. Merge both
Ok(entity_routes.merge(link_routes))
}
```
### Result: Complete API
```
Entity Routes (via EntityDescriptor):
GET /orders ← OrderDescriptor
POST /orders ← OrderDescriptor
GET /orders/{id} ← OrderDescriptor
PUT /orders/{id} ← OrderDescriptor
DELETE /orders/{id} ← OrderDescriptor
GET /invoices ← InvoiceDescriptor
POST /invoices ← InvoiceDescriptor
... (same for all entities)
Link Routes (generic, works for all):
GET /{entity}/{id}/{route_name}
POST /{entity}/{id}/{route_name}
GET /{entity}/{id}/{route_name}/{target_id}
POST /{entity}/{id}/{route_name}/{target_id}
PUT /{entity}/{id}/{route_name}/{target_id}
DELETE /{entity}/{id}/{route_name}/{target_id}
```
---
## 🤔 Why Not Fully Generic CRUD Routes?
### Challenges with Fully Generic CRUD
**Approach 1: Generic Handlers with match**
```rust
pub async fn generic_list(
Path(entity_type): Path<String>,
) -> Result<Response, StatusCode> {
match entity_type.as_str() {
"orders" => /* call order store */,
"invoices" => /* call invoice store */,
"payments" => /* call payment store */,
_ => Err(StatusCode::NOT_FOUND),
}
}
```
❌ **Problems**:
- Loses type safety
- Runtime dispatch overhead
- Hard to customize per entity
- Still need to write match cases
**Approach 2: Trait-based Dynamic Dispatch**
```rust
pub trait CrudService<T>: Send + Sync {
async fn list(&self) -> Result<Vec<T>>;
async fn create(&self, entity: T) -> Result<T>;
// ...
}
// Generic handler using trait
pub async fn generic_list(
Path(entity_type): Path<String>,
State(services): State<HashMap<String, Arc<dyn CrudService<???>>>>
) -> Result<Response, StatusCode> {
// Problem: Can't use dyn CrudService<T> with different T types in same HashMap!
}
```
❌ **Problems**:
- Rust type system makes this very difficult
- Would need type erasure (serde_json::Value everywhere)
- Loses compile-time type safety
- Complex implementation for marginal benefit
### Current Approach: Best of Both Worlds
✅ **Entity Routes**: Declared per entity (type-safe, flexible)
✅ **Link Routes**: Fully generic (zero boilerplate)
**Why it works**:
- Entity operations are **entity-specific** by nature
- Link operations are **generic** by nature (work on IDs and types)
---
## 💡 Key Insights
### 1. Different Routes Have Different Needs
**Entity CRUD**:
- Each entity has unique fields
- Different validation rules
- Specific business logic
- → Best handled with entity-specific code
**Link Management**:
- Universal operations (create, read, update, delete)
- Works on UUIDs and entity types (strings)
- Configuration-driven behavior
- → Perfect for generic code
### 2. EntityDescriptor Pattern
The `EntityDescriptor` pattern gives us:
- **Auto-registration**: Entities self-register their routes
- **Type safety**: Strong typing for each entity
- **Flexibility**: Each entity controls its own routes
- **Consistency**: Framework enforces descriptor pattern
```rust
// Adding a new entity = just implement EntityDescriptor
impl EntityDescriptor for ProductDescriptor {
fn build_routes(&self) -> Router {
Router::new()
.route("/products", get(list).post(create))
.route("/products/{id}", get(get_one).put(update).delete(delete))
.with_state(state)
}
}
// Register it
module.register_entities(registry);
// Done! Routes are auto-generated when server builds
```
### 3. LinkRouteRegistry
The `LinkRouteRegistry` enables semantic URLs:
```yaml
# config/links.yaml
links:
- link_type: has_invoice
source_type: order
target_type: invoice
forward_route_name: invoices # ← User-friendly name
reverse_route_name: order
```
```bash
# User accesses:
GET /orders/123/invoices
# Framework resolves:
route_name="invoices" + source_type="order"
→ LinkDefinition { link_type: "has_invoice", target_type: "invoice", ... }
→ Query: find links where source_id=123 and link_type="has_invoice"
```
---
## 📊 Comparison
| **Declaration** | Per entity (EntityDescriptor) | Generic (one set for all) |
| **Type safety** | Full compile-time | Runtime with validation |
| **Customization** | Easy per entity | Configuration-driven |
| **Boilerplate** | ~20 lines per entity | 0 lines |
| **Performance** | Direct function calls | Registry lookup + dispatch |
| **Flexibility** | High (entity-specific logic) | High (YAML configuration) |
---
## 🎯 Conclusion
this-rs uses a **hybrid approach**:
1. **Entity CRUD routes**: Declared per entity via `EntityDescriptor`
- Maintains type safety
- Allows customization
- Self-registering
2. **Link routes**: Fully generic
- Zero boilerplate
- Configuration-driven
- Works for all entities
3. **ServerBuilder**: Combines both automatically
- Collects entity routes from descriptors
- Adds generic link routes
- Merges into complete API
**Result**: Best of both worlds - type safety where needed, zero boilerplate where possible! 🚀🦀✨
---
## 📚 Related Documentation
- [ServerBuilder Implementation](SERVER_BUILDER_IMPLEMENTATION.md)
- [Architecture Overview](ARCHITECTURE.md)
- [Enriched Links](../guides/ENRICHED_LINKS.md)