origin_mcp_http/
discovery.rs1use origin_domain::{AppError, Result};
8use serde::{Deserialize, Serialize};
9use std::path::Path;
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct Discovery {
14 pub url: String,
16 pub token: String,
18}
19
20impl Discovery {
21 pub fn write(&self, path: &Path) -> Result<()> {
24 if let Some(parent) = path.parent() {
25 std::fs::create_dir_all(parent).map_err(|error| {
26 AppError::storage(format!("cannot create {}: {error}", parent.display()))
27 })?;
28 }
29
30 let encoded = serde_json::to_string_pretty(self)
31 .map_err(|error| AppError::storage(format!("cannot encode discovery: {error}")))?;
32
33 let temporary = path.with_extension("json.tmp");
34 std::fs::write(&temporary, encoded).map_err(|error| {
35 AppError::storage(format!("cannot write {}: {error}", temporary.display()))
36 })?;
37 std::fs::rename(&temporary, path).map_err(|error| {
38 AppError::storage(format!("cannot publish {}: {error}", path.display()))
39 })?;
40
41 tracing::debug!(path = %path.display(), "mcp http endpoint published");
42 Ok(())
43 }
44
45 pub fn read(path: &Path) -> Option<Self> {
50 let contents = std::fs::read_to_string(path).ok()?;
51 serde_json::from_str(&contents).ok()
52 }
53
54 pub fn remove(path: &Path) {
56 let _ = std::fs::remove_file(path);
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 fn temp_path(name: &str) -> std::path::PathBuf {
65 let unique = format!("origin-mcp-discovery-{}-{name}.json", std::process::id());
66 std::env::temp_dir().join(unique)
67 }
68
69 #[test]
70 fn a_discovery_round_trips() {
71 let path = temp_path("round-trip");
72 let discovery = Discovery {
73 url: "http://127.0.0.1:5000/mcp".to_owned(),
74 token: "abc".to_owned(),
75 };
76
77 discovery.write(&path).unwrap();
78 let read = Discovery::read(&path).expect("must read back");
79
80 assert_eq!(read, discovery);
81 Discovery::remove(&path);
82 assert!(Discovery::read(&path).is_none());
83 }
84
85 #[test]
86 fn a_missing_file_reads_as_none() {
87 assert!(Discovery::read(&temp_path("missing")).is_none());
88 }
89}