this/server/router.rs
1//! Router builder utilities for link routes
2
3use crate::core::query::QueryParams;
4use crate::links::handlers::{
5 AppState, create_link, create_linked_entity, delete_link, get_link, get_link_by_route,
6 handle_nested_path_get, list_available_links, list_links, update_link,
7};
8use axum::{Router, extract::Query, routing::get};
9
10/// Build link routes from configuration
11///
12/// These routes are generic and work for all entities using semantic route_names:
13/// - GET /links/{link_id} - Get a specific link by ID
14/// - GET /{entity_type}/{entity_id}/{route_name} - List links (e.g., /users/123/cars-owned)
15/// - POST /{entity_type}/{entity_id}/{route_name} - Create new entity + link (entity + metadata in body)
16/// - GET /{source_type}/{source_id}/{route_name}/{target_id} - Get a specific link (e.g., /users/123/cars-owned/456)
17/// - POST /{source_type}/{source_id}/{route_name}/{target_id} - Create link between existing entities
18/// - PUT /{source_type}/{source_id}/{route_name}/{target_id} - Update link metadata
19/// - DELETE /{source_type}/{source_id}/{route_name}/{target_id} - Delete link
20/// - GET /{entity_type}/{entity_id}/links - List available link types
21///
22/// NOTE: Nested routes are supported up to 2 levels automatically:
23/// - GET /{entity_type}/{entity_id}/{route_name} - List linked entities
24/// - GET /{entity_type}/{entity_id}/{route_name}/{target_id} - Get specific link
25///
26/// For deeper nesting (3+ levels), see: docs/guides/CUSTOM_NESTED_ROUTES.md
27///
28/// The route_name (e.g., "cars-owned", "cars-driven") is resolved to the appropriate
29/// link_type (e.g., "owner", "driver") automatically by the LinkRouteRegistry.
30pub fn build_link_routes(state: AppState) -> Router {
31 use axum::extract::{Path as AxumPath, Request, State as AxumState};
32 use axum::response::IntoResponse;
33 use uuid::Uuid;
34
35 // Handler intelligent qui route vers list_links OU handle_nested_path_get selon la profondeur
36 let smart_handler = |AxumState(state): AxumState<AppState>,
37 AxumPath((entity_type_plural, entity_id, route_name)): AxumPath<(
38 String,
39 Uuid,
40 String,
41 )>,
42 Query(params): Query<QueryParams>,
43 req: Request| async move {
44 let path = req.uri().path();
45 let segments: Vec<&str> = path.trim_matches('/').split('/').collect();
46
47 // Si plus de 3 segments, c'est une route imbriquée à 3+ niveaux
48 if segments.len() >= 5 {
49 // Utiliser le handler générique pour chemins profonds (with pagination)
50 handle_nested_path_get(AxumState(state), AxumPath(path.to_string()), Query(params))
51 .await
52 .map(|r| r.into_response())
53 } else {
54 // Route classique à 2 niveaux - with pagination
55 list_links(
56 AxumState(state),
57 AxumPath((entity_type_plural, entity_id, route_name)),
58 Query(params),
59 )
60 .await
61 .map(|r| r.into_response())
62 }
63 };
64
65 // Handler fallback pour les autres cas (with pagination)
66 let fallback_handler = |AxumState(state): AxumState<AppState>,
67 Query(params): Query<QueryParams>,
68 req: Request| async move {
69 let path = req.uri().path().to_string();
70 handle_nested_path_get(AxumState(state), AxumPath(path), Query(params))
71 .await
72 .map(|r| r.into_response())
73 };
74
75 Router::new()
76 .route("/links/{link_id}", get(get_link))
77 .route(
78 "/{entity_type}/{entity_id}/{route_name}",
79 get(smart_handler).post(create_linked_entity),
80 )
81 .route(
82 "/{source_type}/{source_id}/{route_name}/{target_id}",
83 get(get_link_by_route)
84 .post(create_link)
85 .put(update_link)
86 .delete(delete_link),
87 )
88 .route(
89 "/{entity_type}/{entity_id}/links",
90 get(list_available_links),
91 )
92 .fallback(fallback_handler)
93 .with_state(state)
94}