#![allow(clippy::needless_doctest_main)]
pub mod anki;
#[cfg(feature = "cache")]
pub mod cache;
pub mod decks;
pub mod error;
mod generic;
pub mod model;
pub mod notes;
pub mod result;
mod str_utils;
mod test_utils;
use std::{marker::PhantomData, ops::Deref, sync::Arc};
use derive_where::derive_where;
use error::{AnkiError, CustomSerdeError};
use getset::{Getters, MutGetters};
use num_traits::PrimInt;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use thiserror::Error;
#[cfg(feature = "cache")]
use crate::cache::Cache;
use crate::error::AnkiResult;
pub use reqwest::Client as ReqwestClient;
#[derive(Clone, Debug, Getters, MutGetters)]
pub struct AnkiClient {
backend: Arc<Backend>,
modules: Arc<AnkiModules>,
#[cfg(feature = "cache")]
#[getset(get = "pub", get_mut = "pub")]
cache: Cache,
}
impl AnkiClient {
pub async fn new_auto(port: &str) -> AnkiResult<Self> {
let backend = Arc::new(Backend::new(port).await?);
let modules = Arc::new(AnkiModules::new(backend.clone()));
Ok(Self {
backend: backend.clone(),
modules: modules.clone(),
#[cfg(feature = "cache")]
cache: Cache::init(modules),
})
}
pub fn new_sync(port: &str, version: u8) -> Self {
let backend = Arc::new(Backend::new_sync(port, version));
let modules = Arc::new(AnkiModules::new(backend.clone()));
Self {
backend: backend.clone(),
modules: modules.clone(),
#[cfg(feature = "cache")]
cache: Cache::init(modules),
}
}
pub fn notes(&self) -> &NotesProxy {
&self.modules.notes
}
pub fn models(&self) -> &ModelsProxy {
&self.modules.models
}
pub fn reqwest_client(&self) -> &Client {
&self.backend.client
}
}
#[derive(Clone, Debug, Getters)]
pub struct AnkiModules {
backend: Arc<Backend>,
notes: NotesProxy,
models: ModelsProxy,
#[getset(get = "pub")]
decks: DecksProxy,
}
impl PartialEq for AnkiModules {
fn eq(&self, other: &Self) -> bool {
let Self { backend, .. } = other;
self.backend == *backend
}
}
impl AnkiModules {
fn new(backend: Arc<Backend>) -> Self {
Self {
backend: backend.clone(),
notes: NotesProxy(backend.clone()),
models: ModelsProxy(backend.clone()),
decks: DecksProxy(backend.clone()),
}
}
}
#[derive(Clone, Debug)]
pub struct NotesProxy(Arc<Backend>);
impl Deref for NotesProxy {
type Target = Arc<Backend>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug)]
pub struct ModelsProxy(Arc<Backend>);
impl Deref for ModelsProxy {
type Target = Arc<Backend>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone, Debug)]
pub struct DecksProxy(Arc<Backend>);
impl Deref for DecksProxy {
type Target = Arc<Backend>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Default for AnkiClient {
fn default() -> Self {
let backend = Arc::new(Backend::default());
let modules = Arc::new(AnkiModules::new(backend.clone()));
Self {
backend: backend.clone(),
modules: modules.clone(),
#[cfg(feature = "cache")]
cache: Cache::init(modules.clone()),
}
}
}
impl Deref for AnkiClient {
type Target = Arc<Backend>;
fn deref(&self) -> &Self::Target {
&self.backend
}
}
#[derive(Clone, Debug)]
pub struct Backend {
pub endpoint: String,
pub client: Client,
pub version: u8,
}
impl PartialEq for Backend {
fn eq(&self, other: &Self) -> bool {
let Self {
endpoint, version, ..
} = self;
other.endpoint == *endpoint && other.version == *version
}
}
impl Eq for Backend {}
impl Default for Backend {
fn default() -> Self {
Self {
endpoint: Self::format_url("8765"),
client: Client::new(),
version: 6,
}
}
}
impl Backend {
pub async fn new(port: &str) -> Result<Self, AnkiError> {
let client = Client::new();
let endpoint = Self::format_url(port);
let version = Backend::get_version_internal(&client, &endpoint).await?;
let ac = Self {
endpoint,
client,
version,
};
Ok(ac)
}
pub async fn default_auto() -> Result<Self, AnkiError> {
Self::new("8765").await
}
pub fn new_sync(port: &str, version: u8) -> Self {
Self {
endpoint: Self::format_url(port),
client: Client::new(),
version,
}
}
pub fn format_url(port: &str) -> String {
format!("http://localhost:{port}")
}
pub async fn get_version_internal(c: &Client, url: &str) -> Result<u8, AnkiError> {
let res = match c.get(url).send().await {
Ok(response) => response,
Err(_) => return Err(AnkiError::ConnectionNotFound(url.to_string())),
};
let val: Value = res.json().await.unwrap();
let Some(res) = val.as_object() else {
let cse = CustomSerdeError::expected(None, val, None);
return Err(AnkiError::CustomSerde(cse));
};
let version: String = res.get("apiVersion").unwrap().to_string();
let mut version_str = version
.split_once(".")
.expect("no delimiter `.` found")
.1
.to_string();
version_str.remove(1);
let version = version_str
.parse::<u8>()
.map_err(|_| AnkiError::ParseIntError(version_str.to_string()))?;
Ok(version)
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct Number(isize);
impl Number {
pub fn new(int: impl PrimInt) -> Self {
Self(
int.to_isize()
.unwrap_or_else(|| panic!("num cannot be converted to usize")),
)
}
pub fn from_slice_to_vec(slice: &[impl PrimInt]) -> Vec<Number> {
slice.iter().map(|int| Number::new(*int)).collect()
}
}
impl Deref for Number {
type Target = isize;
fn deref(&self) -> &Self::Target {
&self.0
}
}