anki_bridge/note_actions/
add_note.rs1use serde::Serialize;
25use std::collections::HashMap;
26
27use crate::AnkiRequest;
28use crate::entities::{NoteId, NoteMedia};
29
30#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize)]
32pub struct AddNoteRequest {
33 pub note: AddNoteEntry,
35}
36
37#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize)]
39#[serde(rename_all = "camelCase")]
40pub struct AddNoteEntry {
41 pub deck_name: String,
43 pub model_name: String,
45 #[serde(serialize_with = "crate::serialize::hashmap")]
47 pub fields: HashMap<String, String>,
48 pub options: AddNoteOptions,
49 pub tags: Vec<String>,
51 pub audio: Vec<NoteMedia>,
53 pub video: Vec<NoteMedia>,
55 pub picture: Vec<NoteMedia>,
57}
58
59#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize)]
61#[serde(rename_all = "camelCase")]
62pub struct AddNoteOptions {
63 pub allow_duplicate: bool,
65 pub duplicate_scope: AddNoteDuplicateScope,
68 pub duplicate_scope_options: AddNoteDuplicateScopeOptions,
70}
71
72#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Serialize)]
74#[serde(rename_all = "snake_case")]
75pub enum AddNoteDuplicateScope {
76 #[default]
77 Deck,
78 Collection,
79}
80
81#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize)]
83#[serde(rename_all = "camelCase")]
84pub struct AddNoteDuplicateScopeOptions {
85 pub deck_name: Option<String>,
86 pub check_children: bool,
87 pub check_all_models: bool,
88}
89
90impl AnkiRequest for AddNoteRequest {
91 type Response = NoteId;
92
93 const ACTION: &'static str = "addNote";
94 const VERSION: u8 = 6;
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_serialize() {
103 let request = AddNoteRequest {
104 note: AddNoteEntry {
105 deck_name: "Default".to_string(),
106 model_name: "Basic".to_string(),
107 fields: HashMap::from([
108 ("Front".to_string(), "What is the capital of France?".to_string()),
109 ("Back".to_string(), "Paris".to_string()),
110 ]),
111 options: AddNoteOptions {
112 allow_duplicate: false,
113 duplicate_scope: AddNoteDuplicateScope::Deck,
114 duplicate_scope_options: AddNoteDuplicateScopeOptions {
115 deck_name: Some("Default".to_string()),
116 check_children: false,
117 check_all_models: false,
118 },
119 },
120 tags: vec![
121 "yomichan".to_string()
122 ],
123 audio: vec![
124 NoteMedia {
125 filename: "yomichan_ねこ_猫.mp3".to_string(),
126 data: None,
127 path: None,
128 url: Some("https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=猫&kana=ねこ".to_string()),
129 skip_hash: Some("7e2c2f954ef6051373ba916f000168dc".to_string()),
130 fields: vec![
131 "Front".to_string()
132 ],
133 }
134 ],
135 video: vec![
136 NoteMedia {
137 filename: "countdown.mp4".to_string(),
138 data: None,
139 path: None,
140 url: Some("https://cdn.videvo.net/videvo_files/video/free/2015-06/small_watermarked/Contador_Glam_preview.mp4".to_string()),
141 skip_hash: Some("4117e8aab0d37534d9c8eac362388bbe".to_string()),
142 fields: vec![
143 "Back".to_string()
144 ],
145 }
146 ],
147 picture: vec![
148 NoteMedia {
149 filename: "black_cat.jpg".to_string(),
150 data: None,
151 path: None,
152 url: Some("https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/A_black_cat_named_Tilly.jpg/220px-A_black_cat_named_Tilly.jpg".to_string()),
153 skip_hash: Some("8d6e4646dfae812bf39651b59d7429ce".to_string()),
154 fields: vec![
155 "Back".to_string()
156 ],
157 }
158 ],
159 }
160 };
161
162 let json = serde_json::to_string_pretty(&request).unwrap();
163 assert_eq!(
164 json,
165 r#"{
166 "note": {
167 "deckName": "Default",
168 "modelName": "Basic",
169 "fields": {
170 "Back": "Paris",
171 "Front": "What is the capital of France?"
172 },
173 "options": {
174 "allowDuplicate": false,
175 "duplicateScope": "deck",
176 "duplicateScopeOptions": {
177 "deckName": "Default",
178 "checkChildren": false,
179 "checkAllModels": false
180 }
181 },
182 "tags": [
183 "yomichan"
184 ],
185 "audio": [
186 {
187 "filename": "yomichan_ねこ_猫.mp3",
188 "fields": [
189 "Front"
190 ],
191 "url": "https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=猫&kana=ねこ",
192 "skipHash": "7e2c2f954ef6051373ba916f000168dc"
193 }
194 ],
195 "video": [
196 {
197 "filename": "countdown.mp4",
198 "fields": [
199 "Back"
200 ],
201 "url": "https://cdn.videvo.net/videvo_files/video/free/2015-06/small_watermarked/Contador_Glam_preview.mp4",
202 "skipHash": "4117e8aab0d37534d9c8eac362388bbe"
203 }
204 ],
205 "picture": [
206 {
207 "filename": "black_cat.jpg",
208 "fields": [
209 "Back"
210 ],
211 "url": "https://upload.wikimedia.org/wikipedia/commons/thumb/c/c7/A_black_cat_named_Tilly.jpg/220px-A_black_cat_named_Tilly.jpg",
212 "skipHash": "8d6e4646dfae812bf39651b59d7429ce"
213 }
214 ]
215 }
216}"#
217 );
218 }
219
220 #[test]
221 fn test_deserialize() {
222 let json = "1496198395707";
223 let response: <AddNoteRequest as AnkiRequest>::Response =
224 serde_json::from_str(json).unwrap();
225
226 assert_eq!(response, 1496198395707);
227 }
228}