ankiconnect 0.2.0

A Rust library for interacting with AnkiConnect.
Documentation
use std::{error::Error, fmt::Debug};

use serde::{de::DeserializeOwned, Deserialize, Serialize};

pub mod requests;

pub mod note;
pub use note::*;

pub trait AnkiRequestable
where
    Self: Serialize + Sized,
{
    const VERSION: u16;
    const ACTION: &'static str;
    type Response: DeserializeOwned;

    fn params(self) -> Option<Self> {
        Some(self)
    }
}

#[derive(Debug, Deserialize, Serialize, Clone, Copy, Hash, PartialEq, Eq)]
pub struct CardId(pub u64);
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)]
pub struct NoteId(pub u64);
#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
pub struct DeckId(pub u64);

#[derive(Debug, Deserialize)]
struct AnkiResponse<T> {
    pub result: Option<T>,
    pub error: Option<String>,
}

impl<T> From<AnkiResponse<T>> for Result<T, String> {
    fn from(value: AnkiResponse<T>) -> Self {
        match value.error {
            Some(error) => Err(error)?,
            None => value.result.ok_or_else(|| "No result".into()),
        }
    }
}

#[derive(Debug, Serialize)]
struct AnkiRequest<Params> {
    action: &'static str,
    version: u16,
    #[serde(skip_serializing_if = "Option::is_none")]
    params: Option<Params>,
}
pub struct AnkiClient {
    client: ureq::Agent,
    url: String,
}

impl Default for AnkiClient {
    fn default() -> Self {
        Self::new()
    }
}

impl AnkiClient {
    pub fn new() -> Self {
        AnkiClient {
            client: ureq::agent(),
            url: "http://localhost:8765".to_string(),
        }
    }

    pub fn request<Request>(&self, request: Request) -> Result<Request::Response, Box<dyn Error>>
    where
        Request: AnkiRequestable + Serialize,
    {
        let request = AnkiRequest {
            action: Request::ACTION,
            version: Request::VERSION,
            params: request.params(),
        };

        let response = self.client.post(&self.url).send_json(&request)?;
        let response: AnkiResponse<Request::Response> = response.into_json()?;

        Result::from(response).map_err(|error| error.into())
    }
}