move client core to seperate crate
This commit is contained in:
parent
de2f0a12b5
commit
a3677bcf3c
16 changed files with 326 additions and 284 deletions
|
@ -2,4 +2,5 @@
|
|||
members = [
|
||||
"tokencracker",
|
||||
"cli",
|
||||
"jm_client_core",
|
||||
]
|
||||
|
|
|
@ -16,6 +16,8 @@ name = "jm"
|
|||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
jm_client_core = { path = "../jm_client_core" }
|
||||
|
||||
anyhow = "1.0.34"
|
||||
clap = "2.33.3"
|
||||
env_logger = "0.8.2"
|
||||
|
|
|
@ -1,58 +1,13 @@
|
|||
use crate::util::IntoTableRow;
|
||||
use anyhow::{anyhow, Result};
|
||||
use serde::Deserialize;
|
||||
use jm_client_core::api::{Category, Meme, User};
|
||||
use term_table::{row::Row, table_cell::TableCell};
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct UpResp {
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct CatsResp {
|
||||
pub categories: Vec<Category>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct UsersResp {
|
||||
pub users: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct MemesResp {
|
||||
pub memes: Vec<Meme>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Category {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl IntoTableRow for Category {
|
||||
fn into_table_row(&self) -> Row<'_> {
|
||||
Row::new(vec![TableCell::new(&self.id), TableCell::new(&self.name)])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Meme {
|
||||
pub id: String,
|
||||
pub link: String,
|
||||
pub path: String,
|
||||
pub category: String,
|
||||
pub user: String,
|
||||
}
|
||||
|
||||
impl Meme {
|
||||
pub fn file_name(&self) -> Result<&str> {
|
||||
self.path
|
||||
.split('/')
|
||||
.last()
|
||||
.ok_or_else(|| anyhow!("failed to get file name. server response invalid"))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoTableRow for Meme {
|
||||
fn into_table_row(&self) -> Row<'_> {
|
||||
Row::new(vec![
|
||||
|
@ -63,24 +18,6 @@ impl IntoTableRow for Meme {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
pub id: Option<String>,
|
||||
pub tokenhash: Option<String>,
|
||||
pub userdir: Option<String>,
|
||||
pub dayuploads: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn get_id(&self) -> Option<&String> {
|
||||
self.id
|
||||
.as_ref()
|
||||
.or(self.tokenhash.as_ref())
|
||||
.or(self.userdir.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoTableRow for User {
|
||||
fn into_table_row(&self) -> Row<'_> {
|
||||
Row::new(vec![
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::util::{self, api, IntoTableRow};
|
||||
use crate::util::{self, IntoTableRow};
|
||||
use jm_client_core::util::api;
|
||||
use reqwest::Client;
|
||||
|
||||
pub async fn run(http: &Client) -> anyhow::Result<()> {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::util::{self, api, IntoTableRow, MemeSorting};
|
||||
use crate::util::IntoTableRow;
|
||||
use jm_client_core::util::{self, api, MemeSorting};
|
||||
|
||||
pub async fn run(
|
||||
http: &Client,
|
||||
|
@ -39,7 +40,7 @@ pub async fn run(
|
|||
s.sort_with(&mut memes);
|
||||
}
|
||||
|
||||
let mut table = util::list_table();
|
||||
let mut table = crate::util::list_table();
|
||||
|
||||
for m in memes {
|
||||
table.add_row(m.into_table_row());
|
||||
|
|
|
@ -2,7 +2,8 @@ use anyhow::Result;
|
|||
use log::info;
|
||||
use reqwest::Client;
|
||||
|
||||
use crate::util::{self, api, IntoTableRow};
|
||||
use crate::util::IntoTableRow;
|
||||
use jm_client_core::util::{self, api};
|
||||
|
||||
pub async fn run(
|
||||
http: &Client,
|
||||
|
@ -46,7 +47,7 @@ pub async fn run(
|
|||
|
||||
matches.sort_by(|a, b| b.1.cmp(&a.1));
|
||||
|
||||
let mut table = util::list_table();
|
||||
let mut table = crate::util::list_table();
|
||||
|
||||
for m in matches {
|
||||
table.add_row(m.0.into_table_row());
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use crate::util::open_link;
|
||||
use anyhow::Result;
|
||||
use jm_client_core::{api::UpResp, util};
|
||||
use log::info;
|
||||
use reqwest::{
|
||||
multipart::{Form, Part},
|
||||
|
@ -7,11 +9,6 @@ use reqwest::{
|
|||
};
|
||||
use tokio::{fs::File, io::reader_stream};
|
||||
|
||||
use crate::{
|
||||
api::UpResp,
|
||||
util::{self, open_link},
|
||||
};
|
||||
|
||||
pub async fn run(
|
||||
http: &Client,
|
||||
token: String,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::util::{self, api, IntoTableRow};
|
||||
use crate::util::IntoTableRow;
|
||||
use anyhow::Result;
|
||||
use jm_client_core::util::api;
|
||||
use reqwest::Client;
|
||||
|
||||
pub async fn run(http: &Client) -> Result<()> {
|
||||
let users = api::users(http).await?;
|
||||
let mut table = util::list_table();
|
||||
let mut table = crate::util::list_table();
|
||||
|
||||
for u in users {
|
||||
table.add_row(u.into_table_row())
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::util::MemeSorting;
|
||||
use anyhow::Result;
|
||||
use jm_client_core::util::MemeSorting;
|
||||
use reqwest::Client;
|
||||
use structopt::StructOpt;
|
||||
|
||||
|
|
200
cli/src/util.rs
200
cli/src/util.rs
|
@ -1,152 +1,6 @@
|
|||
use crate::api::Meme;
|
||||
use anyhow::bail;
|
||||
use reqwest::Client;
|
||||
use std::str::FromStr;
|
||||
use term_table::{Table, TableBuilder, TableStyle};
|
||||
use tokio::process::Command;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! init_once_cell {
|
||||
($cell:ident, $init_fn:expr) => {
|
||||
match $cell.get() {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
let x = $init_fn;
|
||||
$cell.get_or_init(|| x)
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod consts {
|
||||
pub const NO_SUCH_CATEGORY_ERROR: &str = "The given Category does not exist!";
|
||||
pub const NO_SUCH_USER_ERROR: &str = "The given User does not exist!";
|
||||
}
|
||||
|
||||
/// ways to communicyte with the JM API
|
||||
pub mod api {
|
||||
use crate::api::{Category, CatsResp, Meme, MemesResp, User, UsersResp};
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use once_cell::sync::OnceCell;
|
||||
use reqwest::Client;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
// cached api responses
|
||||
static USERS: OnceCell<Vec<User>> = OnceCell::new();
|
||||
static CATS: OnceCell<Vec<Category>> = OnceCell::new();
|
||||
lazy_static! {
|
||||
// is this type long enough yet?
|
||||
static ref MEMES: Mutex<HashMap<(Option<String>, Option<String>), Arc<Vec<Meme>>>> =
|
||||
Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Error Deserializing JSON api response: \n{json}\nError: {error}")]
|
||||
pub struct ApiDeserializeError {
|
||||
pub json: String,
|
||||
#[source]
|
||||
pub error: serde_json::Error,
|
||||
}
|
||||
|
||||
pub async fn cats(http: &Client) -> Result<&Vec<Category>> {
|
||||
Ok(init_once_cell!(CATS, {
|
||||
info!("Requesting categories from server");
|
||||
let res = http
|
||||
.get("https://data.tilera.xyz/api/jensmemes/categories")
|
||||
.send()
|
||||
.await?;
|
||||
let cats = try_deserialize_api_reponse::<CatsResp>(&res.bytes().await?)?;
|
||||
cats.categories.into_iter().collect()
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn memes<'a>(
|
||||
http: &Client,
|
||||
cat_filter: Option<String>,
|
||||
usr_filter: Option<String>,
|
||||
) -> Result<Arc<Vec<Meme>>> {
|
||||
let filters = (cat_filter, usr_filter);
|
||||
if let Some(m) = MEMES.lock().unwrap().get(&filters) {
|
||||
return Ok(m.clone());
|
||||
}
|
||||
|
||||
let mut url = Url::options().parse("https://data.tilera.xyz/api/jensmemes/memes")?;
|
||||
let mut pairs = url.query_pairs_mut();
|
||||
|
||||
if let Some(cat) = filters.0.as_ref() {
|
||||
pairs.append_pair("category", cat);
|
||||
}
|
||||
|
||||
if let Some(usr) = filters.1.as_ref() {
|
||||
pairs.append_pair("user", usr);
|
||||
}
|
||||
|
||||
// drop required in order to move the URL into the request
|
||||
drop(pairs);
|
||||
|
||||
info!("Requesting memes from server");
|
||||
let res = http.get(url).send().await?;
|
||||
let memes = try_deserialize_api_reponse::<MemesResp>(&res.bytes().await?)?;
|
||||
|
||||
Ok(MEMES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(filters)
|
||||
.or_insert(Arc::new(memes.memes))
|
||||
.clone())
|
||||
}
|
||||
|
||||
pub async fn users(http: &Client) -> Result<&Vec<User>> {
|
||||
Ok(init_once_cell!(USERS, {
|
||||
info!("Requesting users from server");
|
||||
let res = http
|
||||
.get("https://data.tilera.xyz/api/jensmemes/users")
|
||||
.send()
|
||||
.await?;
|
||||
let users = try_deserialize_api_reponse::<UsersResp>(&res.bytes().await?)?;
|
||||
users.users
|
||||
}))
|
||||
}
|
||||
|
||||
/// tries to deserialize `json` as `T`, and if it fails converts the error
|
||||
/// to a `ApiDeserializeError`
|
||||
pub fn try_deserialize_api_reponse<'a, T: serde::Deserialize<'a>>(json: &'a [u8]) -> Result<T> {
|
||||
let result = serde_json::from_slice::<T>(&json);
|
||||
result.map_err(|error| match std::str::from_utf8(json) {
|
||||
Err(e) => e.into(),
|
||||
Ok(json) => ApiDeserializeError {
|
||||
json: json.into(),
|
||||
error,
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn api_deserialize_error() {
|
||||
let incorrect_json = r#"
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
"#;
|
||||
|
||||
let result = try_deserialize_api_reponse::<UsersResp>(incorrect_json.as_bytes());
|
||||
println!("{}", result.unwrap_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn open_link(url: &str) -> anyhow::Result<()> {
|
||||
match std::env::var_os("BROWSER") {
|
||||
Some(browser) => {
|
||||
|
@ -168,60 +22,6 @@ pub fn list_table<'a>() -> Table<'a> {
|
|||
.build()
|
||||
}
|
||||
|
||||
pub enum MemeSorting {
|
||||
Id,
|
||||
Link,
|
||||
Category,
|
||||
User,
|
||||
}
|
||||
|
||||
impl MemeSorting {
|
||||
pub fn sort_with(&self, memes: &mut [&Meme]) {
|
||||
macro_rules! sort {
|
||||
($list:ident, $field:ident) => {
|
||||
$list.sort_by(|a, b| {
|
||||
a.$field
|
||||
.to_ascii_lowercase()
|
||||
.cmp(&b.$field.to_ascii_lowercase())
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Id => sort!(memes, id),
|
||||
Self::Link => sort!(memes, link),
|
||||
Self::Category => sort!(memes, category),
|
||||
Self::User => sort!(memes, user),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MemeSorting {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_ref() {
|
||||
"id" => Ok(Self::Id),
|
||||
"link" => Ok(Self::Link),
|
||||
"category" => Ok(Self::Category),
|
||||
"user" => Ok(Self::User),
|
||||
_ => bail!("Invalid Meme sorting! options are id, link, category and user!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_user_exists(http: &Client, user: &str) -> anyhow::Result<()> {
|
||||
if !api::users(http).await?.iter().any(|u| u.name == user) {
|
||||
bail!(consts::NO_SUCH_USER_ERROR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn assert_category_exists(http: &Client, cat: &str) -> anyhow::Result<()> {
|
||||
if !api::cats(http).await?.iter().any(|c| c.id == cat) {
|
||||
bail!(consts::NO_SUCH_CATEGORY_ERROR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub trait IntoTableRow {
|
||||
fn into_table_row(&self) -> term_table::row::Row;
|
||||
}
|
||||
|
|
20
jm_client_core/Cargo.toml
Normal file
20
jm_client_core/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "jm_client_core"
|
||||
version = "0.1.0"
|
||||
authors = ["LordMZTE <lord@mzte.de>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.34"
|
||||
env_logger = "0.8.2"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.11"
|
||||
once_cell = "1.5.2"
|
||||
reqwest = "0.10.9"
|
||||
serde = { version = "1.0.117", features = ["derive"] }
|
||||
serde_json = "1.0.60"
|
||||
thiserror = "1.0.23"
|
||||
tokio = { version = "0.2.23", features = ["macros", "fs", "process"] }
|
||||
url = "2.2.0"
|
64
jm_client_core/src/api.rs
Normal file
64
jm_client_core/src/api.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct UpResp {
|
||||
pub files: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct CatsResp {
|
||||
pub categories: Vec<Category>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct UsersResp {
|
||||
pub users: Vec<User>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct MemesResp {
|
||||
pub memes: Vec<Meme>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Category {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Meme {
|
||||
pub id: String,
|
||||
pub link: String,
|
||||
pub path: String,
|
||||
pub category: String,
|
||||
pub user: String,
|
||||
}
|
||||
|
||||
impl Meme {
|
||||
pub fn file_name(&self) -> Result<&str> {
|
||||
self.path
|
||||
.split('/')
|
||||
.last()
|
||||
.ok_or_else(|| anyhow!("failed to get file name. server response invalid"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct User {
|
||||
pub name: String,
|
||||
pub id: Option<String>,
|
||||
pub tokenhash: Option<String>,
|
||||
pub userdir: Option<String>,
|
||||
pub dayuploads: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn get_id(&self) -> Option<&String> {
|
||||
self.id
|
||||
.as_ref()
|
||||
.or(self.tokenhash.as_ref())
|
||||
.or(self.userdir.as_ref())
|
||||
}
|
||||
}
|
2
jm_client_core/src/lib.rs
Normal file
2
jm_client_core/src/lib.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod api;
|
||||
pub mod util;
|
201
jm_client_core/src/util.rs
Normal file
201
jm_client_core/src/util.rs
Normal file
|
@ -0,0 +1,201 @@
|
|||
use crate::api::Meme;
|
||||
use anyhow::bail;
|
||||
use reqwest::Client;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! init_once_cell {
|
||||
($cell:ident, $init_fn:expr) => {
|
||||
match $cell.get() {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
let x = $init_fn;
|
||||
$cell.get_or_init(|| x)
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod consts {
|
||||
pub const NO_SUCH_CATEGORY_ERROR: &str = "The given Category does not exist!";
|
||||
pub const NO_SUCH_USER_ERROR: &str = "The given User does not exist!";
|
||||
}
|
||||
|
||||
/// ways to communicyte with the JM API
|
||||
pub mod api {
|
||||
use crate::api::{Category, CatsResp, Meme, MemesResp, User, UsersResp};
|
||||
use anyhow::Result;
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use once_cell::sync::OnceCell;
|
||||
use reqwest::Client;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
// cached api responses
|
||||
static USERS: OnceCell<Vec<User>> = OnceCell::new();
|
||||
static CATS: OnceCell<Vec<Category>> = OnceCell::new();
|
||||
lazy_static! {
|
||||
// is this type long enough yet?
|
||||
static ref MEMES: Mutex<HashMap<(Option<String>, Option<String>), Arc<Vec<Meme>>>> =
|
||||
Mutex::new(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Error Deserializing JSON api response: \n{json}\nError: {error}")]
|
||||
pub struct ApiDeserializeError {
|
||||
pub json: String,
|
||||
#[source]
|
||||
pub error: serde_json::Error,
|
||||
}
|
||||
|
||||
pub async fn cats(http: &Client) -> Result<&Vec<Category>> {
|
||||
Ok(init_once_cell!(CATS, {
|
||||
info!("Requesting categories from server");
|
||||
let res = http
|
||||
.get("https://data.tilera.xyz/api/jensmemes/categories")
|
||||
.send()
|
||||
.await?;
|
||||
let cats = try_deserialize_api_reponse::<CatsResp>(&res.bytes().await?)?;
|
||||
cats.categories.into_iter().collect()
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn memes<'a>(
|
||||
http: &Client,
|
||||
cat_filter: Option<String>,
|
||||
usr_filter: Option<String>,
|
||||
) -> Result<Arc<Vec<Meme>>> {
|
||||
let filters = (cat_filter, usr_filter);
|
||||
if let Some(m) = MEMES.lock().unwrap().get(&filters) {
|
||||
return Ok(m.clone());
|
||||
}
|
||||
|
||||
let mut url = Url::options().parse("https://data.tilera.xyz/api/jensmemes/memes")?;
|
||||
let mut pairs = url.query_pairs_mut();
|
||||
|
||||
if let Some(cat) = filters.0.as_ref() {
|
||||
pairs.append_pair("category", cat);
|
||||
}
|
||||
|
||||
if let Some(usr) = filters.1.as_ref() {
|
||||
pairs.append_pair("user", usr);
|
||||
}
|
||||
|
||||
// drop required in order to move the URL into the request
|
||||
drop(pairs);
|
||||
|
||||
info!("Requesting memes from server");
|
||||
let res = http.get(url).send().await?;
|
||||
let memes = try_deserialize_api_reponse::<MemesResp>(&res.bytes().await?)?;
|
||||
|
||||
Ok(MEMES
|
||||
.lock()
|
||||
.unwrap()
|
||||
.entry(filters)
|
||||
.or_insert(Arc::new(memes.memes))
|
||||
.clone())
|
||||
}
|
||||
|
||||
pub async fn users(http: &Client) -> Result<&Vec<User>> {
|
||||
Ok(init_once_cell!(USERS, {
|
||||
info!("Requesting users from server");
|
||||
let res = http
|
||||
.get("https://data.tilera.xyz/api/jensmemes/users")
|
||||
.send()
|
||||
.await?;
|
||||
let users = try_deserialize_api_reponse::<UsersResp>(&res.bytes().await?)?;
|
||||
users.users
|
||||
}))
|
||||
}
|
||||
|
||||
/// tries to deserialize `json` as `T`, and if it fails converts the error
|
||||
/// to a `ApiDeserializeError`
|
||||
pub fn try_deserialize_api_reponse<'a, T: serde::Deserialize<'a>>(json: &'a [u8]) -> Result<T> {
|
||||
let result = serde_json::from_slice::<T>(&json);
|
||||
result.map_err(|error| match std::str::from_utf8(json) {
|
||||
Err(e) => e.into(),
|
||||
Ok(json) => ApiDeserializeError {
|
||||
json: json.into(),
|
||||
error,
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn api_deserialize_error() {
|
||||
let incorrect_json = r#"
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
"#;
|
||||
|
||||
let result = try_deserialize_api_reponse::<UsersResp>(incorrect_json.as_bytes());
|
||||
println!("{}", result.unwrap_err());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum MemeSorting {
|
||||
Id,
|
||||
Link,
|
||||
Category,
|
||||
User,
|
||||
}
|
||||
|
||||
impl MemeSorting {
|
||||
pub fn sort_with(&self, memes: &mut [&Meme]) {
|
||||
macro_rules! sort {
|
||||
($list:ident, $field:ident) => {
|
||||
$list.sort_by(|a, b| {
|
||||
a.$field
|
||||
.to_ascii_lowercase()
|
||||
.cmp(&b.$field.to_ascii_lowercase())
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Id => sort!(memes, id),
|
||||
Self::Link => sort!(memes, link),
|
||||
Self::Category => sort!(memes, category),
|
||||
Self::User => sort!(memes, user),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for MemeSorting {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.to_lowercase().as_ref() {
|
||||
"id" => Ok(Self::Id),
|
||||
"link" => Ok(Self::Link),
|
||||
"category" => Ok(Self::Category),
|
||||
"user" => Ok(Self::User),
|
||||
_ => bail!("Invalid Meme sorting! options are id, link, category and user!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn assert_user_exists(http: &Client, user: &str) -> anyhow::Result<()> {
|
||||
if !api::users(http).await?.iter().any(|u| u.name == user) {
|
||||
bail!(consts::NO_SUCH_USER_ERROR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub async fn assert_category_exists(http: &Client, cat: &str) -> anyhow::Result<()> {
|
||||
if !api::cats(http).await?.iter().any(|c| c.id == cat) {
|
||||
bail!(consts::NO_SUCH_CATEGORY_ERROR);
|
||||
}
|
||||
Ok(())
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
use serde::de::{Error, Unexpected, Visitor};
|
||||
use core::fmt::Formatter;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use serde::{
|
||||
de::{Error, Unexpected, Visitor},
|
||||
Deserialize,
|
||||
Deserializer,
|
||||
};
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
@ -36,7 +39,8 @@ where
|
|||
where
|
||||
E: Error,
|
||||
{
|
||||
v.parse().map_err(|_| E::invalid_type(Unexpected::Str(v), &"a u32"))
|
||||
v.parse()
|
||||
.map_err(|_| E::invalid_type(Unexpected::Str(v), &"a u32"))
|
||||
}
|
||||
|
||||
/// implementing u64 instead of 32 because it is used as fallback
|
||||
|
@ -44,7 +48,8 @@ where
|
|||
where
|
||||
E: Error,
|
||||
{
|
||||
v.try_into().map_err(|_| E::invalid_type(Unexpected::Unsigned(v), &"a u32"))
|
||||
v.try_into()
|
||||
.map_err(|_| E::invalid_type(Unexpected::Unsigned(v), &"a u32"))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use anyhow::Result;
|
||||
use clap::{App, Arg};
|
||||
use reqwest::{Client, Url};
|
||||
use tokencracker::{api::{JensmemesUser, UserResponse}, hex_string_hash};
|
||||
use tokencracker::{
|
||||
api::{JensmemesUser, UserResponse},
|
||||
hex_string_hash,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
|
@ -33,7 +36,12 @@ async fn main() -> Result<()> {
|
|||
|
||||
let (username, userdir) =
|
||||
if let (200..=210, Ok(usr)) = (response.status().as_u16(), response.bytes().await) {
|
||||
let UserResponse { user: JensmemesUser {name, tokenhash, ..}, .. } = serde_json::from_slice::<UserResponse>(&usr)?;
|
||||
let UserResponse {
|
||||
user: JensmemesUser {
|
||||
name, tokenhash, ..
|
||||
},
|
||||
..
|
||||
} = serde_json::from_slice::<UserResponse>(&usr)?;
|
||||
(name, tokenhash)
|
||||
} else {
|
||||
("Not in Database".into(), public.clone())
|
||||
|
@ -43,7 +51,8 @@ async fn main() -> Result<()> {
|
|||
"Username: {}
|
||||
Public Token: {}
|
||||
Private Token: {}
|
||||
User: https://data.tilera.xyz/file/jensmemes/images/{}",
|
||||
User: https://data.tilera.xyz/file/jensmemes/images/{}\
|
||||
",
|
||||
username, public, private, userdir
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in a new issue