macro_rules! anime_list_query {
($op:literal, $vars:literal, $media_args:literal) => {
concat!(
"query ",
$op,
$vars,
" {\n",
" Page(page: $page, perPage: $perPage) {\n",
" pageInfo { total currentPage lastPage hasNextPage }\n",
" media(",
$media_args,
") {\n",
" id\n",
" title { romaji english native }\n",
" description(asHtml: false)\n",
" startDate { year month day }\n",
" coverImage { large extraLarge }\n",
" bannerImage\n",
" genres\n",
" popularity\n",
" averageScore\n",
" format\n",
" countryOfOrigin\n",
" isAdult\n",
" }\n",
" }\n",
"}\n"
)
};
}
pub const SEARCH_ANIME: &str = anime_list_query!(
"SearchAnime",
"($query: String, $page: Int, $perPage: Int, $formatIn: [MediaFormat])",
"search: $query, type: ANIME, format_in: $formatIn, sort: [SEARCH_MATCH, POPULARITY_DESC]"
);
pub const LIST_TRENDING_ANIME: &str = anime_list_query!(
"ListTrendingAnime",
"($page: Int, $perPage: Int, $formatIn: [MediaFormat])",
"type: ANIME, format_in: $formatIn, sort: [TRENDING_DESC]"
);
pub const LIST_POPULAR_ANIME: &str = anime_list_query!(
"ListPopularAnime",
"($page: Int, $perPage: Int, $formatIn: [MediaFormat])",
"type: ANIME, format_in: $formatIn, sort: [POPULARITY_DESC]"
);
pub const LIST_TOP_SCORED_ANIME: &str = anime_list_query!(
"ListTopScoredAnime",
"($page: Int, $perPage: Int, $formatIn: [MediaFormat])",
"type: ANIME, format_in: $formatIn, sort: [SCORE_DESC]"
);
pub const MEDIA_DETAILS: &str = r#"
query MediaDetails($id: Int!) {
Media(id: $id, type: ANIME) {
id
title { romaji english native }
description(asHtml: false)
startDate { year month day }
endDate { year month day }
coverImage { large extraLarge }
bannerImage
genres
popularity
averageScore
episodes
duration
status
format
countryOfOrigin
isAdult
studios(isMain: true) {
nodes { name }
}
}
}
"#;
pub const SEARCH_STAFF: &str = r#"
query SearchStaff($query: String, $page: Int, $perPage: Int) {
Page(page: $page, perPage: $perPage) {
pageInfo { total currentPage lastPage hasNextPage }
staff(search: $query, sort: [SEARCH_MATCH]) {
id
name { full native }
image { large }
primaryOccupations
}
}
}
"#;
pub const STAFF_DETAILS: &str = r#"
query StaffDetails($id: Int!) {
Staff(id: $id) {
id
name { full native alternative }
image { large }
description
primaryOccupations
gender
dateOfBirth { year month day }
dateOfDeath { year month day }
homeTown
siteUrl
}
}
"#;
#[cfg(test)]
mod tests {
use super::*;
const ALL: &[(&str, &str)] = &[
("SEARCH_ANIME", SEARCH_ANIME),
("LIST_TRENDING_ANIME", LIST_TRENDING_ANIME),
("LIST_POPULAR_ANIME", LIST_POPULAR_ANIME),
("LIST_TOP_SCORED_ANIME", LIST_TOP_SCORED_ANIME),
("MEDIA_DETAILS", MEDIA_DETAILS),
("SEARCH_STAFF", SEARCH_STAFF),
("STAFF_DETAILS", STAFF_DETAILS),
];
const LIST_QUERIES: &[&str] = &[
SEARCH_ANIME,
LIST_TRENDING_ANIME,
LIST_POPULAR_ANIME,
LIST_TOP_SCORED_ANIME,
];
#[test]
fn queries_are_well_formed() {
for (name, q) in ALL {
let braces = q.chars().filter(|&c| c == '{').count();
let close = q.chars().filter(|&c| c == '}').count();
assert_eq!(braces, close, "{name}: unbalanced braces");
let parens = q.chars().filter(|&c| c == '(').count();
let cparens = q.chars().filter(|&c| c == ')').count();
assert_eq!(parens, cparens, "{name}: unbalanced parens");
assert_eq!(q.matches("query ").count(), 1, "{name}: not one operation");
}
}
#[test]
fn list_queries_share_one_media_selection() {
let selection = |q: &str| {
let start = q.find("media(").expect("media selection");
let brace = q[start..].find('{').expect("media body") + start;
let end = q[brace..].find("\n }").expect("media body end") + brace;
q[brace..end].to_string()
};
let first = selection(LIST_QUERIES[0]);
for q in &LIST_QUERIES[1..] {
assert_eq!(selection(q), first, "list media selections diverged");
}
for field in ["id", "title", "startDate", "coverImage", "genres", "format"] {
assert!(first.contains(field), "missing {field}");
}
}
#[test]
fn no_dead_queried_fields() {
for q in LIST_QUERIES {
for dead in ["episodes", "duration", "status"] {
assert!(
!q.contains(dead),
"list query still requests dead field {dead}"
);
}
}
assert!(
!MEDIA_DETAILS.contains("season"),
"details still requests season*"
);
assert!(
!SEARCH_STAFF.contains("languageV2"),
"staff search requests languageV2"
);
for (name, q) in ALL {
if let Some(start) = q.find("pageInfo {") {
let end = q[start..].find('}').expect("pageInfo body") + start;
assert!(
!q[start..end].contains("perPage"),
"{name}: pageInfo still selects perPage"
);
}
}
}
}