holger_ron/
lib.rs

1// holger-ron/src/lib.rs
2use serde::{Deserialize, Serialize};
3//use hyper::{Request, Response, Body};
4//use hyper::body::{BoxBody, Bytes};
5
6
7use http_body_util::BodyExt;
8use std::{
9    fs::File,
10    io::BufReader,
11    path::Path,
12};
13
14use anyhow::Context;
15use anyhow::Result;
16use ron::de::from_reader;
17
18// ========================= Wire Holger  =========================
19
20use std::collections::HashMap;
21use std::sync::Arc;
22use holger_traits::RepositoryBackendTrait;
23use crate::exposed::ExposedEndpoint;
24use crate::exposed::fast_routes::FastRoutes;
25pub use crate::repository::Repository;
26pub use crate::storage::StorageEndpoint;
27
28pub mod exposed;
29mod repository;
30mod storage;
31
32pub fn wire_holger(holger: &mut Holger) -> Result<()> {
33    // ========================= PASS 1: Index by name =========================
34    let mut repo_map = HashMap::new();
35    let mut exposed_map = HashMap::new();
36    let mut storage_map = HashMap::new();
37
38    for repo in &*holger.repositories {
39        repo_map.insert(repo.ron_name.clone(), repo as *const Repository);
40    }
41    for exp in &*holger.exposed_endpoints {
42        exposed_map.insert(exp.ron_name.clone(), exp as *const ExposedEndpoint);
43    }
44    for st in &*holger.storage_endpoints {
45        storage_map.insert(st.ron_name.clone(), st as *const StorageEndpoint);
46    }
47
48    // ========================= PASS 2: Wire forward references =========================
49    for repo in &mut holger.repositories {
50        // Wire upstreams
51        for name in &repo.ron_upstreams {
52            if let Some(ptr) = repo_map.get(name) {
53                repo.wired_upstreams.push(*ptr);
54            } else {
55                return Err(anyhow::anyhow!("Missing upstream repo: {}", name));
56            }
57        }
58
59        // Wire IN IO
60        if let Some(io) = &mut repo.ron_in {
61            io.wired_storage = *storage_map
62                .get(&io.ron_storage_endpoint)
63                .ok_or_else(|| anyhow::anyhow!("Missing storage endpoint: {}", io.ron_storage_endpoint))?;
64            io.wired_exposed = *exposed_map
65                .get(&io.ron_exposed_endpoint)
66                .ok_or_else(|| anyhow::anyhow!("Missing exposed endpoint: {}", io.ron_exposed_endpoint))?;
67        }
68
69        // Wire OUT IO
70        if let Some(io) = &mut repo.ron_out {
71            io.wired_storage = *storage_map
72                .get(&io.ron_storage_endpoint)
73                .ok_or_else(|| anyhow::anyhow!("Missing storage endpoint: {}", io.ron_storage_endpoint))?;
74            io.wired_exposed = *exposed_map
75                .get(&io.ron_exposed_endpoint)
76                .ok_or_else(|| anyhow::anyhow!("Missing exposed endpoint: {}", io.ron_exposed_endpoint))?;
77        }
78    }
79
80    // ========================= PASS 2b: Wire reverse links =========================
81    for exp in &mut holger.exposed_endpoints {
82        for repo in &holger.repositories {
83            if let Some(io) = &repo.ron_in {
84                if io.ron_exposed_endpoint == exp.ron_name {
85                    exp.wired_in_repositories.push(repo as *const Repository);
86                }
87            }
88            if let Some(io) = &repo.ron_out {
89                if io.ron_exposed_endpoint == exp.ron_name {
90                    exp.wired_out_repositories.push(repo as *const Repository);
91                }
92            }
93        }
94    }
95
96    for st in &mut holger.storage_endpoints {
97        for repo in &holger.repositories {
98            if let Some(io) = &repo.ron_in {
99                if io.ron_storage_endpoint == st.ron_name {
100                    st.wired_in_repositories.push(repo as *const Repository);
101                }
102            }
103            if let Some(io) = &repo.ron_out {
104                if io.ron_storage_endpoint == st.ron_name {
105                    st.wired_out_repositories.push(repo as *const Repository);
106                }
107            }
108        }
109    }
110
111    // ========================= PASS 3: Reverse wiring =========================
112    for repo in &holger.repositories {
113        let repo_ptr = repo as *const Repository;
114
115        // Wire IN
116        if let Some(io) = &repo.ron_in {
117            // Storage reverse link
118            let storage_ptr = io.wired_storage;
119            let storage = unsafe { &mut *(storage_ptr as *mut StorageEndpoint) };
120            storage.wired_in_repositories.push(repo_ptr);
121
122            // Exposed reverse link
123            let exposed_ptr = io.wired_exposed;
124            let exposed = unsafe { &mut *(exposed_ptr as *mut ExposedEndpoint) };
125            exposed.wired_in_repositories.push(repo_ptr);
126        }
127
128        // Wire OUT
129        if let Some(io) = &repo.ron_out {
130            // Storage reverse link
131            let storage_ptr = io.wired_storage;
132            let storage = unsafe { &mut *(storage_ptr as *mut StorageEndpoint) };
133            storage.wired_out_repositories.push(repo_ptr);
134
135            // Exposed reverse link
136            let exposed_ptr = io.wired_exposed;
137            let exposed = unsafe { &mut *(exposed_ptr as *mut ExposedEndpoint) };
138            exposed.wired_out_repositories.push(repo_ptr);
139        }
140    }
141    // ========================= PASS 4: Attach FastRoutes and backend =========================
142    for exp in &mut holger.exposed_endpoints {
143        // 1️⃣ Build route table
144        let mut routes: Vec<(String, Arc<dyn RepositoryBackendTrait>)> = Vec::new();
145
146        for &repo_ptr in &exp.wired_out_repositories {
147            if repo_ptr.is_null() { continue; }
148            let repo: &Repository = unsafe { &*repo_ptr };
149
150            if let Some(backend_arc) = &repo.backend_repository {
151                routes.push((repo.ron_name.clone(), backend_arc.clone()));
152            }
153        }
154
155        exp.aggregated_routes = if routes.is_empty() {
156            None
157        } else {
158            Some(FastRoutes::new(routes))
159        };
160
161        // 2️⃣ Instantiate backend
162        exp.backend_from_config()?;
163
164        // 3️⃣ Pass aggregated_routes to Http2Backend
165        if let Some(routes) = exp.aggregated_routes.clone() {
166            let backend_mut = Arc::get_mut(&mut exp.backend_http2)
167                .expect("backend_http2 Arc must be unique here in wire_holger");
168            backend_mut.set_fast_routes(routes);
169        }
170    }
171
172    Ok(())
173}
174
175// ========================= ROOT HOLGER =========================
176
177#[derive(Serialize, Deserialize)]
178pub struct Holger {
179    pub repositories: Vec<Repository>,
180    pub exposed_endpoints: Vec<ExposedEndpoint>,
181    pub storage_endpoints: Vec<StorageEndpoint>,
182}
183
184// ========================= LOAD RON CONFIG =========================
185
186pub fn read_ron_config<P: AsRef<Path>>(path: P) -> Result<Holger> {
187    let file = File::open(path)?;
188    let reader = BufReader::new(file);
189    let holger: Holger = from_reader(reader)?;
190    Ok(holger)
191}
192
193
194impl Holger {
195    pub fn start(&self) -> anyhow::Result<()> {
196        for ep in &self.exposed_endpoints {
197            let backend = ep.backend_http2.clone();
198            tokio::spawn(async move {
199                if let Err(e) = backend.start().await {
200                    eprintln!("Backend start failed: {e}");
201                }
202            });
203        }
204        Ok(())
205    }
206
207
208    pub fn stop(&self) -> anyhow::Result<()> {
209        for ep in &self.exposed_endpoints {
210            // Arc<Http2Backend> → just clone and call stop()
211            ep.backend_http2.stop()?;
212        }
213        Ok(())
214    }
215    pub fn instantiate_backends(&mut self) -> anyhow::Result<()> {
216        for ep in &mut self.exposed_endpoints {
217            ep.backend_from_config()?;
218        }
219        for se in &mut self.storage_endpoints {
220            se.backend_from_config()?;
221        }
222        for repo in &mut self.repositories {
223            repo.backend_from_config()?;
224        }
225        Ok(())
226    }
227}
228// ========================= RON STRUCTS =========================
229
230
231
232
233