tools_interface/
pagepile.rs

1/// # PagePile
2/// Module for interacting with the PagePile tool.
3/// You can retrieve the list of pages in a PagePile by ID.
4/// There are blocking and async methods available.
5///
6/// ## Example
7/// ```ignore
8/// let mut pp = PagePile::new(12345); // Your PagePile ID
9/// pp.get().await.unwrap();
10/// let wiki = pp.wiki().unwrap();
11/// let page_titles = pp.prefixed_titles();
12/// ```
13use crate::{Site, Tool, ToolsError, fancy_title::FancyTitle};
14use async_trait::async_trait;
15use serde_json::{Value, json};
16
17#[derive(Debug, Default, PartialEq)]
18pub struct PagePile {
19    id: u32,
20
21    prefixed_titles: Vec<String>,
22    language: Option<String>,
23    project: Option<String>,
24    wiki: Option<String>,
25}
26
27impl PagePile {
28    /// Creates a new PagePile with the given ID.
29    pub fn new(id: u32) -> Self {
30        Self {
31            id,
32            ..Default::default()
33        }
34    }
35
36    /// Returns the namespace-prefixed pages in the PagePile.
37    pub fn prefixed_titles(&self) -> &[String] {
38        &self.prefixed_titles
39    }
40
41    /// Returns the language for the PagePile, if known.
42    pub fn language(&self) -> Option<&String> {
43        self.language.as_ref()
44    }
45
46    /// Returns the project for the PagePile, if known.
47    pub fn project(&self) -> Option<&String> {
48        self.project.as_ref()
49    }
50
51    /// Returns the wiki for the PagePile, if known.
52    pub fn wiki(&self) -> Option<&String> {
53        self.wiki.as_ref()
54    }
55
56    /// Returns the site for the PagePile, if wither the wiki, or the language and project are known.
57    pub fn site(&self) -> Option<crate::Site> {
58        Some(match &self.wiki {
59            Some(wiki) => Site::from_wiki(wiki)?,
60            None => Site::from_language_project(self.language.as_ref()?, self.project.as_ref()?),
61        })
62    }
63
64    pub async fn as_json(&self) -> Option<Value> {
65        let site = self.site()?;
66        let api = site.api().await.ok()?;
67        Some(json!({
68            "pages": self.prefixed_titles()
69                .iter()
70                .map(|prefixed_title| FancyTitle::from_prefixed(prefixed_title, &api).to_json())
71                .collect::<Vec<Value>>(),
72            "site": site,
73        }))
74    }
75}
76
77#[async_trait]
78impl Tool for PagePile {
79    fn get_url(&self) -> String {
80        format!(
81            "https://pagepile.toolforge.org/api.php?id={id}&action=get_data&doit&format=json",
82            id = self.id
83        )
84    }
85
86    fn set_from_json(&mut self, j: Value) -> Result<(), ToolsError> {
87        self.language = j["language"].as_str().map(|s| s.to_string());
88        self.project = j["project"].as_str().map(|s| s.to_string());
89        self.wiki = j["wiki"].as_str().map(|s| s.to_string());
90        self.prefixed_titles = j["pages"]
91            .as_array()
92            .ok_or(ToolsError::Json("['pages'] has no rows array".into()))?
93            .iter()
94            .filter_map(|page| page.as_str())
95            .map(|prefixed_title| prefixed_title.to_string())
96            .collect();
97        let pages_returned = j["pages_returned"].as_i64().ok_or(ToolsError::Json(
98            "['pages_returned'] is not an integer".into(),
99        ))?;
100        let pages_total = j["pages_total"]
101            .as_i64()
102            .ok_or(ToolsError::Json("['pages_total'] is not an integer".into()))?;
103        if pages_returned != pages_total {
104            return Err(ToolsError::Json(format!(
105                "pages_returned ({}) != pages_total ({})",
106                pages_returned, pages_total
107            )));
108        }
109        if pages_total != self.prefixed_titles.len() as i64 {
110            return Err(ToolsError::Json(format!(
111                "pages_total ({}) != prefixed_titles.len() ({})",
112                pages_total,
113                self.prefixed_titles.len()
114            )));
115        }
116        Ok(())
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_pagepile_new() {
126        let pp = PagePile::new(1);
127        assert_eq!(pp.id, 1);
128    }
129
130    #[cfg(feature = "blocking")]
131    #[test]
132    fn test_pagepile_get_blocking() {
133        let mut pp = PagePile::new(51805);
134        pp.run_blocking().unwrap();
135        assert_eq!(pp.language().unwrap(), "de");
136        assert_eq!(pp.project().unwrap(), "wikipedia");
137        assert_eq!(pp.wiki().unwrap(), "dewiki");
138        assert_eq!(pp.prefixed_titles().len(), 1747);
139    }
140
141    #[cfg(feature = "tokio")]
142    #[tokio::test]
143    async fn test_pagepile_get_async() {
144        let mut pp = PagePile::new(51805);
145        pp.run().await.unwrap();
146        assert_eq!(pp.language().unwrap(), "de");
147        assert_eq!(pp.project().unwrap(), "wikipedia");
148        assert_eq!(pp.wiki().unwrap(), "dewiki");
149        assert_eq!(pp.prefixed_titles().len(), 1747);
150    }
151}