this-rs 0.0.9

Framework for building complex multi-entity REST and GraphQL APIs with many relationships
Documentation
# 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

| Aspect | Entity Routes | Link Routes |
|--------|--------------|-------------|
| **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