ryra_core/registry/
resolve.rs1use std::path::{Path, PathBuf};
2
3use crate::config::schema::Config;
4use crate::error::{Error, Result};
5use crate::registry;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum ServiceRef {
13 Bundled(String),
15 Custom { registry: String, service: String },
17}
18
19impl ServiceRef {
20 pub fn parse(input: &str) -> Result<Self> {
26 let parts: Vec<&str> = input.split('/').collect();
27 match parts.as_slice() {
28 [""] => Err(Error::InvalidServiceRef(
29 "service reference cannot be empty".to_string(),
30 )),
31 [name] => {
32 if name.is_empty() {
33 Err(Error::InvalidServiceRef(
34 "service reference cannot be empty".to_string(),
35 ))
36 } else {
37 Ok(ServiceRef::Bundled(name.to_string()))
38 }
39 }
40 [registry, service] => {
41 if registry.is_empty() {
42 return Err(Error::InvalidServiceRef(format!(
43 "registry name cannot be empty in reference '{input}'"
44 )));
45 }
46 if service.is_empty() {
47 return Err(Error::InvalidServiceRef(format!(
48 "service name cannot be empty in reference '{input}'"
49 )));
50 }
51 Ok(ServiceRef::Custom {
52 registry: registry.to_string(),
53 service: service.to_string(),
54 })
55 }
56 _ => Err(Error::InvalidServiceRef(format!(
57 "invalid service reference '{input}': expected 'service' or 'registry/service'"
58 ))),
59 }
60 }
61
62 pub fn service_name(&self) -> &str {
64 match self {
65 ServiceRef::Bundled(name) => name,
66 ServiceRef::Custom { service, .. } => service,
67 }
68 }
69
70 pub fn registry_name(&self) -> &str {
74 match self {
75 ServiceRef::Bundled(_) => "bundled",
76 ServiceRef::Custom { registry, .. } => registry,
77 }
78 }
79}
80
81pub async fn resolve_registry_dir(
86 service_ref: &ServiceRef,
87 config: &Config,
88 cache_dir: &Path,
89) -> Result<PathBuf> {
90 match service_ref {
91 ServiceRef::Bundled(_) => registry::bundled::ensure_bundled(cache_dir),
92 ServiceRef::Custom { registry, .. } => {
93 let entry = config
94 .registries
95 .iter()
96 .find(|r| r.name == *registry)
97 .ok_or_else(|| Error::RegistryNotFound(registry.clone()))?;
98
99 let dest = cache_dir.join("registries").join(registry);
100 registry::fetch::clone_or_pull(&entry.url, &dest).await?;
101 Ok(dest)
102 }
103 }
104}
105
106pub async fn resolve_service(
111 service_ref: &ServiceRef,
112 config: &Config,
113 cache_dir: &Path,
114) -> Result<registry::RegistryService> {
115 let repo_dir = resolve_registry_dir(service_ref, config, cache_dir).await?;
116 registry::find_service(&repo_dir, service_ref.service_name())
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn parse_bundled_service() {
125 let r = ServiceRef::parse("forgejo").expect("should parse");
126 assert_eq!(r, ServiceRef::Bundled("forgejo".to_string()));
127 assert_eq!(r.service_name(), "forgejo");
128 assert_eq!(r.registry_name(), "bundled");
129 }
130
131 #[test]
132 fn parse_custom_service() {
133 let r = ServiceRef::parse("acme/forgejo").expect("should parse");
134 assert_eq!(
135 r,
136 ServiceRef::Custom {
137 registry: "acme".to_string(),
138 service: "forgejo".to_string(),
139 }
140 );
141 assert_eq!(r.service_name(), "forgejo");
142 assert_eq!(r.registry_name(), "acme");
143 }
144
145 #[test]
146 fn parse_empty_fails() {
147 let err = ServiceRef::parse("").expect_err("empty input should fail");
148 let msg = err.to_string();
149 assert!(
150 msg.contains("empty"),
151 "expected 'empty' in error message, got: {msg}"
152 );
153 }
154
155 #[test]
156 fn parse_empty_parts_fails() {
157 let err = ServiceRef::parse("/forgejo").expect_err("leading slash should fail");
158 let msg = err.to_string();
159 assert!(
160 msg.contains("empty"),
161 "expected 'empty' in error for '/forgejo', got: {msg}"
162 );
163
164 let err = ServiceRef::parse("acme/").expect_err("trailing slash should fail");
165 let msg = err.to_string();
166 assert!(
167 msg.contains("empty"),
168 "expected 'empty' in error for 'acme/', got: {msg}"
169 );
170 }
171
172 #[test]
173 fn parse_too_many_slashes_fails() {
174 let err = ServiceRef::parse("acme/sub/forgejo").expect_err("too many slashes should fail");
175 let msg = err.to_string();
176 assert!(
177 msg.contains("invalid"),
178 "expected 'invalid' in error message, got: {msg}"
179 );
180 }
181}