Skip to main content

backbone_accounting/presentation/http/
hierarchy_handler.rs

1//! Non-CRUD HTTP surface for entity hierarchy reads.
2//!
3//! Hand-authored (user-owned; see `metaphor.codegen.yaml`). Wraps `HierarchyService`:
4//!   GET /accounts/:id/hierarchy
5//!   GET /cost-centers/:id/hierarchy
6//!   GET /fiscal-periods/:id/hierarchy
7//!
8//! Each returns the ancestor chain (root → self) so a client can show where the entity sits in its
9//! tree without walking parent links itself.
10//!
11//! Tenancy (ADR-0029): the wire carries no tenant parameter. The composing service's tenancy
12//! decorator scopes the read from the request's own scope, so a company in the query string
13//! could only agree with it or contradict it.
14
15use std::sync::Arc;
16
17use axum::{
18    extract::Path, extract::Query, extract::State, response::IntoResponse, routing::get, Json,
19    Router,
20};
21use serde::Deserialize;
22use uuid::Uuid;
23
24use crate::application::service::hierarchy_service::HierarchyService;
25use crate::domain::repositories::hierarchy_repository::HierarchyTable;
26
27fn err(e: anyhow::Error) -> (axum::http::StatusCode, Json<serde_json::Value>) {
28    (
29        axum::http::StatusCode::INTERNAL_SERVER_ERROR,
30        Json(serde_json::json!({ "success": false, "error": e.to_string() })),
31    )
32}
33
34async fn ancestors(
35    State(svc): State<Arc<HierarchyService>>,
36    table: HierarchyTable,
37    Path(id): Path<Uuid>,
38) -> impl IntoResponse {
39    match svc.ancestors(table, id).await {
40        Ok(chain) => {
41            Json(serde_json::json!({ "success": true, "hierarchy": chain })).into_response()
42        }
43        Err(e) => err(e).into_response(),
44    }
45}
46
47/// Route composer for the three hierarchy endpoints.
48pub fn create_hierarchy_routes(service: Arc<HierarchyService>) -> Router {
49    Router::new()
50        .route(
51            "/accounts/:id/hierarchy",
52            get(|st, id| ancestors(st, HierarchyTable::Account, id)),
53        )
54        .route(
55            "/cost-centers/:id/hierarchy",
56            get(|st, id| ancestors(st, HierarchyTable::CostCenter, id)),
57        )
58        .route(
59            "/fiscal-periods/:id/hierarchy",
60            get(|st, id| ancestors(st, HierarchyTable::FiscalPeriod, id)),
61        )
62        .with_state(service)
63}