use std::time::Duration;
use reqwest::Client;
use serde::{Serialize, de::DeserializeOwned};
use crate::actions::{
CardActions, DeckActions, GuiActions, MediaActions, MiscActions, ModelActions, NoteActions,
StatisticsActions,
};
use crate::error::{Error, Result};
use crate::request::{AnkiRequest, AnkiResponse};
const DEFAULT_URL: &str = "http://127.0.0.1:8765";
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(30);
#[derive(Debug, Clone)]
pub struct AnkiClient {
http_client: Client,
base_url: String,
api_key: Option<String>,
}
impl AnkiClient {
pub fn new() -> Self {
Self::builder().build()
}
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
pub fn decks(&self) -> DeckActions<'_> {
DeckActions { client: self }
}
pub fn misc(&self) -> MiscActions<'_> {
MiscActions { client: self }
}
pub fn notes(&self) -> NoteActions<'_> {
NoteActions { client: self }
}
pub fn cards(&self) -> CardActions<'_> {
CardActions { client: self }
}
pub fn media(&self) -> MediaActions<'_> {
MediaActions { client: self }
}
pub fn models(&self) -> ModelActions<'_> {
ModelActions { client: self }
}
pub fn gui(&self) -> GuiActions<'_> {
GuiActions { client: self }
}
pub fn statistics(&self) -> StatisticsActions<'_> {
StatisticsActions { client: self }
}
pub(crate) async fn invoke_without_params<R>(&self, action: &str) -> Result<R>
where
R: DeserializeOwned,
{
let request = AnkiRequest::<()>::without_params(action, self.api_key.as_deref());
self.send_request(&request).await
}
pub(crate) async fn invoke<P, R>(&self, action: &str, params: P) -> Result<R>
where
P: Serialize,
R: DeserializeOwned,
{
let request = AnkiRequest::new(action, params, self.api_key.as_deref());
self.send_request(&request).await
}
pub(crate) async fn invoke_void<P>(&self, action: &str, params: P) -> Result<()>
where
P: Serialize,
{
let request = AnkiRequest::new(action, params, self.api_key.as_deref());
self.send_void_request(&request).await
}
pub(crate) async fn invoke_void_without_params(&self, action: &str) -> Result<()> {
let request = AnkiRequest::<()>::without_params(action, self.api_key.as_deref());
self.send_void_request(&request).await
}
pub(crate) async fn invoke_nullable_without_params<R>(&self, action: &str) -> Result<Option<R>>
where
R: DeserializeOwned,
{
let request = AnkiRequest::<()>::without_params(action, self.api_key.as_deref());
self.send_nullable_request(&request).await
}
async fn send_request<T, R>(&self, request: &AnkiRequest<'_, T>) -> Result<R>
where
T: Serialize,
R: DeserializeOwned,
{
let response = self
.http_client
.post(&self.base_url)
.json(request)
.send()
.await
.map_err(|e| {
if e.is_connect() {
Error::ConnectionRefused
} else {
Error::Http(e)
}
})?;
let anki_response: AnkiResponse<R> = response.json().await?;
match (anki_response.result, anki_response.error) {
(Some(result), None) => Ok(result),
(None, Some(err)) => {
if err.contains("permission") {
Err(Error::PermissionDenied)
} else {
Err(Error::AnkiConnect(err))
}
}
(None, None) => Err(Error::EmptyResponse),
(Some(_), Some(err)) => Err(Error::AnkiConnect(err)),
}
}
async fn send_void_request<T>(&self, request: &AnkiRequest<'_, T>) -> Result<()>
where
T: Serialize,
{
let response = self
.http_client
.post(&self.base_url)
.json(request)
.send()
.await
.map_err(|e| {
if e.is_connect() {
Error::ConnectionRefused
} else {
Error::Http(e)
}
})?;
let anki_response: AnkiResponse<serde_json::Value> = response.json().await?;
if let Some(err) = anki_response.error {
if err.contains("permission") {
Err(Error::PermissionDenied)
} else {
Err(Error::AnkiConnect(err))
}
} else {
Ok(())
}
}
async fn send_nullable_request<T, R>(&self, request: &AnkiRequest<'_, T>) -> Result<Option<R>>
where
T: Serialize,
R: DeserializeOwned,
{
let response = self
.http_client
.post(&self.base_url)
.json(request)
.send()
.await
.map_err(|e| {
if e.is_connect() {
Error::ConnectionRefused
} else {
Error::Http(e)
}
})?;
let anki_response: AnkiResponse<R> = response.json().await?;
match (anki_response.result, anki_response.error) {
(Some(result), None) => Ok(Some(result)),
(None, Some(err)) => {
if err.contains("permission") {
Err(Error::PermissionDenied)
} else {
Err(Error::AnkiConnect(err))
}
}
(None, None) => Ok(None),
(Some(_), Some(err)) => Err(Error::AnkiConnect(err)),
}
}
}
impl Default for AnkiClient {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ClientBuilder {
base_url: String,
api_key: Option<String>,
timeout: Duration,
}
impl ClientBuilder {
pub fn new() -> Self {
Self {
base_url: DEFAULT_URL.to_string(),
api_key: None,
timeout: DEFAULT_TIMEOUT,
}
}
pub fn url(mut self, url: impl Into<String>) -> Self {
self.base_url = url.into();
self
}
pub fn api_key(mut self, key: impl Into<String>) -> Self {
self.api_key = Some(key.into());
self
}
pub fn timeout(mut self, duration: Duration) -> Self {
self.timeout = duration;
self
}
pub fn build(self) -> AnkiClient {
let http_client = Client::builder()
.timeout(self.timeout)
.build()
.expect("Failed to build HTTP client");
AnkiClient {
http_client,
base_url: self.base_url,
api_key: self.api_key,
}
}
}
impl Default for ClientBuilder {
fn default() -> Self {
Self::new()
}
}