#![deny(missing_docs)]
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::page_query::{PageContext, PageQueryResult, PageQueryResultList};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PageCategoryEntry {
#[serde(skip)]
pub page: PageContext,
#[serde(default)]
pub ns: i64,
#[serde(default)]
pub title: String,
pub sortkey: Option<String>,
pub sortkeyprefix: Option<String>,
pub timestamp: Option<String>,
pub hidden: Option<Value>,
}
impl PageCategoryEntry {
pub fn is_hidden(&self) -> bool {
match &self.hidden {
Some(Value::Bool(b)) => *b,
Some(Value::String(_)) => true, _ => false,
}
}
}
impl PageQueryResult for PageCategoryEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["categories"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageCategoryEntry =
serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageCategoryList = PageQueryResultList<PageCategoryEntry>;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn sample_result() -> Value {
json!({
"query": {
"pages": {
"736": {
"pageid": 736,
"ns": 0,
"title": "Albert Einstein",
"categories": [
{"ns": 14, "title": "Category:1879 births"},
{"ns": 14, "title": "Category:German physicists"},
{"ns": 14, "title": "Category:Nobel laureates in Physics"}
]
}
}
}
})
}
#[test]
fn from_result() {
let list = PageCategoryList::from_result(&sample_result());
assert_eq!(list.len(), 3);
assert_eq!(list.items()[0].page.title, "Albert Einstein");
assert_eq!(list.items()[0].ns, 14);
assert_eq!(list.items()[0].title, "Category:1879 births");
}
#[test]
fn from_result_v2() {
let result = json!({
"query": {
"pages": [
{
"pageid": 1, "ns": 0, "title": "Page A",
"categories": [{"ns": 14, "title": "Category:Foo"}]
},
{
"pageid": 2, "ns": 0, "title": "Page B",
"categories": [
{"ns": 14, "title": "Category:Bar"},
{"ns": 14, "title": "Category:Baz"}
]
}
]
}
});
let list = PageCategoryList::from_result(&result);
assert_eq!(list.len(), 3);
assert_eq!(list.items()[0].page.title, "Page A");
assert_eq!(list.items()[1].page.title, "Page B");
}
#[test]
fn page_with_no_categories() {
let result = json!({
"query": {"pages": {"1": {"pageid": 1, "ns": 0, "title": "Test"}}}
});
let list = PageCategoryList::from_result(&result);
assert!(list.is_empty());
}
#[test]
fn with_sortkey_and_timestamp() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Test",
"categories": [{
"ns": 14,
"title": "Category:Foo",
"sortkey": "TEST",
"sortkeyprefix": "Test",
"timestamp": "2024-01-15T10:30:00Z"
}]
}
}
}
});
let list = PageCategoryList::from_result(&result);
assert_eq!(list.len(), 1);
assert_eq!(list.items()[0].sortkey.as_deref(), Some("TEST"));
assert_eq!(list.items()[0].sortkeyprefix.as_deref(), Some("Test"));
assert_eq!(
list.items()[0].timestamp.as_deref(),
Some("2024-01-15T10:30:00Z")
);
}
#[test]
fn hidden_category_v1() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Test",
"categories": [{"ns": 14, "title": "Category:Hidden", "hidden": ""}]
}
}
}
});
let list = PageCategoryList::from_result(&result);
assert!(list.items()[0].is_hidden());
}
#[test]
fn hidden_category_v2() {
let result = json!({
"query": {
"pages": [{
"pageid": 1, "ns": 0, "title": "Test",
"categories": [{"ns": 14, "title": "Category:Hidden", "hidden": true}]
}]
}
});
let list = PageCategoryList::from_result(&result);
assert!(list.items()[0].is_hidden());
}
#[test]
fn not_hidden() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Test",
"categories": [{"ns": 14, "title": "Category:Visible"}]
}
}
}
});
let list = PageCategoryList::from_result(&result);
assert!(!list.items()[0].is_hidden());
}
#[tokio::test]
async fn integration_fetch_categories() {
use crate::Api;
use crate::action_api::{ActionApiQuery, ActionApiQueryCommonBuilder, ActionApiRunnable};
use wiremock::matchers::query_param;
use wiremock::{Mock, ResponseTemplate};
let server = crate::test_helpers::test_helpers_mod::start_enwiki_mock().await;
Mock::given(query_param("prop", "categories"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"batchcomplete": "",
"query": {
"pages": {
"736": {
"pageid": 736, "ns": 0, "title": "Albert Einstein",
"categories": [
{"ns": 14, "title": "Category:1879 births"},
{"ns": 14, "title": "Category:German physicists"},
{"ns": 14, "title": "Category:Nobel laureates in Physics"}
]
}
}
}
})))
.mount(&server)
.await;
let api = Api::new(&server.uri()).await.unwrap();
let result = ActionApiQuery::categories()
.cllimit(10)
.titles(&["Albert Einstein"])
.run(&api)
.await
.unwrap();
let list = PageCategoryList::from_result(&result);
assert!(!list.is_empty());
for entry in list.items() {
assert_eq!(entry.page.title, "Albert Einstein");
assert_eq!(entry.ns, 14);
assert!(entry.title.starts_with("Category:"));
}
}
}