namespace.rs (4663B)
1 use std::path::PathBuf; 2 3 use crate::RadrootsRuntimePathsError; 4 5 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 6 pub enum RadrootsRuntimeNamespaceKind { 7 App, 8 Service, 9 Worker, 10 Shared, 11 } 12 13 impl RadrootsRuntimeNamespaceKind { 14 #[must_use] 15 pub fn path_segment(self) -> &'static str { 16 match self { 17 Self::App => "apps", 18 Self::Service => "services", 19 Self::Worker => "workers", 20 Self::Shared => "shared", 21 } 22 } 23 } 24 25 #[derive(Debug, Clone, PartialEq, Eq)] 26 pub struct RadrootsRuntimeNamespace { 27 kind: RadrootsRuntimeNamespaceKind, 28 value: String, 29 } 30 31 impl RadrootsRuntimeNamespace { 32 pub fn app(value: impl Into<String>) -> Result<Self, RadrootsRuntimePathsError> { 33 Self::new(RadrootsRuntimeNamespaceKind::App, value) 34 } 35 36 pub fn service(value: impl Into<String>) -> Result<Self, RadrootsRuntimePathsError> { 37 Self::new(RadrootsRuntimeNamespaceKind::Service, value) 38 } 39 40 pub fn worker(value: impl Into<String>) -> Result<Self, RadrootsRuntimePathsError> { 41 Self::new(RadrootsRuntimeNamespaceKind::Worker, value) 42 } 43 44 pub fn shared(value: impl Into<String>) -> Result<Self, RadrootsRuntimePathsError> { 45 Self::new(RadrootsRuntimeNamespaceKind::Shared, value) 46 } 47 48 pub fn new( 49 kind: RadrootsRuntimeNamespaceKind, 50 value: impl Into<String>, 51 ) -> Result<Self, RadrootsRuntimePathsError> { 52 let value = value.into(); 53 validate_component(&value)?; 54 Ok(Self { kind, value }) 55 } 56 57 #[must_use] 58 pub fn kind(&self) -> RadrootsRuntimeNamespaceKind { 59 self.kind 60 } 61 62 #[must_use] 63 pub fn value(&self) -> &str { 64 self.value.as_str() 65 } 66 67 #[must_use] 68 pub fn relative_path(&self) -> PathBuf { 69 PathBuf::from(self.kind.path_segment()).join(self.value.as_str()) 70 } 71 } 72 73 fn validate_component(value: &str) -> Result<(), RadrootsRuntimePathsError> { 74 let trimmed = value.trim(); 75 if trimmed.is_empty() 76 || trimmed == "." 77 || trimmed == ".." 78 || trimmed.contains('/') 79 || trimmed.contains('\\') 80 { 81 return Err(RadrootsRuntimePathsError::InvalidNamespaceComponent { 82 value: value.to_owned(), 83 }); 84 } 85 Ok(()) 86 } 87 88 #[cfg(test)] 89 mod tests { 90 use std::path::PathBuf; 91 92 use super::{RadrootsRuntimeNamespace, RadrootsRuntimeNamespaceKind}; 93 use crate::RadrootsRuntimePathsError; 94 95 #[test] 96 fn namespace_kind_path_segments_are_canonical() { 97 assert_eq!(RadrootsRuntimeNamespaceKind::App.path_segment(), "apps"); 98 assert_eq!( 99 RadrootsRuntimeNamespaceKind::Service.path_segment(), 100 "services" 101 ); 102 assert_eq!( 103 RadrootsRuntimeNamespaceKind::Worker.path_segment(), 104 "workers" 105 ); 106 assert_eq!( 107 RadrootsRuntimeNamespaceKind::Shared.path_segment(), 108 "shared" 109 ); 110 } 111 112 #[test] 113 fn namespace_constructors_preserve_kind_and_value() { 114 let app = RadrootsRuntimeNamespace::app("cli").expect("app namespace"); 115 assert_eq!(app.kind(), RadrootsRuntimeNamespaceKind::App); 116 assert_eq!(app.value(), "cli"); 117 assert_eq!(app.relative_path(), PathBuf::from("apps/cli")); 118 119 let service = RadrootsRuntimeNamespace::service("myc").expect("service namespace"); 120 assert_eq!(service.kind(), RadrootsRuntimeNamespaceKind::Service); 121 assert_eq!(service.value(), "myc"); 122 assert_eq!(service.relative_path(), PathBuf::from("services/myc")); 123 124 let worker = RadrootsRuntimeNamespace::worker("rhi").expect("worker namespace"); 125 assert_eq!(worker.kind(), RadrootsRuntimeNamespaceKind::Worker); 126 assert_eq!(worker.value(), "rhi"); 127 assert_eq!(worker.relative_path(), PathBuf::from("workers/rhi")); 128 129 let shared = RadrootsRuntimeNamespace::shared("runtime").expect("shared namespace"); 130 assert_eq!(shared.kind(), RadrootsRuntimeNamespaceKind::Shared); 131 assert_eq!(shared.value(), "runtime"); 132 assert_eq!(shared.relative_path(), PathBuf::from("shared/runtime")); 133 } 134 135 #[test] 136 fn namespace_validation_rejects_invalid_components() { 137 for invalid in ["", " ", ".", "..", "a/b", r"a\b"] { 138 let err = RadrootsRuntimeNamespace::new(RadrootsRuntimeNamespaceKind::App, invalid) 139 .expect_err("invalid namespace component should fail"); 140 assert_eq!( 141 err, 142 RadrootsRuntimePathsError::InvalidNamespaceComponent { 143 value: invalid.to_owned(), 144 } 145 ); 146 } 147 } 148 }