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