use origin_domain::{AppError, Result};
use serde::{Deserialize, Serialize};
use std::path::Path;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Discovery {
pub url: String,
pub token: String,
}
impl Discovery {
pub fn write(&self, path: &Path) -> Result<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|error| {
AppError::storage(format!("cannot create {}: {error}", parent.display()))
})?;
}
let encoded = serde_json::to_string_pretty(self)
.map_err(|error| AppError::storage(format!("cannot encode discovery: {error}")))?;
let temporary = path.with_extension("json.tmp");
std::fs::write(&temporary, encoded).map_err(|error| {
AppError::storage(format!("cannot write {}: {error}", temporary.display()))
})?;
std::fs::rename(&temporary, path).map_err(|error| {
AppError::storage(format!("cannot publish {}: {error}", path.display()))
})?;
tracing::debug!(path = %path.display(), "mcp http endpoint published");
Ok(())
}
pub fn read(path: &Path) -> Option<Self> {
let contents = std::fs::read_to_string(path).ok()?;
serde_json::from_str(&contents).ok()
}
pub fn remove(path: &Path) {
let _ = std::fs::remove_file(path);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn temp_path(name: &str) -> std::path::PathBuf {
let unique = format!("origin-mcp-discovery-{}-{name}.json", std::process::id());
std::env::temp_dir().join(unique)
}
#[test]
fn a_discovery_round_trips() {
let path = temp_path("round-trip");
let discovery = Discovery {
url: "http://127.0.0.1:5000/mcp".to_owned(),
token: "abc".to_owned(),
};
discovery.write(&path).unwrap();
let read = Discovery::read(&path).expect("must read back");
assert_eq!(read, discovery);
Discovery::remove(&path);
assert!(Discovery::read(&path).is_none());
}
#[test]
fn a_missing_file_reads_as_none() {
assert!(Discovery::read(&temp_path("missing")).is_none());
}
}