lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

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 }