use geth_types::{PipeId, ResourceId, UnixMillis}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PipeResource { pub id: PipeId, pub resource: ResourceId, pub name: String, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PipeListener { pub id: PipeId, pub name: String, pub listened_at: UnixMillis, pub note: String, } #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct PipeConnection { pub target: String, pub connected_at: UnixMillis, pub local_listener_found: bool, pub note: String, } #[derive(Debug, thiserror::Error)] pub enum PipeError { #[error("invalid pipe name or target: {0}")] InvalidName(String), } pub fn validate_pipe_name(name: &str) -> Result<(), PipeError> { if name.is_empty() || !name .bytes() .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-' | b':')) { return Err(PipeError::InvalidName(name.to_owned())); } Ok(()) } #[must_use] pub fn pipe_roadmap() -> &'static str { "future pipes are authorized Iroh bidirectional streams for stdin/stdout and forwarding" } #[must_use] pub fn local_pipe_runtime_note() -> &'static str { "local daemon registry only; byte streams and Iroh transport are not implemented yet" } #[cfg(test)] mod tests { use super::*; #[test] fn pipe_name_validation_rejects_paths_and_empty_names() { assert!(validate_pipe_name("inbox").is_ok()); assert!(validate_pipe_name("node:laptop").is_ok()); assert!(validate_pipe_name("").is_err()); assert!(validate_pipe_name("../inbox").is_err()); assert!(validate_pipe_name("inbox/main").is_err()); assert!(validate_pipe_name("inbox main").is_err()); } }