#![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 PageLinkEntry {
#[serde(skip)]
pub page: PageContext,
#[serde(default)]
pub ns: i64,
#[serde(default)]
pub title: String,
}
impl PageQueryResult for PageLinkEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["links"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageLinkEntry = serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageLinkList = PageQueryResultList<PageLinkEntry>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PageTemplateEntry {
#[serde(skip)]
pub page: PageContext,
#[serde(default)]
pub ns: i64,
#[serde(default)]
pub title: String,
}
impl PageQueryResult for PageTemplateEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["templates"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageTemplateEntry =
serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageTemplateList = PageQueryResultList<PageTemplateEntry>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PageImageEntry {
#[serde(skip)]
pub page: PageContext,
#[serde(default)]
pub ns: i64,
#[serde(default)]
pub title: String,
}
impl PageQueryResult for PageImageEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["images"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageImageEntry = serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageImageList = PageQueryResultList<PageImageEntry>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PageExtLinkEntry {
pub page: PageContext,
pub url: String,
}
impl PageQueryResult for PageExtLinkEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["extlinks"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let url = v["url"]
.as_str()
.or_else(|| v["*"].as_str())?
.to_string();
Some(Self {
page: ctx.clone(),
url,
})
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageExtLinkList = PageQueryResultList<PageExtLinkEntry>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PageLinksHereEntry {
#[serde(skip)]
pub page: PageContext,
pub pageid: Option<u64>,
#[serde(default)]
pub ns: i64,
#[serde(default)]
pub title: String,
pub redirect: Option<Value>,
}
impl PageLinksHereEntry {
pub fn is_redirect(&self) -> bool {
match &self.redirect {
Some(Value::Bool(b)) => *b,
Some(Value::String(_)) => true,
_ => false,
}
}
}
impl PageQueryResult for PageLinksHereEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["linkshere"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageLinksHereEntry =
serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageLinksHereList = PageQueryResultList<PageLinksHereEntry>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PageIwLinkEntry {
#[serde(skip)]
pub page: PageContext,
#[serde(default)]
pub prefix: String,
#[serde(rename = "*", default)]
pub title: String,
pub url: Option<String>,
}
impl PageQueryResult for PageIwLinkEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["iwlinks"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageIwLinkEntry = serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageIwLinkList = PageQueryResultList<PageIwLinkEntry>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PageLangLinkEntry {
#[serde(skip)]
pub page: PageContext,
#[serde(default)]
pub lang: String,
#[serde(rename = "*", default)]
pub title: String,
pub url: Option<String>,
pub autonym: Option<String>,
pub langname: Option<String>,
}
impl PageQueryResult for PageLangLinkEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["langlinks"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageLangLinkEntry =
serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageLangLinkList = PageQueryResultList<PageLangLinkEntry>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PageContributorEntry {
pub page: PageContext,
pub userid: u64,
pub name: String,
}
impl PageQueryResult for PageContributorEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["contributors"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
Some(Self {
page: ctx.clone(),
userid: v["userid"].as_u64()?,
name: v["name"].as_str()?.to_string(),
})
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageContributorList = PageQueryResultList<PageContributorEntry>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PageRedirectEntry {
#[serde(skip)]
pub page: PageContext,
pub pageid: Option<u64>,
#[serde(default)]
pub ns: i64,
#[serde(default)]
pub title: String,
pub fragment: Option<String>,
}
impl PageQueryResult for PageRedirectEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["redirects"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageRedirectEntry =
serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageRedirectList = PageQueryResultList<PageRedirectEntry>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PageFileUsageEntry {
#[serde(skip)]
pub page: PageContext,
pub pageid: Option<u64>,
#[serde(default)]
pub ns: i64,
#[serde(default)]
pub title: String,
pub redirect: Option<Value>,
}
impl PageQueryResult for PageFileUsageEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["fileusage"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageFileUsageEntry =
serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageFileUsageList = PageQueryResultList<PageFileUsageEntry>;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PageTranscludedInEntry {
#[serde(skip)]
pub page: PageContext,
pub pageid: Option<u64>,
#[serde(default)]
pub ns: i64,
#[serde(default)]
pub title: String,
pub redirect: Option<Value>,
}
impl PageQueryResult for PageTranscludedInEntry {
fn from_page_value(page: &Value) -> Vec<Self> {
let ctx = PageContext::from_value(page);
page["transcludedin"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|v| {
let mut entry: PageTranscludedInEntry =
serde_json::from_value(v.clone()).ok()?;
entry.page = ctx.clone();
Some(entry)
})
.collect()
})
.unwrap_or_default()
}
}
pub type PageTranscludedInList = PageQueryResultList<PageTranscludedInEntry>;
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn links_from_result() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Test",
"links": [
{"ns": 0, "title": "Physics"},
{"ns": 0, "title": "Mathematics"},
{"ns": 14, "title": "Category:Science"}
]
}
}
}
});
let list = PageLinkList::from_result(&result);
assert_eq!(list.len(), 3);
assert_eq!(list.items()[0].page.title, "Test");
assert_eq!(list.items()[0].title, "Physics");
assert_eq!(list.items()[2].ns, 14);
}
#[test]
fn links_empty_page() {
let result = json!({
"query": {"pages": {"1": {"pageid": 1, "ns": 0, "title": "Empty"}}}
});
let list = PageLinkList::from_result(&result);
assert!(list.is_empty());
}
#[test]
fn templates_from_result() {
let result = json!({
"query": {
"pages": [{
"pageid": 1, "ns": 0, "title": "Test",
"templates": [
{"ns": 10, "title": "Template:Cite web"},
{"ns": 10, "title": "Template:Infobox"}
]
}]
}
});
let list = PageTemplateList::from_result(&result);
assert_eq!(list.len(), 2);
assert_eq!(list.items()[0].title, "Template:Cite web");
assert_eq!(list.items()[0].ns, 10);
}
#[test]
fn images_from_result() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Test",
"images": [
{"ns": 6, "title": "File:Example.jpg"},
{"ns": 6, "title": "File:Photo.png"}
]
}
}
}
});
let list = PageImageList::from_result(&result);
assert_eq!(list.len(), 2);
assert_eq!(list.items()[0].ns, 6);
assert_eq!(list.items()[0].title, "File:Example.jpg");
}
#[test]
fn extlinks_from_result_v1() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Test",
"extlinks": [
{"*": "https://example.com"},
{"*": "https://example.org"}
]
}
}
}
});
let list = PageExtLinkList::from_result(&result);
assert_eq!(list.len(), 2);
assert_eq!(list.items()[0].url, "https://example.com");
assert_eq!(list.items()[0].page.title, "Test");
}
#[test]
fn extlinks_from_result_v2() {
let result = json!({
"query": {
"pages": [{
"pageid": 1, "ns": 0, "title": "Test",
"extlinks": [
{"url": "https://example.com"}
]
}]
}
});
let list = PageExtLinkList::from_result(&result);
assert_eq!(list.len(), 1);
assert_eq!(list.items()[0].url, "https://example.com");
}
#[test]
fn linkshere_from_result() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Target",
"linkshere": [
{"pageid": 10, "ns": 0, "title": "Linking page"},
{"pageid": 11, "ns": 0, "title": "Redirect", "redirect": ""}
]
}
}
}
});
let list = PageLinksHereList::from_result(&result);
assert_eq!(list.len(), 2);
assert_eq!(list.items()[0].page.title, "Target");
assert_eq!(list.items()[0].title, "Linking page");
assert!(!list.items()[0].is_redirect());
assert!(list.items()[1].is_redirect());
}
#[test]
fn iwlinks_from_result() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Test",
"iwlinks": [
{"prefix": "wikt", "*": "physics"}
]
}
}
}
});
let list = PageIwLinkList::from_result(&result);
assert_eq!(list.len(), 1);
assert_eq!(list.items()[0].prefix, "wikt");
assert_eq!(list.items()[0].title, "physics");
}
#[test]
fn langlinks_from_result() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Physics",
"langlinks": [
{"lang": "de", "*": "Physik"},
{"lang": "fr", "*": "Physique", "url": "https://fr.wikipedia.org/wiki/Physique"}
]
}
}
}
});
let list = PageLangLinkList::from_result(&result);
assert_eq!(list.len(), 2);
assert_eq!(list.items()[0].lang, "de");
assert_eq!(list.items()[0].title, "Physik");
assert!(list.items()[0].url.is_none());
assert_eq!(list.items()[1].lang, "fr");
assert!(list.items()[1].url.is_some());
}
#[test]
fn contributors_from_result() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Test",
"contributors": [
{"userid": 100, "name": "Alice"},
{"userid": 200, "name": "Bob"}
]
}
}
}
});
let list = PageContributorList::from_result(&result);
assert_eq!(list.len(), 2);
assert_eq!(list.items()[0].userid, 100);
assert_eq!(list.items()[0].name, "Alice");
assert_eq!(list.items()[1].name, "Bob");
}
#[test]
fn redirects_from_result() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 0, "title": "Physics",
"redirects": [
{"pageid": 50, "ns": 0, "title": "Fysics"},
{"pageid": 51, "ns": 0, "title": "Physics (science)", "fragment": "Overview"}
]
}
}
}
});
let list = PageRedirectList::from_result(&result);
assert_eq!(list.len(), 2);
assert_eq!(list.items()[0].title, "Fysics");
assert!(list.items()[0].fragment.is_none());
assert_eq!(list.items()[1].fragment.as_deref(), Some("Overview"));
}
#[test]
fn fileusage_from_result() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 6, "title": "File:Example.jpg",
"fileusage": [
{"pageid": 10, "ns": 0, "title": "Albert Einstein"},
{"pageid": 11, "ns": 0, "title": "Physics"}
]
}
}
}
});
let list = PageFileUsageList::from_result(&result);
assert_eq!(list.len(), 2);
assert_eq!(list.items()[0].page.title, "File:Example.jpg");
assert_eq!(list.items()[0].title, "Albert Einstein");
}
#[test]
fn transcludedin_from_result() {
let result = json!({
"query": {
"pages": {
"1": {
"pageid": 1, "ns": 10, "title": "Template:Infobox",
"transcludedin": [
{"pageid": 100, "ns": 0, "title": "Physics"},
{"pageid": 101, "ns": 0, "title": "Chemistry"}
]
}
}
}
});
let list = PageTranscludedInList::from_result(&result);
assert_eq!(list.len(), 2);
assert_eq!(list.items()[0].page.title, "Template:Infobox");
assert_eq!(list.items()[0].title, "Physics");
}
#[test]
fn links_multiple_pages() {
let result = json!({
"query": {
"pages": [
{
"pageid": 1, "ns": 0, "title": "Page A",
"links": [{"ns": 0, "title": "X"}]
},
{
"pageid": 2, "ns": 0, "title": "Page B",
"links": [{"ns": 0, "title": "Y"}, {"ns": 0, "title": "Z"}]
}
]
}
});
let list = PageLinkList::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");
}
}