use crate::{
error::RestApiError,
prelude::{Diff, HtmlFlavor, Lint, RestApi, RevisionInfo},
};
use serde_json::{Value, from_value};
use std::collections::HashMap;
#[derive(Clone, Copy, Debug)]
pub struct Revision {
id: usize,
}
impl Revision {
pub const fn new(id: usize) -> Self {
Self { id }
}
pub const fn id(&self) -> usize {
self.id
}
pub async fn get(&self, api: &RestApi) -> Result<(RevisionInfo, String), RestApiError> {
let path = format!("/revision/{}", self.id);
let params = HashMap::new();
let request = api
.build_request(path, params, reqwest::Method::GET)
.await?
.build()?;
let response = api.execute(request).await?;
let j: Value = response.json().await?;
let wikitext = j["source"]
.as_str()
.ok_or(RestApiError::MissingResults)?
.to_string();
let ret = from_value::<RevisionInfo>(j)?;
Ok((ret, wikitext))
}
pub async fn get_bare(&self, api: &RestApi) -> Result<(RevisionInfo, String), RestApiError> {
let path = format!("/revision/{}/bare", self.id);
let params = HashMap::new();
let request = api
.build_request(path, params, reqwest::Method::GET)
.await?
.build()?;
let response = api.execute(request).await?;
let j: Value = response.json().await?;
let html_url = j["html_url"]
.as_str()
.ok_or(RestApiError::MissingResults)?
.to_string();
let ret = from_value::<RevisionInfo>(j)?;
Ok((ret, html_url))
}
pub async fn get_html(
&self,
api: &RestApi,
stash: bool,
flavor: HtmlFlavor,
) -> Result<String, RestApiError> {
let path = format!("/revision/{}/html", self.id);
let mut params = HashMap::new();
params.insert("stash".to_string(), stash.to_string());
params.insert("flavor".to_string(), flavor.to_string());
let request = api
.build_request(path, params, reqwest::Method::GET)
.await?
.build()?;
let response = api.execute(request).await?;
let ret = response.text().await?;
Ok(ret)
}
pub async fn get_with_html(
&self,
api: &RestApi,
stash: bool,
flavor: HtmlFlavor,
) -> Result<(RevisionInfo, String), RestApiError> {
let path = format!("/revision/{}/with_html", self.id);
let mut params = HashMap::new();
params.insert("stash".to_string(), stash.to_string());
params.insert("flavor".to_string(), flavor.to_string());
let request = api
.build_request(path, params, reqwest::Method::GET)
.await?
.build()?;
let response = api.execute(request).await?;
let j: Value = response.json().await?;
let html = j["html"]
.as_str()
.ok_or(RestApiError::MissingResults)?
.to_string();
let ret = from_value::<RevisionInfo>(j)?;
Ok((ret, html))
}
pub async fn get_lint(&self, api: &RestApi) -> Result<Vec<Lint>, RestApiError> {
let path = format!("/revision/{}/lint", self.id);
let params = HashMap::new();
let request = api
.build_request(path, params, reqwest::Method::GET)
.await?
.build()?;
let response = api.execute(request).await?;
let ret: Vec<Lint> = response.json().await?;
Ok(ret)
}
pub async fn get_compare(&self, api: &RestApi, to: usize) -> Result<Diff, RestApiError> {
let path = format!("/revision/{}/compare/{to}", self.id);
let params = HashMap::new();
let request = api
.build_request(path, params, reqwest::Method::GET)
.await?
.build()?;
let response = api.execute(request).await?;
let ret: Diff = response.json().await?;
Ok(ret)
}
}
#[cfg(test)]
mod tests {
use super::*;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
const TEST_REVISION_ID: usize = 1316925953;
const TEST_REVISION_OLD_ID: usize = 1316608902;
async fn get_mock_api(test_file: &str, test_path: &str) -> (RestApi, MockServer) {
let mock_path = format!("w/rest.php/v1{}", test_path.replace(' ', "%20"));
let mock_server = MockServer::start().await;
let test_text: String =
std::fs::read_to_string(format!("test_data/{test_file}")).expect("Test file missing");
if test_file.ends_with(".json") {
let json: Value = serde_json::from_str(&test_text).expect("Failed to parse JSON");
Mock::given(method("GET"))
.and(path(&mock_path))
.respond_with(ResponseTemplate::new(200).set_body_json(&json))
.mount(&mock_server)
.await;
} else {
Mock::given(method("GET"))
.and(path(&mock_path))
.respond_with(ResponseTemplate::new(200).set_body_string(&test_text))
.mount(&mock_server)
.await;
}
let api = RestApi::builder(&(mock_server.uri() + "/w/rest.php"))
.expect("Failed to create RestApi")
.build();
(api, mock_server)
}
#[tokio::test]
async fn test_get() {
let (api, _mock_server) = get_mock_api(
"revision_get.json",
&format!("/revision/{TEST_REVISION_ID}"),
)
.await;
let revision = Revision::new(TEST_REVISION_ID);
let (revision_info, wikitext) = revision
.get(&api)
.await
.expect("Failed to get page content");
assert_eq!(revision_info.size, 114334);
assert!(wikitext.contains("[[FreeBSD]]"));
}
#[tokio::test]
async fn test_get_html() {
let (api, _mock_server) = get_mock_api(
"revision_get_html.html",
&format!("/revision/{TEST_REVISION_ID}/html"),
)
.await;
let revision = Revision::new(TEST_REVISION_ID);
let html = revision
.get_html(&api, false, HtmlFlavor::View)
.await
.expect("Failed to get page content");
assert!(html.contains("<title>Rust (programming language)</title>"));
}
#[tokio::test]
async fn test_get_with_html() {
let (api, _mock_server) = get_mock_api(
"revision_get_with_html.json",
&format!("/revision/{TEST_REVISION_ID}/with_html"),
)
.await;
let revision = Revision::new(TEST_REVISION_ID);
let (revision_info, html) = revision
.get_with_html(&api, false, HtmlFlavor::View)
.await
.expect("Failed to get page content");
assert_eq!(revision_info.size, 114334);
assert!(html.contains("<title>Rust (programming language)</title>"));
}
#[tokio::test]
async fn test_get_bare() {
let (api, _mock_server) = get_mock_api(
"revision_get_bare.json",
&format!("/revision/{TEST_REVISION_ID}/bare"),
)
.await;
let revision = Revision::new(TEST_REVISION_ID);
let (revision_info, html_url) = revision
.get_bare(&api)
.await
.expect("Failed to get page content");
assert_eq!(revision_info.size, 114334);
assert_eq!(
html_url,
"https://en.wikipedia.org/w/rest.php/v1/revision/1316925953/html"
);
}
#[tokio::test]
async fn test_get_compare() {
let (api, _mock_server) = get_mock_api(
"revision_compare.json",
&format!("/revision/{TEST_REVISION_ID}/compare/{TEST_REVISION_OLD_ID}"),
)
.await;
let revision = Revision::new(TEST_REVISION_ID);
let result = revision
.get_compare(&api, TEST_REVISION_OLD_ID)
.await
.expect("Failed to get page content");
assert_eq!(result.diff.len(), 6);
assert_eq!(result.from.sections.len(), 52);
assert_eq!(result.to.sections.len(), 52);
}
#[tokio::test]
async fn test_get_lint() {
let (api, _mock_server) = get_mock_api(
"revision_lint.json",
&format!("/revision/{TEST_REVISION_ID}/lint"),
)
.await;
let page = Revision::new(TEST_REVISION_ID);
let lints = page
.get_lint(&api)
.await
.expect("Failed to get page content");
assert_eq!(lints.len(), 14);
assert!(lints.iter().any(|lint| lint.type_name == "duplicate-ids"
&& lint.template_info.as_ref().unwrap().name == "Template:Cite_web"));
}
}