holger_core/
config.rs

1use crate::repository::rust::RustRepo;
2use crate::repository::types::IOInstance;
3use std::fs;
4use std::path::Path;
5
6use crate::exposed::{ExposedEndpointBackend, ExposedEndpointInstance};
7use url::Url;
8use crate::{
9    ArtifactFormat, HolgerConfig, RepositoryInstance, RepositoryBackend, StorageEndpointInstance,
10    RepositoryType, StorageType,
11};
12use std::collections::HashMap;
13use std::sync::Arc;
14use anyhow::Result;
15use crate::exposed::http2::Http2Backend;
16
17pub fn factory(config: &HolgerConfig) -> Result<HolgerInstance> {
18    // 1. Build StorageEndpointInstances
19    let mut storage_map: HashMap<String, Arc<StorageEndpointInstance>> = HashMap::new();
20    let mut storage_endpoints = Vec::new();
21
22    for se in &config.storage_endpoints {
23        let instance = Arc::new(StorageEndpointInstance::from_config(se)?);
24        storage_map.insert(se.name.clone(), instance.clone());
25        storage_endpoints.push(instance);
26    }
27
28    // 2. Build ExposedEndpointInstances
29    let mut endpoint_map: HashMap<String, Arc<ExposedEndpointInstance>> = HashMap::new();
30    let mut exposed_endpoints = Vec::new();
31
32    for ep in &config.exposed_endpoints {
33        // ✅ Only 4 args; from_config parses ip/port from url_prefix
34        let backend: Arc<Http2Backend> = Http2Backend::from_config(
35            ep.name.clone(),
36            &ep.url_prefix,
37            &ep.cert,
38            &ep.key,
39        )?;
40
41        // Cast to Arc<dyn ExposedEndpointBackend>
42        let backend_arc: Arc<dyn ExposedEndpointBackend> = backend.clone();
43
44        let (ip, port) = parse_ip_port(&ep.url_prefix);
45        let instance = Arc::new(ExposedEndpointInstance {
46            name: ep.name.clone(),
47            ip,
48            port,
49            routes: HashMap::new(),
50            backend: backend_arc,
51        });
52
53        endpoint_map.insert(ep.name.clone(), instance.clone());
54        exposed_endpoints.push(instance);
55    }
56
57    // 3. Prepare resolver closures
58    let resolve_storage = |name: &str| {
59        storage_map
60            .get(name)
61            .cloned()
62            .ok_or_else(|| anyhow::anyhow!("Storage endpoint '{}' not found", name))
63    };
64
65    let resolve_endpoint = |name: &str| {
66        endpoint_map
67            .get(name)
68            .cloned()
69            .ok_or_else(|| anyhow::anyhow!("Exposed endpoint '{}' not found", name))
70    };
71
72    // 4. Build RepositoryInstances
73    let mut repositories = Vec::new();
74    for r in &config.repositories {
75        let repo = Arc::new(RepositoryInstance::from_config(
76            r,
77            &resolve_storage,
78            &resolve_endpoint,
79        )?);
80        repositories.push(repo);
81    }
82
83    // 5. Wire up routes: each repo attaches itself to its endpoint
84    for repo in &repositories {
85        if let Some(out_io) = &repo.out_io {
86            let ep_name = &out_io.endpoint.name;
87            if let Some(endpoint_arc) = endpoint_map.get(ep_name) {
88                // We cannot modify through Arc directly without Mutex,
89                // so clone a new instance with updated routes if needed
90                // Or design routes to be RwLock if dynamic routing required.
91                // For now, just log/skip mut insert if immutable
92                // println!("Attaching {} to endpoint {}", repo.name, ep_name);
93            }
94        }
95    }
96
97    Ok(HolgerInstance {
98        exposed_endpoints,
99        storage_endpoints,
100        repositories,
101    })
102}
103/// Inline helper to parse IP + port from URL
104fn parse_ip_port(url: &str) -> (String, u16) {
105    let clean = url.trim_end_matches('/');
106    let without_scheme = clean.split("://").nth(1).unwrap_or(clean);
107    let mut parts = without_scheme.split(':');
108    let ip = parts.next().unwrap_or("127.0.0.1").to_string();
109    let port = parts.next().and_then(|p| p.parse().ok()).unwrap_or(443);
110    (ip, port)
111}
112
113
114
115pub fn load_config_from_path<P: AsRef<Path>>(path: P) -> Result<HolgerConfig> {
116    let data = fs::read_to_string(path)?;
117    let config: HolgerConfig = toml::from_str(&data)?;
118    Ok(config)
119}
120
121#[derive(Debug)]
122pub struct HolgerInstance {
123    pub exposed_endpoints: Vec<Arc<ExposedEndpointInstance>>,
124    pub storage_endpoints: Vec<Arc<StorageEndpointInstance>>,
125    pub repositories: Vec<Arc<RepositoryInstance>>,
126}
127
128impl HolgerInstance {
129    pub fn start(&self) -> anyhow::Result<()> {
130        self.exposed_endpoints
131            .iter()
132            .map(|ep| ep.backend.as_ref()) // just map, no filter_map
133            .try_for_each(|backend| backend.start())?;
134        Ok(())
135    }
136
137    pub fn stop(&self) -> anyhow::Result<()> {
138        self.exposed_endpoints
139            .iter()
140            .map(|ep| ep.backend.as_ref())
141            .try_for_each(|backend| backend.stop())?;
142        Ok(())
143    }
144}
145