canon_protocol/
dependency.rs1use crate::error::{ProtocolError, ProtocolResult};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone)]
6pub struct Dependency {
7 pub publisher: String,
8 pub name: String,
9 pub version: Option<String>,
10}
11
12impl Dependency {
13 pub fn parse(uri: &str) -> ProtocolResult<Self> {
15 let parts: Vec<&str> = uri.splitn(2, '@').collect();
17
18 let path_parts: Vec<&str> = parts[0].split('/').collect();
20 if path_parts.len() != 2 {
21 return Err(ProtocolError::InvalidUri(format!(
22 "Invalid dependency URI format: {}",
23 uri
24 )));
25 }
26
27 let publisher = path_parts[0].to_string();
28 let name = path_parts[1].to_string();
29
30 let version = if parts.len() > 1 {
32 Some(parts[1].to_string())
33 } else {
34 None
35 };
36
37 Ok(Self {
38 publisher,
39 name,
40 version,
41 })
42 }
43
44 pub fn local_path(&self, registry_domain: &str) -> PathBuf {
46 let mut path = PathBuf::from(".canon");
47 path.push("specs"); path.push(registry_domain); path.push(&self.publisher);
50 path.push(&self.name);
51 if let Some(ref version) = self.version {
52 path.push(version);
53 }
54 path
55 }
56
57 pub fn registry_url(&self, registry_base: &str) -> String {
59 let version = self.version.as_deref().unwrap_or("latest");
60 format!(
61 "{}/specs/{}/{}/{}/",
62 registry_base.trim_end_matches('/'),
63 self.publisher,
64 self.name,
65 version
66 )
67 }
68
69 pub fn is_installed(&self, registry_domain: &str) -> bool {
71 self.local_path(registry_domain).exists()
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn test_parse_dependency() {
81 let dep = Dependency::parse("canon-protocol.org/type@1.0.0").unwrap();
82 assert_eq!(dep.publisher, "canon-protocol.org");
83 assert_eq!(dep.name, "type");
84 assert_eq!(dep.version, Some("1.0.0".to_string()));
85
86 let dep_no_version = Dependency::parse("example.com/api").unwrap();
87 assert_eq!(dep_no_version.publisher, "example.com");
88 assert_eq!(dep_no_version.name, "api");
89 assert_eq!(dep_no_version.version, None);
90 }
91
92 #[test]
93 fn test_local_path() {
94 let dep = Dependency {
95 publisher: "canon-protocol.org".to_string(),
96 name: "type".to_string(),
97 version: Some("1.0.0".to_string()),
98 };
99
100 let path = dep.local_path("spec.farm");
101 assert_eq!(
102 path,
103 PathBuf::from(".canon/specs/spec.farm/canon-protocol.org/type/1.0.0")
104 );
105 }
106
107 #[test]
108 fn test_registry_url() {
109 let dep = Dependency {
110 publisher: "canon-protocol.org".to_string(),
111 name: "type".to_string(),
112 version: Some("1.0.0".to_string()),
113 };
114
115 let url = dep.registry_url("https://spec.farm");
116 assert_eq!(
117 url,
118 "https://spec.farm/specs/canon-protocol.org/type/1.0.0/"
119 );
120
121 let url_with_slash = dep.registry_url("https://spec.farm/");
123 assert_eq!(
124 url_with_slash,
125 "https://spec.farm/specs/canon-protocol.org/type/1.0.0/"
126 );
127 }
128
129 #[test]
130 fn test_registry_url_without_version() {
131 let dep = Dependency {
132 publisher: "example.com".to_string(),
133 name: "api".to_string(),
134 version: None,
135 };
136
137 let url = dep.registry_url("https://registry.canon-protocol.org");
138 assert_eq!(
139 url,
140 "https://registry.canon-protocol.org/specs/example.com/api/latest/"
141 );
142 }
143}