refactor: make serde optional and fix clippy warnings
This commit is contained in:
parent
df28d5c014
commit
18895752aa
28
Cargo.toml
28
Cargo.toml
|
@ -6,7 +6,7 @@ authors = ["Kyle Clemens <git@kyleclemens.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["logic"]
|
default = ["logic", "with_serde"]
|
||||||
|
|
||||||
logic = [
|
logic = [
|
||||||
"cssparser",
|
"cssparser",
|
||||||
|
@ -14,23 +14,37 @@ logic = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"scraper",
|
"scraper",
|
||||||
]
|
]
|
||||||
|
with_serde = [
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"url_serde",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cssparser = { version = "0.24", optional = true }
|
cssparser = { version = "0.24", optional = true }
|
||||||
failure = { version = "0.1", optional = true }
|
failure = { version = "0.1", optional = true }
|
||||||
lazy_static = { version = "1", optional = true }
|
lazy_static = { version = "1", optional = true }
|
||||||
scraper = { version = "0.9", optional = true }
|
scraper = { version = "0.9", optional = true }
|
||||||
serde = "1"
|
serde = { version = "1", optional = true }
|
||||||
serde_derive = "1"
|
serde_derive = { version = "1", optional = true }
|
||||||
serde_json = "1"
|
|
||||||
url = "1"
|
url = "1"
|
||||||
url_serde = "0.2"
|
url_serde = { version = "0.2", optional = true }
|
||||||
|
|
||||||
[dependencies.chrono]
|
# with serde
|
||||||
|
[target.'cfg(feature = "with_serde")'.dependencies.chrono]
|
||||||
version = "0.4"
|
version = "0.4"
|
||||||
features = ["serde"]
|
features = ["serde"]
|
||||||
|
|
||||||
[dependencies.ffxiv_types]
|
[target.'cfg(feature = "with_serde")'.dependencies.ffxiv_types]
|
||||||
version = "1"
|
version = "1"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["worlds", "races", "clans", "guardians", "with_serde"]
|
features = ["worlds", "races", "clans", "guardians", "with_serde"]
|
||||||
|
|
||||||
|
# without serde
|
||||||
|
[target.'cfg(not(feature = "with_serde"))'.dependencies.chrono]
|
||||||
|
version = "0.4"
|
||||||
|
|
||||||
|
[target.'cfg(not(feature = "with_serde"))'.dependencies.ffxiv_types]
|
||||||
|
version = "1"
|
||||||
|
default-features = false
|
||||||
|
features = ["worlds", "races", "clans", "guardians"]
|
||||||
|
|
|
@ -20,7 +20,7 @@ pub enum Error {
|
||||||
impl Error {
|
impl Error {
|
||||||
pub fn missing_element(select: &scraper::Selector) -> Self {
|
pub fn missing_element(select: &scraper::Selector) -> Self {
|
||||||
use cssparser::ToCss;
|
use cssparser::ToCss;
|
||||||
let css = select.selectors.iter().map(|x| x.to_css_string()).collect::<Vec<_>>().join(" ");
|
let css = select.selectors.iter().map(ToCss::to_css_string).collect::<Vec<_>>().join(" ");
|
||||||
Error::MissingElement(css)
|
Error::MissingElement(css)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
#![feature(crate_visibility_modifier)]
|
#![feature(crate_visibility_modifier)]
|
||||||
|
#![allow(clippy::unreadable_literal)]
|
||||||
|
|
||||||
#[cfg(feature = "logic")] extern crate failure;
|
|
||||||
#[cfg(feature = "logic")] #[macro_use] extern crate lazy_static;
|
|
||||||
pub extern crate ffxiv_types;
|
pub extern crate ffxiv_types;
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
|
|
||||||
#[cfg(feature = "logic")]
|
#[cfg(feature = "logic")]
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
|
@ -14,6 +14,8 @@ use scraper::{
|
||||||
|
|
||||||
macro_rules! selectors {
|
macro_rules! selectors {
|
||||||
($($name:ident => $selector:expr);+$(;)?) => {
|
($($name:ident => $selector:expr);+$(;)?) => {
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
$(
|
$(
|
||||||
static ref $name: scraper::Selector = scraper::Selector::parse($selector).unwrap();
|
static ref $name: scraper::Selector = scraper::Selector::parse($selector).unwrap();
|
||||||
|
@ -38,7 +40,7 @@ crate fn plain_parse(html: &Html, select: &scraper::Selector) -> Result<String>
|
||||||
let string = html
|
let string = html
|
||||||
.select(select)
|
.select(select)
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::missing_element(select))?
|
.ok_or_else(|| Error::missing_element(select))?
|
||||||
.text()
|
.text()
|
||||||
.collect();
|
.collect();
|
||||||
Ok(string)
|
Ok(string)
|
||||||
|
@ -48,13 +50,13 @@ crate fn plain_parse_elem<'a>(html: ElementRef<'a>, select: &scraper::Selector)
|
||||||
let string = html
|
let string = html
|
||||||
.select(select)
|
.select(select)
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::missing_element(select))?
|
.ok_or_else(|| Error::missing_element(select))?
|
||||||
.text()
|
.text()
|
||||||
.collect();
|
.collect();
|
||||||
Ok(string)
|
Ok(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
crate fn parse_id<'a>(a: &'a Element) -> Result<u64> {
|
crate fn parse_id(a: &Element) -> Result<u64> {
|
||||||
let href = a.attr("href").ok_or_else(|| Error::invalid_content("href on link", None))?;
|
let href = a.attr("href").ok_or_else(|| Error::invalid_content("href on link", None))?;
|
||||||
let last = href
|
let last = href
|
||||||
.split('/')
|
.split('/')
|
||||||
|
|
|
@ -135,7 +135,7 @@ fn parse_rcg(html: &Html) -> Result<(Race, Clan, Gender)> {
|
||||||
fn parse_guardian(html: &Html) -> Result<Guardian> {
|
fn parse_guardian(html: &Html) -> Result<Guardian> {
|
||||||
let guardian_str = plain_parse(&html, &*PROFILE_GUARDIAN)?;
|
let guardian_str = plain_parse(&html, &*PROFILE_GUARDIAN)?;
|
||||||
guardian_str
|
guardian_str
|
||||||
.split(",")
|
.split(',')
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| Error::invalid_content("first part of guardian", Some(&guardian_str)))
|
.ok_or_else(|| Error::invalid_content("first part of guardian", Some(&guardian_str)))
|
||||||
.and_then(|x| Guardian::from_str(&x)
|
.and_then(|x| Guardian::from_str(&x)
|
||||||
|
@ -206,7 +206,7 @@ fn parse_jobs(html: &Html) -> Result<BTreeMap<Job, JobInfo>> {
|
||||||
Ok(jobs)
|
Ok(jobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_job<'a>(elem: ElementRef<'a>) -> Result<(Job, JobInfo)> {
|
fn parse_job(elem: ElementRef) -> Result<(Job, JobInfo)> {
|
||||||
let job = crate::logic::plain_parse_elem(elem, &*CLASS_NAME)
|
let job = crate::logic::plain_parse_elem(elem, &*CLASS_NAME)
|
||||||
.and_then(|x| Job::parse(&x).ok_or_else(|| Error::invalid_content("valid job", Some(&x))))?;
|
.and_then(|x| Job::parse(&x).ok_or_else(|| Error::invalid_content("valid job", Some(&x))))?;
|
||||||
|
|
||||||
|
|
|
@ -97,11 +97,11 @@ fn parse_pvp_rank(html: &Html, select: &scraper::Selector) -> Result<Option<u64>
|
||||||
let rank_str = plain_parse(html, select)?;
|
let rank_str = plain_parse(html, select)?;
|
||||||
|
|
||||||
let rank = rank_str
|
let rank = rank_str
|
||||||
.split(":")
|
.split(':')
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.ok_or_else(|| Error::invalid_content("colon-separated text", Some(&rank_str)))
|
.ok_or_else(|| Error::invalid_content("colon-separated text", Some(&rank_str)))
|
||||||
.and_then(|x| x
|
.and_then(|x| x
|
||||||
.split(" ")
|
.split(' ')
|
||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| Error::invalid_content("space-separated text", Some(&rank_str))))?;
|
.ok_or_else(|| Error::invalid_content("space-separated text", Some(&rank_str))))?;
|
||||||
|
|
||||||
|
@ -119,16 +119,16 @@ fn parse_formed(html: &Html) -> Result<DateTime<Utc>> {
|
||||||
let script = html
|
let script = html
|
||||||
.select(&*FC_FORMED)
|
.select(&*FC_FORMED)
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::missing_element(&*FC_FORMED))?
|
.ok_or_else(|| Error::missing_element(&*FC_FORMED))?
|
||||||
.inner_html();
|
.inner_html();
|
||||||
|
|
||||||
let timestamp = script
|
let timestamp = script
|
||||||
.split("strftime(")
|
.split("strftime(")
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.ok_or(Error::invalid_content("strftime call", Some(&script)))?
|
.ok_or_else(|| Error::invalid_content("strftime call", Some(&script)))?
|
||||||
.split(",")
|
.split(',')
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::invalid_content("comma-separated strftime call", Some(&script)))?;
|
.ok_or_else(|| Error::invalid_content("comma-separated strftime call", Some(&script)))?;
|
||||||
let timestamp: i64 = timestamp.parse().map_err(Error::InvalidNumber)?;
|
let timestamp: i64 = timestamp.parse().map_err(Error::InvalidNumber)?;
|
||||||
|
|
||||||
let utc = Local.timestamp(timestamp, 0).with_timezone(&Utc);
|
let utc = Local.timestamp(timestamp, 0).with_timezone(&Utc);
|
||||||
|
@ -154,7 +154,6 @@ fn parse_estate(html: &Html) -> Result<Option<Estate>> {
|
||||||
|
|
||||||
fn parse_crest(html: &Html) -> Result<Vec<Url>> {
|
fn parse_crest(html: &Html) -> Result<Vec<Url>> {
|
||||||
html.select(&*FC_CREST)
|
html.select(&*FC_CREST)
|
||||||
.into_iter()
|
|
||||||
.filter_map(|x| x.value().attr("src"))
|
.filter_map(|x| x.value().attr("src"))
|
||||||
.map(|x| Url::parse(x).map_err(Error::InvalidUrl))
|
.map(|x| Url::parse(x).map_err(Error::InvalidUrl))
|
||||||
.collect()
|
.collect()
|
||||||
|
@ -173,7 +172,7 @@ fn parse_grand_company(html: &Html) -> Result<GrandCompany> {
|
||||||
fn parse_reputation(html: &Html) -> Result<BTreeMap<GrandCompany, u8>> {
|
fn parse_reputation(html: &Html) -> Result<BTreeMap<GrandCompany, u8>> {
|
||||||
let mut reps = BTreeMap::new();
|
let mut reps = BTreeMap::new();
|
||||||
|
|
||||||
for elem in html.select(&*FC_REPUTATION).into_iter() {
|
for elem in html.select(&*FC_REPUTATION) {
|
||||||
let name: String = elem
|
let name: String = elem
|
||||||
.select(&*FC_REPUTATION_NAME)
|
.select(&*FC_REPUTATION_NAME)
|
||||||
.next()
|
.next()
|
||||||
|
|
|
@ -63,7 +63,7 @@ fn parse_members(html_str: &str) -> Result<Paginated<LinkshellMember>> {
|
||||||
|
|
||||||
let results: Vec<LinkshellMember> = html
|
let results: Vec<LinkshellMember> = html
|
||||||
.select(&*ITEM_ENTRY)
|
.select(&*ITEM_ENTRY)
|
||||||
.map(|x| parse_single(x))
|
.map(parse_single)
|
||||||
.collect::<Result<_>>()?;
|
.collect::<Result<_>>()?;
|
||||||
|
|
||||||
Ok(Paginated {
|
Ok(Paginated {
|
||||||
|
@ -72,8 +72,7 @@ fn parse_members(html_str: &str) -> Result<Paginated<LinkshellMember>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_single(html: ElementRef) -> Result<LinkshellMember> {
|
||||||
fn parse_single<'a>(html: ElementRef<'a>) -> Result<LinkshellMember> {
|
|
||||||
let character = super::search::character::parse_single(html)?;
|
let character = super::search::character::parse_single(html)?;
|
||||||
|
|
||||||
let role = parse_role(html)?;
|
let role = parse_role(html)?;
|
||||||
|
@ -84,7 +83,7 @@ fn parse_single<'a>(html: ElementRef<'a>) -> Result<LinkshellMember> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_role<'a>(html: ElementRef<'a>) -> Result<Option<Role>> {
|
fn parse_role(html: ElementRef) -> Result<Option<Role>> {
|
||||||
let role = match html.select(&*ITEM_ROLE).next() {
|
let role = match html.select(&*ITEM_ROLE).next() {
|
||||||
Some(r) => r,
|
Some(r) => r,
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
|
@ -104,6 +103,7 @@ mod test {
|
||||||
};
|
};
|
||||||
use super::parse;
|
use super::parse;
|
||||||
|
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use ffxiv_types::World;
|
use ffxiv_types::World;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
|
|
@ -66,7 +66,7 @@ crate fn parse_pagination(html: &Html) -> Result<Pagination> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
crate fn parse_no_results<'a>(html: &Html) -> bool {
|
crate fn parse_no_results(html: &Html) -> bool {
|
||||||
html.select(&*NO_RESULTS)
|
html.select(&*NO_RESULTS)
|
||||||
.next()
|
.next()
|
||||||
.map(|x| x.text().collect::<String>() == "Your search yielded no results.")
|
.map(|x| x.text().collect::<String>() == "Your search yielded no results.")
|
||||||
|
|
|
@ -41,7 +41,7 @@ pub fn parse(html: &str) -> Result<Paginated<CharacterSearchItem>> {
|
||||||
|
|
||||||
let results: Vec<CharacterSearchItem> = html
|
let results: Vec<CharacterSearchItem> = html
|
||||||
.select(&*ITEM_ENTRY)
|
.select(&*ITEM_ENTRY)
|
||||||
.map(|x| parse_single(x))
|
.map(parse_single)
|
||||||
.collect::<Result<_>>()?;
|
.collect::<Result<_>>()?;
|
||||||
|
|
||||||
Ok(Paginated {
|
Ok(Paginated {
|
||||||
|
@ -50,7 +50,7 @@ pub fn parse(html: &str) -> Result<Paginated<CharacterSearchItem>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
crate fn parse_single<'a>(html: ElementRef<'a>) -> Result<CharacterSearchItem> {
|
crate fn parse_single(html: ElementRef) -> Result<CharacterSearchItem> {
|
||||||
let id = parse_id(html)?;
|
let id = parse_id(html)?;
|
||||||
|
|
||||||
let name = plain_parse(html, &*ITEM_NAME)?;
|
let name = plain_parse(html, &*ITEM_NAME)?;
|
||||||
|
@ -72,7 +72,7 @@ crate fn parse_single<'a>(html: ElementRef<'a>) -> Result<CharacterSearchItem> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_id<'a>(html: ElementRef<'a>) -> Result<u64> {
|
fn parse_id(html: ElementRef) -> Result<u64> {
|
||||||
let e = html
|
let e = html
|
||||||
.select(&*ITEM_ID)
|
.select(&*ITEM_ID)
|
||||||
.next()
|
.next()
|
||||||
|
@ -80,13 +80,13 @@ fn parse_id<'a>(html: ElementRef<'a>) -> Result<u64> {
|
||||||
crate::logic::parse_id(e.value())
|
crate::logic::parse_id(e.value())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_world<'a>(html: ElementRef<'a>) -> Result<World> {
|
fn parse_world(html: ElementRef) -> Result<World> {
|
||||||
let world_str = plain_parse(html, &*ITEM_WORLD)?;
|
let world_str = plain_parse(html, &*ITEM_WORLD)?;
|
||||||
World::from_str(&world_str)
|
World::from_str(&world_str)
|
||||||
.map_err(|_| Error::invalid_content("valid world", Some(&world_str)))
|
.map_err(|_| Error::invalid_content("valid world", Some(&world_str)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_free_company_id<'a>(html: ElementRef<'a>) -> Result<Option<u64>> {
|
fn parse_free_company_id(html: ElementRef) -> Result<Option<u64>> {
|
||||||
let elem = match html
|
let elem = match html
|
||||||
.select(&*ITEM_FREE_COMPANY)
|
.select(&*ITEM_FREE_COMPANY)
|
||||||
.next()
|
.next()
|
||||||
|
@ -97,7 +97,7 @@ fn parse_free_company_id<'a>(html: ElementRef<'a>) -> Result<Option<u64>> {
|
||||||
crate::logic::parse_id(elem.value()).map(Some)
|
crate::logic::parse_id(elem.value()).map(Some)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_grand_company<'a>(html: ElementRef<'a>) -> Result<Option<GrandCompanyInfo>> {
|
fn parse_grand_company(html: ElementRef) -> Result<Option<GrandCompanyInfo>> {
|
||||||
let text = html
|
let text = html
|
||||||
.select(&*ITEM_GRAND_COMPANY)
|
.select(&*ITEM_GRAND_COMPANY)
|
||||||
.next()
|
.next()
|
||||||
|
@ -109,7 +109,7 @@ fn parse_grand_company<'a>(html: ElementRef<'a>) -> Result<Option<GrandCompanyIn
|
||||||
crate::logic::parse_grand_company(text).map(Some)
|
crate::logic::parse_grand_company(text).map(Some)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_face<'a>(html: ElementRef<'a>) -> Result<Url> {
|
fn parse_face(html: ElementRef) -> Result<Url> {
|
||||||
let face_elem = html
|
let face_elem = html
|
||||||
.select(&*ITEM_FACE)
|
.select(&*ITEM_FACE)
|
||||||
.next()
|
.next()
|
||||||
|
|
|
@ -47,7 +47,7 @@ pub fn parse(s: &str) -> Result<Paginated<FreeCompanySearchItem>> {
|
||||||
|
|
||||||
let results: Vec<FreeCompanySearchItem> = html
|
let results: Vec<FreeCompanySearchItem> = html
|
||||||
.select(&*ITEM_ENTRY)
|
.select(&*ITEM_ENTRY)
|
||||||
.map(|x| parse_single(x))
|
.map(parse_single)
|
||||||
.collect::<Result<_>>()?;
|
.collect::<Result<_>>()?;
|
||||||
|
|
||||||
Ok(Paginated {
|
Ok(Paginated {
|
||||||
|
@ -56,7 +56,7 @@ pub fn parse(s: &str) -> Result<Paginated<FreeCompanySearchItem>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_single<'a>(html: ElementRef<'a>) -> Result<FreeCompanySearchItem> {
|
fn parse_single(html: ElementRef) -> Result<FreeCompanySearchItem> {
|
||||||
let id = parse_id(html)?;
|
let id = parse_id(html)?;
|
||||||
let grand_company = parse_grand_company(html)?;
|
let grand_company = parse_grand_company(html)?;
|
||||||
let name = plain_parse(html, &*ITEM_NAME)?;
|
let name = plain_parse(html, &*ITEM_NAME)?;
|
||||||
|
@ -82,7 +82,7 @@ fn parse_single<'a>(html: ElementRef<'a>) -> Result<FreeCompanySearchItem> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_id<'a>(html: ElementRef<'a>) -> Result<u64> {
|
fn parse_id(html: ElementRef) -> Result<u64> {
|
||||||
let e = html
|
let e = html
|
||||||
.select(&*ITEM_ID)
|
.select(&*ITEM_ID)
|
||||||
.next()
|
.next()
|
||||||
|
@ -90,31 +90,31 @@ fn parse_id<'a>(html: ElementRef<'a>) -> Result<u64> {
|
||||||
crate::logic::parse_id(e.value())
|
crate::logic::parse_id(e.value())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_grand_company<'a>(html: ElementRef<'a>) -> Result<GrandCompany> {
|
fn parse_grand_company(html: ElementRef) -> Result<GrandCompany> {
|
||||||
let gc_str = plain_parse(html, &*ITEM_GRAND_COMPANY)?;
|
let gc_str = plain_parse(html, &*ITEM_GRAND_COMPANY)?;
|
||||||
GrandCompany::parse(&gc_str)
|
GrandCompany::parse(&gc_str)
|
||||||
.ok_or_else(|| Error::invalid_content("valid grand company", Some(&gc_str)))
|
.ok_or_else(|| Error::invalid_content("valid grand company", Some(&gc_str)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_world<'a>(html: ElementRef<'a>) -> Result<World> {
|
fn parse_world(html: ElementRef) -> Result<World> {
|
||||||
let world_str = plain_parse(html, &*ITEM_WORLD)?;
|
let world_str = plain_parse(html, &*ITEM_WORLD)?;
|
||||||
World::from_str(&world_str)
|
World::from_str(&world_str)
|
||||||
.map_err(|_| Error::invalid_content("valid world", Some(&world_str)))
|
.map_err(|_| Error::invalid_content("valid world", Some(&world_str)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_crest<'a>(html: ElementRef<'a>) -> Result<Vec<Url>> {
|
fn parse_crest(html: ElementRef) -> Result<Vec<Url>> {
|
||||||
html.select(&*ITEM_CREST)
|
html.select(&*ITEM_CREST)
|
||||||
.filter_map(|x| x.value().attr("src"))
|
.filter_map(|x| x.value().attr("src"))
|
||||||
.map(|x| Url::from_str(x).map_err(Error::InvalidUrl))
|
.map(|x| Url::from_str(x).map_err(Error::InvalidUrl))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_active_members<'a>(html: ElementRef<'a>) -> Result<u16> {
|
fn parse_active_members(html: ElementRef) -> Result<u16> {
|
||||||
plain_parse(html, &*ITEM_ACTIVE_MEMBERS)
|
plain_parse(html, &*ITEM_ACTIVE_MEMBERS)
|
||||||
.and_then(|x| x.parse().map_err(Error::InvalidNumber))
|
.and_then(|x| x.parse().map_err(Error::InvalidNumber))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_estate_built<'a>(html: ElementRef<'a>) -> Result<bool> {
|
fn parse_estate_built(html: ElementRef) -> Result<bool> {
|
||||||
let estate_built = plain_parse(html, &*ITEM_ESTATE_BUILT)?;
|
let estate_built = plain_parse(html, &*ITEM_ESTATE_BUILT)?;
|
||||||
let built = match estate_built.as_str() {
|
let built = match estate_built.as_str() {
|
||||||
"Estate Built" => true,
|
"Estate Built" => true,
|
||||||
|
@ -125,20 +125,20 @@ fn parse_estate_built<'a>(html: ElementRef<'a>) -> Result<bool> {
|
||||||
Ok(built)
|
Ok(built)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_formed<'a>(html: ElementRef<'a>) -> Result<DateTime<Utc>> {
|
fn parse_formed(html: ElementRef) -> Result<DateTime<Utc>> {
|
||||||
let script = html
|
let script = html
|
||||||
.select(&*ITEM_FORMED)
|
.select(&*ITEM_FORMED)
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::missing_element(&*ITEM_FORMED))?
|
.ok_or_else(|| Error::missing_element(&*ITEM_FORMED))?
|
||||||
.inner_html();
|
.inner_html();
|
||||||
|
|
||||||
let timestamp = script
|
let timestamp = script
|
||||||
.split("strftime(")
|
.split("strftime(")
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.ok_or(Error::invalid_content("strftime call", Some(&script)))?
|
.ok_or_else(|| Error::invalid_content("strftime call", Some(&script)))?
|
||||||
.split(",")
|
.split(',')
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::invalid_content("comma-separated strftime call", Some(&script)))?;
|
.ok_or_else(|| Error::invalid_content("comma-separated strftime call", Some(&script)))?;
|
||||||
let timestamp: i64 = timestamp.parse().map_err(Error::InvalidNumber)?;
|
let timestamp: i64 = timestamp.parse().map_err(Error::InvalidNumber)?;
|
||||||
|
|
||||||
let utc = Local.timestamp(timestamp, 0).with_timezone(&Utc);
|
let utc = Local.timestamp(timestamp, 0).with_timezone(&Utc);
|
||||||
|
@ -146,7 +146,7 @@ fn parse_formed<'a>(html: ElementRef<'a>) -> Result<DateTime<Utc>> {
|
||||||
Ok(utc)
|
Ok(utc)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_active<'a>(html: ElementRef<'a>) -> Result<Active> {
|
fn parse_active(html: ElementRef) -> Result<Active> {
|
||||||
plain_parse(html, &*ITEM_ACTIVE)
|
plain_parse(html, &*ITEM_ACTIVE)
|
||||||
.and_then(|x| x
|
.and_then(|x| x
|
||||||
.split(": ")
|
.split(": ")
|
||||||
|
@ -157,7 +157,7 @@ fn parse_active<'a>(html: ElementRef<'a>) -> Result<Active> {
|
||||||
.ok_or_else(|| Error::invalid_content("valid activity", Some(&x))))
|
.ok_or_else(|| Error::invalid_content("valid activity", Some(&x))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_recruitment<'a>(html: ElementRef<'a>) -> Result<RecruitmentStatus> {
|
fn parse_recruitment(html: ElementRef) -> Result<RecruitmentStatus> {
|
||||||
plain_parse(html, &*ITEM_RECRUITMENT)
|
plain_parse(html, &*ITEM_RECRUITMENT)
|
||||||
.and_then(|x| x
|
.and_then(|x| x
|
||||||
.split(": ")
|
.split(": ")
|
||||||
|
|
|
@ -36,7 +36,7 @@ pub fn parse(s: &str) -> Result<Paginated<LinkshellSearchItem>> {
|
||||||
|
|
||||||
let results: Vec<LinkshellSearchItem> = html
|
let results: Vec<LinkshellSearchItem> = html
|
||||||
.select(&*ITEM_ENTRY)
|
.select(&*ITEM_ENTRY)
|
||||||
.map(|x| parse_single(x))
|
.map(parse_single)
|
||||||
.collect::<Result<_>>()?;
|
.collect::<Result<_>>()?;
|
||||||
|
|
||||||
Ok(Paginated {
|
Ok(Paginated {
|
||||||
|
@ -45,7 +45,7 @@ pub fn parse(s: &str) -> Result<Paginated<LinkshellSearchItem>> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_single<'a>(html: ElementRef<'a>) -> Result<LinkshellSearchItem> {
|
fn parse_single(html: ElementRef) -> Result<LinkshellSearchItem> {
|
||||||
let id = parse_id(html)?;
|
let id = parse_id(html)?;
|
||||||
let name = plain_parse(html, &*ITEM_NAME)?;
|
let name = plain_parse(html, &*ITEM_NAME)?;
|
||||||
let world = parse_world(html)?;
|
let world = parse_world(html)?;
|
||||||
|
@ -59,7 +59,7 @@ fn parse_single<'a>(html: ElementRef<'a>) -> Result<LinkshellSearchItem> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_id<'a>(html: ElementRef<'a>) -> Result<u64> {
|
fn parse_id(html: ElementRef) -> Result<u64> {
|
||||||
let e = html
|
let e = html
|
||||||
.select(&*ITEM_ID)
|
.select(&*ITEM_ID)
|
||||||
.next()
|
.next()
|
||||||
|
@ -67,13 +67,13 @@ fn parse_id<'a>(html: ElementRef<'a>) -> Result<u64> {
|
||||||
crate::logic::parse_id(e.value())
|
crate::logic::parse_id(e.value())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_world<'a>(html: ElementRef<'a>) -> Result<World> {
|
fn parse_world(html: ElementRef) -> Result<World> {
|
||||||
let world_str = plain_parse(html, &*ITEM_WORLD)?;
|
let world_str = plain_parse(html, &*ITEM_WORLD)?;
|
||||||
World::from_str(&world_str)
|
World::from_str(&world_str)
|
||||||
.map_err(|_| Error::invalid_content("valid world", Some(&world_str)))
|
.map_err(|_| Error::invalid_content("valid world", Some(&world_str)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_active_members<'a>(html: ElementRef<'a>) -> Result<u8> {
|
fn parse_active_members(html: ElementRef) -> Result<u8> {
|
||||||
plain_parse(html, &*ITEM_ACTIVE_MEMBERS)
|
plain_parse(html, &*ITEM_ACTIVE_MEMBERS)
|
||||||
.and_then(|x| x.parse().map_err(Error::InvalidNumber))
|
.and_then(|x| x.parse().map_err(Error::InvalidNumber))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
macro_rules! ffxiv_enum {
|
macro_rules! ffxiv_enum {
|
||||||
($(#[$meta:meta])* $name:ident { $($variant:ident => $str_repr:expr),+$(,)? }) => {
|
($(#[$meta:meta])* $name:ident { $($variant:ident => $str_repr:expr),+$(,)? }) => {
|
||||||
$(#[$meta])*
|
$(#[$meta])*
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(serde_derive::Serialize, serde_derive::Deserialize))]
|
||||||
pub enum $name {
|
pub enum $name {
|
||||||
$($variant,)+
|
$($variant,)+
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use super::GrandCompany;
|
use super::GrandCompany;
|
||||||
|
|
||||||
|
#[cfg(feature = "with_serde")] use serde_derive::{Deserialize, Serialize};
|
||||||
use ffxiv_types::{World, Race, Clan, Guardian};
|
use ffxiv_types::{World, Race, Clan, Guardian};
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct Character {
|
pub struct Character {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
|
|
||||||
|
@ -22,26 +22,28 @@ pub struct Character {
|
||||||
pub city_state: CityState,
|
pub city_state: CityState,
|
||||||
|
|
||||||
pub grand_company: Option<GrandCompanyInfo>,
|
pub grand_company: Option<GrandCompanyInfo>,
|
||||||
#[serde(with = "crate::util::serde::opt_u64_str")]
|
#[cfg_attr(feature = "with_serde", serde(with = "crate::util::serde::opt_u64_str"))]
|
||||||
pub free_company_id: Option<u64>,
|
pub free_company_id: Option<u64>,
|
||||||
|
|
||||||
pub profile_text: String,
|
pub profile_text: String,
|
||||||
|
|
||||||
pub jobs: BTreeMap<Job, JobInfo>,
|
pub jobs: BTreeMap<Job, JobInfo>,
|
||||||
|
|
||||||
#[serde(with = "url_serde")]
|
#[cfg_attr(feature = "with_serde", serde(with = "url_serde"))]
|
||||||
pub face: Url,
|
pub face: Url,
|
||||||
#[serde(with = "url_serde")]
|
#[cfg_attr(feature = "with_serde", serde(with = "url_serde"))]
|
||||||
pub portrait: Url,
|
pub portrait: Url,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct GrandCompanyInfo {
|
pub struct GrandCompanyInfo {
|
||||||
pub name: GrandCompany,
|
pub name: GrandCompany,
|
||||||
pub rank: String,
|
pub rank: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct JobInfo {
|
pub struct JobInfo {
|
||||||
pub level: Option<u8>,
|
pub level: Option<u8>,
|
||||||
pub experience: Option<u64>,
|
pub experience: Option<u64>,
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
use super::GrandCompany;
|
use super::GrandCompany;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
use ffxiv_types::World;
|
use ffxiv_types::World;
|
||||||
|
#[cfg(feature = "with_serde")] use serde_derive::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct FreeCompany {
|
pub struct FreeCompany {
|
||||||
#[serde(with = "crate::util::serde::u64_str")]
|
#[cfg_attr(feature = "with_serde", serde(with = "crate::util::serde::u64_str"))]
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub world: World,
|
pub world: World,
|
||||||
pub slogan: String,
|
pub slogan: String,
|
||||||
#[serde(with = "crate::util::serde::multi_url")]
|
#[cfg_attr(feature = "with_serde", serde(with = "crate::util::serde::multi_url"))]
|
||||||
pub crest: Vec<Url>,
|
pub crest: Vec<Url>,
|
||||||
pub grand_company: GrandCompany,
|
pub grand_company: GrandCompany,
|
||||||
pub active_members: u16,
|
pub active_members: u16,
|
||||||
|
@ -26,13 +26,15 @@ pub struct FreeCompany {
|
||||||
pub reputation: BTreeMap<GrandCompany, u8>,
|
pub reputation: BTreeMap<GrandCompany, u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct PvpRankings {
|
pub struct PvpRankings {
|
||||||
pub weekly: Option<u64>,
|
pub weekly: Option<u64>,
|
||||||
pub monthly: Option<u64>,
|
pub monthly: Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct Estate {
|
pub struct Estate {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub address: String,
|
pub address: String,
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use super::search::{Paginated, character::CharacterSearchItem};
|
use super::search::{Paginated, character::CharacterSearchItem};
|
||||||
|
|
||||||
use ffxiv_types::World;
|
use ffxiv_types::World;
|
||||||
|
#[cfg(feature = "with_serde")] use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct Linkshell {
|
pub struct Linkshell {
|
||||||
#[serde(with = "crate::util::serde::u64_str")]
|
#[cfg_attr(feature = "with_serde", serde(with = "crate::util::serde::u64_str"))]
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub world: World,
|
pub world: World,
|
||||||
|
@ -12,9 +14,10 @@ pub struct Linkshell {
|
||||||
pub members: Paginated<LinkshellMember>,
|
pub members: Paginated<LinkshellMember>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct LinkshellMember {
|
pub struct LinkshellMember {
|
||||||
#[serde(flatten)]
|
#[cfg_attr(feature = "with_serde", serde(flatten))]
|
||||||
pub character: CharacterSearchItem,
|
pub character: CharacterSearchItem,
|
||||||
pub role: Option<Role>,
|
pub role: Option<Role>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
|
#[cfg(feature = "with_serde")] use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub mod character;
|
pub mod character;
|
||||||
pub mod free_company;
|
pub mod free_company;
|
||||||
pub mod linkshell;
|
pub mod linkshell;
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct Pagination {
|
pub struct Pagination {
|
||||||
pub current_page: u64,
|
pub current_page: u64,
|
||||||
pub total_pages: u64,
|
pub total_pages: u64,
|
||||||
pub total_results: u64,
|
pub total_results: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct Paginated<T> {
|
pub struct Paginated<T> {
|
||||||
pub pagination: Pagination,
|
pub pagination: Pagination,
|
||||||
pub results: Vec<T>,
|
pub results: Vec<T>,
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
use crate::models::character::GrandCompanyInfo;
|
use crate::models::character::GrandCompanyInfo;
|
||||||
|
|
||||||
use ffxiv_types::World;
|
use ffxiv_types::World;
|
||||||
|
#[cfg(feature = "with_serde")] use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct CharacterSearchItem {
|
pub struct CharacterSearchItem {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub world: World,
|
pub world: World,
|
||||||
pub grand_company: Option<GrandCompanyInfo>,
|
pub grand_company: Option<GrandCompanyInfo>,
|
||||||
#[serde(with = "crate::util::serde::opt_u64_str")]
|
#[cfg_attr(feature = "with_serde", serde(with = "crate::util::serde::opt_u64_str"))]
|
||||||
pub free_company_id: Option<u64>,
|
pub free_company_id: Option<u64>,
|
||||||
#[serde(with = "url_serde")]
|
#[cfg_attr(feature = "with_serde", serde(with = "url_serde"))]
|
||||||
pub face: Url,
|
pub face: Url,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
use crate::models::GrandCompany;
|
use crate::models::GrandCompany;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
use ffxiv_types::World;
|
use ffxiv_types::World;
|
||||||
|
#[cfg(feature = "with_serde")] use serde_derive::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct FreeCompanySearchItem {
|
pub struct FreeCompanySearchItem {
|
||||||
#[serde(with = "crate::util::serde::u64_str")]
|
#[cfg_attr(feature = "with_serde", serde(with = "crate::util::serde::u64_str"))]
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub world: World,
|
pub world: World,
|
||||||
#[serde(with = "crate::util::serde::multi_url")]
|
#[cfg_attr(feature = "with_serde", serde(with = "crate::util::serde::multi_url"))]
|
||||||
pub crest: Vec<Url>,
|
pub crest: Vec<Url>,
|
||||||
pub grand_company: GrandCompany,
|
pub grand_company: GrandCompany,
|
||||||
pub active_members: u16,
|
pub active_members: u16,
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
use ffxiv_types::World;
|
use ffxiv_types::World;
|
||||||
|
#[cfg(feature = "with_serde")] use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "with_serde", derive(Deserialize, Serialize))]
|
||||||
pub struct LinkshellSearchItem {
|
pub struct LinkshellSearchItem {
|
||||||
#[serde(with = "crate::util::serde::u64_str")]
|
#[cfg_attr(feature = "with_serde", serde(with = "crate::util::serde::u64_str"))]
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub world: World,
|
pub world: World,
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
pub mod serde;
|
#[cfg(feature = "with_serde")] pub mod serde;
|
||||||
|
|
|
@ -2,6 +2,7 @@ use serde::{Deserializer, Deserialize, Serializer, ser::SerializeSeq};
|
||||||
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[allow(clippy::ptr_arg)]
|
||||||
crate fn serialize<S>(urls: &Vec<Url>, serializer: S) -> Result<S::Ok, S::Error>
|
crate fn serialize<S>(urls: &Vec<Url>, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer,
|
where S: Serializer,
|
||||||
{
|
{
|
||||||
|
@ -16,5 +17,5 @@ crate fn deserialize<'de, D>(deserializer: D) -> Result<Vec<Url>, D::Error>
|
||||||
where D: Deserializer<'de>
|
where D: Deserializer<'de>
|
||||||
{
|
{
|
||||||
let urls: Vec<url_serde::De<Url>> = Vec::deserialize(deserializer)?;
|
let urls: Vec<url_serde::De<Url>> = Vec::deserialize(deserializer)?;
|
||||||
Ok(urls.into_iter().map(|u| u.into_inner()).collect())
|
Ok(urls.into_iter().map(url_serde::De::into_inner).collect())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use serde::{Deserializer, Deserialize, Serializer, de::Unexpected};
|
use serde::{Deserializer, Deserialize, Serializer, de::Unexpected};
|
||||||
|
|
||||||
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
crate fn serialize<S>(u: &u64, serializer: S) -> Result<S::Ok, S::Error>
|
crate fn serialize<S>(u: &u64, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
where S: Serializer,
|
where S: Serializer,
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue