pubmed_client/pubmed/client/
einfo.rs1use crate::error::{PubMedError, Result};
4use crate::pubmed::models::{DatabaseInfo, FieldInfo, LinkInfo};
5use crate::pubmed::responses::EInfoResponse;
6use tracing::{debug, info, instrument};
7
8use super::PubMedClient;
9
10impl PubMedClient {
11 #[instrument(skip(self))]
36 pub async fn get_database_list(&self) -> Result<Vec<String>> {
37 let url = format!("{}/einfo.fcgi?retmode=json", self.base_url);
39
40 debug!("Making EInfo API request for database list");
41 let response = self.make_request(&url).await?;
42
43 let einfo_response: EInfoResponse = response.json().await?;
44
45 let db_list = einfo_response.einfo_result.db_list.unwrap_or_default();
46
47 info!(
48 databases_found = db_list.len(),
49 "Database list retrieved successfully"
50 );
51
52 Ok(db_list)
53 }
54
55 #[instrument(skip(self), fields(database = %database))]
87 pub async fn get_database_info(&self, database: &str) -> Result<DatabaseInfo> {
88 if database.trim().is_empty() {
89 return Err(PubMedError::ApiError {
90 status: 400,
91 message: "Database name cannot be empty".to_string(),
92 });
93 }
94
95 let url = format!(
97 "{}/einfo.fcgi?db={}&retmode=json",
98 self.base_url,
99 urlencoding::encode(database)
100 );
101
102 debug!("Making EInfo API request for database details");
103 let response = self.make_request(&url).await?;
104
105 let einfo_response: EInfoResponse = response.json().await?;
106
107 let db_info_list =
108 einfo_response
109 .einfo_result
110 .db_info
111 .ok_or_else(|| PubMedError::ApiError {
112 status: 404,
113 message: format!("Database '{database}' not found or no information available"),
114 })?;
115
116 let db_info = db_info_list
117 .into_iter()
118 .next()
119 .ok_or_else(|| PubMedError::ApiError {
120 status: 404,
121 message: format!("Database '{database}' information not found"),
122 })?;
123
124 let fields = db_info
126 .field_list
127 .unwrap_or_default()
128 .into_iter()
129 .map(|field| FieldInfo {
130 name: field.name,
131 full_name: field.full_name,
132 description: field.description,
133 term_count: field.term_count.and_then(|s| s.parse().ok()),
134 is_date: field.is_date.as_deref() == Some("Y"),
135 is_numerical: field.is_numerical.as_deref() == Some("Y"),
136 single_token: field.single_token.as_deref() == Some("Y"),
137 hierarchy: field.hierarchy.as_deref() == Some("Y"),
138 is_hidden: field.is_hidden.as_deref() == Some("Y"),
139 })
140 .collect();
141
142 let links = db_info
143 .link_list
144 .unwrap_or_default()
145 .into_iter()
146 .map(|link| LinkInfo {
147 name: link.name,
148 menu: link.menu,
149 description: link.description,
150 target_db: link.db_to,
151 })
152 .collect();
153
154 let database_info = DatabaseInfo {
155 name: db_info.db_name,
156 menu_name: db_info.menu_name,
157 description: db_info.description,
158 build: db_info.db_build,
159 count: db_info.count.and_then(|s| s.parse().ok()),
160 last_update: db_info.last_update,
161 fields,
162 links,
163 };
164
165 info!(
166 fields_count = database_info.fields.len(),
167 links_count = database_info.links.len(),
168 "Database information retrieved successfully"
169 );
170
171 Ok(database_info)
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178 use crate::config::ClientConfig;
179
180 #[test]
181 fn test_empty_database_name_validation() {
182 use tokio_test;
183
184 let config = ClientConfig::new();
185 let client = PubMedClient::with_config(config);
186
187 let result = tokio_test::block_on(client.get_database_info(""));
188 assert!(result.is_err());
189
190 if let Err(e) = result {
191 assert!(e.to_string().contains("empty"));
192 }
193 }
194
195 #[test]
196 fn test_whitespace_database_name_validation() {
197 use tokio_test;
198
199 let config = ClientConfig::new();
200 let client = PubMedClient::with_config(config);
201
202 let result = tokio_test::block_on(client.get_database_info(" "));
203 assert!(result.is_err());
204
205 if let Err(e) = result {
206 assert!(e.to_string().contains("empty"));
207 }
208 }
209}