use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use crate::api::editor::EditorBindingRow;
use crate::auth::binding_secrets::BindingSecretStore;
use crate::error::OlError;
use crate::runtime::multi_tool::{resolve_routes, RouteTable};
#[derive(Clone)]
pub struct SharedRoutes {
inner: Arc<Mutex<Arc<RouteTable>>>,
}
impl SharedRoutes {
pub fn new(initial: RouteTable) -> Self {
Self {
inner: Arc::new(Mutex::new(Arc::new(initial))),
}
}
pub fn snapshot(&self) -> Arc<RouteTable> {
self.inner.lock().expect("routes mutex poisoned").clone()
}
pub fn store(&self, new_table: RouteTable) {
let mut guard = self.inner.lock().expect("routes mutex poisoned");
*guard = Arc::new(new_table);
}
}
pub struct ReloadInputs<'a> {
pub manifest_path: &'a Path,
pub live_bindings: &'a [EditorBindingRow],
pub secret_store: &'a dyn BindingSecretStore,
pub manifest_secret_ids_fallback: Option<Vec<String>>,
}
pub fn build_route_table(inputs: &ReloadInputs<'_>) -> Result<RouteTable, OlError> {
let manifest = crate::manifest::load(inputs.manifest_path)?;
let mut known: Vec<String> = inputs.secret_store.list_known()?;
if let Some(extra) = &inputs.manifest_secret_ids_fallback {
for id in extra {
if !known.contains(id) {
if inputs.secret_store.retrieve(id).is_ok() {
known.push(id.clone());
}
}
}
}
resolve_routes(&manifest, inputs.live_bindings, &known)
}
pub fn reload_into(routes: &SharedRoutes, inputs: &ReloadInputs<'_>) -> Result<usize, OlError> {
let table = build_route_table(inputs)?;
let n = table.len();
routes.store(table);
Ok(n)
}
pub fn owned_path(path: &Path) -> PathBuf {
path.to_path_buf()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::auth::binding_secrets::FileBindingSecretStore;
use secrecy::SecretString;
use tempfile::TempDir;
#[test]
fn shared_routes_snapshot_is_independent_of_subsequent_swaps() {
let routes = SharedRoutes::new(RouteTable::empty());
let snap = routes.snapshot();
assert_eq!(snap.len(), 0);
routes.store(RouteTable::empty());
assert_eq!(snap.len(), 0);
}
#[test]
fn build_route_table_uses_file_store_list_known() {
let tmp = TempDir::new().unwrap();
let store = FileBindingSecretStore::new(tmp.path(), "mach_test");
store
.store("bnd_42", SecretString::from("whsec_AAAA".to_string()))
.unwrap();
let known = store.list_known().unwrap();
assert_eq!(known, vec!["bnd_42".to_string()]);
}
}