gglib_core/ports/
model_repository.rs1use async_trait::async_trait;
7
8use super::RepositoryError;
9use crate::domain::{Model, NewModel};
10
11#[async_trait]
25pub trait ModelRepository: Send + Sync {
26 async fn list(&self) -> Result<Vec<Model>, RepositoryError>;
28
29 async fn get_by_id(&self, id: i64) -> Result<Model, RepositoryError>;
33
34 async fn get_by_name(&self, name: &str) -> Result<Model, RepositoryError>;
38
39 async fn insert(&self, model: &NewModel) -> Result<Model, RepositoryError>;
45
46 async fn update(&self, model: &Model) -> Result<(), RepositoryError>;
50
51 async fn delete(&self, id: i64) -> Result<(), RepositoryError>;
55
56 async fn get_by_identifier(&self, identifier: &str) -> Result<Option<Model>, RepositoryError> {
73 if let Ok(id) = identifier.parse::<i64>() {
74 match self.get_by_id(id).await {
75 Ok(model) => return Ok(Some(model)),
76 Err(RepositoryError::NotFound(_)) => {}
77 Err(e) => return Err(e),
78 }
79 }
80 match self.get_by_name(identifier).await {
81 Ok(model) => Ok(Some(model)),
82 Err(RepositoryError::NotFound(_)) => Ok(None),
83 Err(e) => Err(e),
84 }
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use crate::domain::ModelCapabilities;
92 use chrono::Utc;
93 use std::collections::HashMap;
94 use std::path::PathBuf;
95
96 fn model() -> Model {
98 Model {
99 id: 7,
100 name: "qwen3".to_string(),
101 model_key: String::new(),
102 file_path: PathBuf::from("/models/qwen3.gguf"),
103 param_count_b: 7.0,
104 architecture: None,
105 quantization: None,
106 context_length: None,
107 expert_count: None,
108 expert_used_count: None,
109 expert_shared_count: None,
110 metadata: HashMap::new(),
111 added_at: Utc::now(),
112 hf_repo_id: None,
113 hf_commit_sha: None,
114 hf_filename: None,
115 download_date: None,
116 last_update_check: None,
117 tags: vec![],
118 capabilities: ModelCapabilities::default(),
119 inference_defaults: None,
120 defaults_origin: None,
121 server_defaults: None,
122 benchmark_summary: None,
123 }
124 }
125
126 struct OneModelRepo {
129 storage_error_on_id: bool,
130 }
131
132 #[async_trait]
133 impl ModelRepository for OneModelRepo {
134 async fn list(&self) -> Result<Vec<Model>, RepositoryError> {
135 Ok(vec![model()])
136 }
137
138 async fn get_by_id(&self, id: i64) -> Result<Model, RepositoryError> {
139 if self.storage_error_on_id {
140 return Err(RepositoryError::Storage("disk on fire".into()));
141 }
142 if id == 7 {
143 Ok(model())
144 } else {
145 Err(RepositoryError::NotFound(format!("id={id}")))
146 }
147 }
148
149 async fn get_by_name(&self, name: &str) -> Result<Model, RepositoryError> {
150 if name == "qwen3" {
151 Ok(model())
152 } else {
153 Err(RepositoryError::NotFound(format!("name={name}")))
154 }
155 }
156
157 async fn insert(&self, _model: &NewModel) -> Result<Model, RepositoryError> {
158 unimplemented!("not exercised by these tests")
159 }
160
161 async fn update(&self, _model: &Model) -> Result<(), RepositoryError> {
162 unimplemented!("not exercised by these tests")
163 }
164
165 async fn delete(&self, _id: i64) -> Result<(), RepositoryError> {
166 unimplemented!("not exercised by these tests")
167 }
168 }
169
170 fn repo() -> OneModelRepo {
171 OneModelRepo {
172 storage_error_on_id: false,
173 }
174 }
175
176 #[tokio::test]
177 async fn resolves_a_numeric_identifier_by_id() {
178 let found = repo().get_by_identifier("7").await.unwrap();
179 assert_eq!(found.unwrap().name, "qwen3");
180 }
181
182 #[tokio::test]
183 async fn resolves_a_non_numeric_identifier_by_name() {
184 let found = repo().get_by_identifier("qwen3").await.unwrap();
185 assert_eq!(found.unwrap().id, 7);
186 }
187
188 #[tokio::test]
191 async fn numeric_miss_falls_through_to_the_name_lookup() {
192 assert!(repo().get_by_identifier("42").await.unwrap().is_none());
193 }
194
195 #[tokio::test]
196 async fn unknown_identifier_is_none_not_an_error() {
197 assert!(repo().get_by_identifier("ghost").await.unwrap().is_none());
198 }
199
200 #[tokio::test]
203 async fn storage_failure_on_the_id_lookup_propagates() {
204 let repo = OneModelRepo {
205 storage_error_on_id: true,
206 };
207 let err = repo.get_by_identifier("7").await.unwrap_err();
208 assert!(matches!(err, RepositoryError::Storage(_)));
209 }
210}