anki_bridge 0.10.2

AnkiBridge is a Rust library that provides a bridge between your Rust code and the Anki application, enabling HTTP communication and seamless data transmission.
Documentation
/*
* The MIT License (MIT)
*
* Copyright (c) 2025 DaniƩl Kerkmann <daniel@kerkmann.dev>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

use crate::entities::{DeckId, ModelFieldId, ModelId, ModelTemplateId, Timestamp};
use serde::{Deserialize, Serialize};

/// An Anki model.
///
/// It is a template that defines the structure and behavior of flashcards, including fields for
/// information and how that information is displayed during study sessions.
///
/// This struct can be found in `CreateModelRequest` and `FindModelsByIdRequest`.
/// Reference: [notetypes.proto]
///
/// [notetypes.proto]: https://github.com/ankitects/anki/blob/25.02/proto/anki/notetypes.proto#L46-L130
#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Model {
    /// Model ID.
    pub id: ModelId,
    /// Model name.
    pub name: String,
    pub r#type: u64,
    /// Last modified, in seconds since Epoch (January 1, 1970).
    #[serde(rename = "mod")]
    pub modified_time: Timestamp,
    pub usn: i64,
    #[serde(rename = "sortf")]
    pub sort_field_idx: u32,
    #[serde(rename = "did")]
    pub deck_id: Option<DeckId>,
    pub css: String,
    pub latex_pre: String,
    pub latex_post: String,
    #[serde(rename = "latexsvg")]
    pub latex_svg: bool,
    pub original_stock_kind: u32,
    #[serde(rename = "flds")]
    pub fields: Vec<ModelField>,
    #[serde(rename = "tmpls")]
    pub templates: Vec<ModelTemplate>,
    #[serde(rename = "req")]
    pub requirements: Vec<ModelRequirement>,
}

#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelField {
    pub name: String,
    pub ord: Option<u32>,
    pub sticky: bool,
    pub rtl: bool,
    pub font: String,
    pub size: u64,
    pub description: String,
    pub plain_text: bool,
    pub collapsed: bool,
    pub exclude_from_search: bool,
    pub id: ModelFieldId,
    pub tag: Option<String>,
    pub prevent_deletion: bool,
}

#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelTemplate {
    pub id: ModelTemplateId,
    pub name: String,
    pub ord: Option<u32>,
    #[serde(rename = "qfmt")]
    pub question_format: String,
    #[serde(rename = "afmt")]
    pub answer_format: String,
    #[serde(rename = "bqfmt")]
    pub question_format_browser: String,
    #[serde(rename = "bafmt")]
    pub answer_format_browser: String,
    #[serde(rename = "did")]
    pub target_deck_id: Option<DeckId>,
    pub bfont: String,
    pub bsize: u32,
}

#[derive(Default, Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModelRequirement {
    pub card_ord: u32,
    pub kind: ModelRequirementKind,
    pub fields_ords: Vec<u32>,
}

#[derive(Default, Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ModelRequirementKind {
    #[default]
    None = 0,
    Any = 1,
    All = 2,
}

impl From<Vec<serde_json::Value>> for ModelRequirement {
    fn from(value: Vec<serde_json::Value>) -> Self {
        let len = value.len();
        let card_ord = if len > 0 {
            value[0].as_u64().expect("expected 'card_ord' to be u32") as u32
        } else {
            0
        };
        let kind = if len > 1 {
            match value[1].as_str().expect("expected 'kind' to be str") {
                "none" => ModelRequirementKind::None,
                "any" => ModelRequirementKind::Any,
                "all" => ModelRequirementKind::All,
                unk => panic!("unknown requirement kind {unk}"),
            }
        } else {
            Default::default()
        };
        let fields_ords = if len > 1 {
            value[2]
                .as_array()
                .expect("expected 'fields_ords' to be [u32]")
                .iter()
                .map(|v| v.as_u64().expect("expected 'fields_ords' value to be u32") as u32)
                .collect()
        } else {
            vec![]
        };
        Self {
            card_ord,
            kind,
            fields_ords,
        }
    }
}