tools_interface/
missing_topics.rs1use crate::{Site, Tool, ToolsError};
20use async_trait::async_trait;
21use serde_json::Value;
22
23#[derive(Debug, Default, PartialEq)]
24pub struct MissingTopics {
25 site: Site,
26 category_depth: Option<u32>,
27 category: Option<String>,
28 article: Option<String>,
29 occurs_more_often_than: Option<u32>,
30 no_template_links: Option<bool>,
31 no_singles: bool,
32
33 url_used: String,
34 results: Vec<(String, u64)>,
35 tool_url: String,
36}
37
38impl MissingTopics {
39 pub fn new(site: Site) -> Self {
41 Self {
42 site,
43 tool_url: "https://missingtopics.toolforge.org/".to_string(),
44 ..Default::default()
45 }
46 }
47
48 pub fn with_category(mut self, category: &str, category_depth: u32) -> Self {
52 self.category = Some(category.into());
53 self.category_depth = Some(category_depth);
54 self
55 }
56
57 pub fn with_article(mut self, article: &str) -> Self {
60 self.article = Some(article.into());
61 self
62 }
63
64 pub fn limit(mut self, occurs_more_often_than: u32) -> Self {
66 self.no_singles = true;
67 self.occurs_more_often_than = Some(occurs_more_often_than);
68 self
69 }
70
71 pub fn no_template_links(mut self, no_template_links: bool) -> Self {
73 self.no_template_links = Some(no_template_links);
74 self
75 }
76
77 pub fn url_used(&self) -> &str {
79 &self.url_used
80 }
81
82 pub fn results(&self) -> &[(String, u64)] {
85 &self.results
86 }
87
88 pub fn site(&self) -> &Site {
90 &self.site
91 }
92}
93
94#[async_trait]
95impl Tool for MissingTopics {
96 fn generate_paramters(&self) -> Result<Vec<(String, String)>, ToolsError> {
97 let mut parameters: Vec<(String, String)> = [
98 ("language".to_string(), self.site.language().to_string()),
99 ("project".to_string(), self.site.project().to_string()),
100 ("doit".to_string(), "Run".to_string()),
101 ("wikimode".to_string(), "json".to_string()),
102 ]
103 .to_vec();
104
105 if self.category.is_some() && self.category_depth.is_some() && self.article.is_some() {
106 return Err(ToolsError::Tool(
107 "Only one of category or article can be set".to_string(),
108 ));
109 }
110 if let (Some(category), Some(category_depth)) = (&self.category, &self.category_depth) {
111 parameters.push(("category".to_string(), category.to_string()));
112 parameters.push(("depth".to_string(), category_depth.to_string()));
113 } else if let Some(article) = &self.article {
114 parameters.push(("article".to_string(), article.to_string()));
115 } else {
116 return Err(ToolsError::Tool(
117 "Either category or article must be set".to_string(),
118 ));
119 }
120 match self.no_singles {
121 true => parameters.push(("nosingles".to_string(), "1".to_string())),
122 false => parameters.push(("nosingles".to_string(), "0".to_string())),
123 }
124 match self.no_template_links {
125 Some(true) => parameters.push(("no_template_links".to_string(), "1".to_string())),
126 Some(false) => parameters.push(("no_template_links".to_string(), "0".to_string())),
127 _ => {}
128 }
129 if let Some(occurs_more_often_than) = self.occurs_more_often_than {
130 parameters.push(("limitnum".to_string(), occurs_more_often_than.to_string()));
131 }
132 Ok(parameters)
133 }
134
135 #[cfg(feature = "tokio")]
136 async fn run(&mut self) -> Result<(), ToolsError> {
138 let url = &self.tool_url;
139 let parameters = self.generate_paramters()?;
140 let client = crate::ToolsInterface::tokio_client()?;
141 let response = client.get(url).query(¶meters).send().await?;
142 let j: Value = response.json().await?;
143 self.from_json(j)
144 }
145
146 #[cfg(feature = "blocking")]
147 fn run_blocking(&mut self) -> Result<(), ToolsError> {
149 let url = &self.tool_url;
150 let parameters = self.generate_paramters()?;
151 let client = crate::ToolsInterface::blocking_client()?;
152 let j: Value = client.get(url).query(¶meters).send()?.json()?;
153 self.from_json(j)
154 }
155
156 fn from_json(&mut self, j: Value) -> Result<(), ToolsError> {
157 if j["status"].as_str() != Some("OK") {
158 return Err(ToolsError::Tool(format!(
159 "MissingTopics status is not OK: {:?}",
160 j["status"]
161 )));
162 }
163 self.results = j["results"]
164 .as_object()
165 .ok_or(ToolsError::Json("['results'] has no object".into()))?
166 .iter()
167 .filter_map(|(k, v)| Some((k.to_string(), v.as_u64()?)))
168 .collect();
169 self.url_used = j["url"]
170 .as_str()
171 .ok_or(ToolsError::Json("['url'] is missing".into()))?
172 .to_string();
173 Ok(())
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::Site;
181 use serde_json::json;
182 use wiremock::matchers::{method, path, query_param_contains};
183 use wiremock::{Mock, MockServer, ResponseTemplate};
184
185 async fn get_mock_server() -> MockServer {
186 let mock_path = format!("/");
187 let mock_server = MockServer::start().await;
188 Mock::given(method("GET"))
189 .and(query_param_contains("language","de"))
190 .and(query_param_contains("project","wikipedia"))
191 .and(query_param_contains("article","Biologie"))
192 .and(query_param_contains("language","de"))
193 .and(query_param_contains("doit","Run"))
194 .and(query_param_contains("wikimode","json"))
195 .and(query_param_contains("no_template_links","1"))
196 .and(path(&mock_path))
197 .respond_with(ResponseTemplate::new(200).set_body_json(json!({"results":{"Ethnobiologie":4,"Landschaftsdiversität":3,"Micrographia":4,"Spezielle_Botanik":6,"Wbetavirus":4,"Zellphysiologie":4},"status":"OK","url":"https://missingtopics.toolforge.org/?language=de&project=wikipedia&depth=1&category=&article=Biologie&wikimode=json&limitnum=1¬emplatelinks=0"})))
198 .mount(&mock_server)
199 .await;
200 mock_server
201 }
202
203 #[cfg(feature = "tokio")]
204 #[tokio::test]
205 async fn test_missing_topics_run_async() {
206 let mock_server = get_mock_server().await;
207 let mut mt = MissingTopics::new(Site::from_wiki("dewiki").unwrap())
208 .with_article("Biologie")
209 .no_template_links(true);
210 mt.tool_url = format!("{}/", mock_server.uri());
211 mt.run().await.unwrap();
212 assert_eq!(mt.results.len(), 6);
213 assert_eq!(mt.results[5].0, "Zellphysiologie");
214 assert_eq!(mt.results[5].1, 4);
215 assert_eq!(mt.url_used, "https://missingtopics.toolforge.org/?language=de&project=wikipedia&depth=1&category=&article=Biologie&wikimode=json&limitnum=1¬emplatelinks=0")
216 }
217}