1use super::Note;
4use serde::de::DeserializeOwned;
5use std::{io::Read, path::Path};
6
7pub trait NoteFromString: Note
9where
10 Self::Properties: DeserializeOwned,
11{
12 fn from_string(raw_text: impl AsRef<str>) -> Result<Self, Self::Error>;
17}
18
19pub trait NoteFromReader: Note
21where
22 Self::Properties: DeserializeOwned,
23 Self::Error: From<std::io::Error>,
24{
25 fn from_reader(read: &mut impl Read) -> Result<Self, Self::Error>;
27}
28
29impl<N> NoteFromReader for N
30where
31 N: NoteFromString,
32 N::Properties: DeserializeOwned,
33 N::Error: From<std::io::Error>,
34{
35 #[cfg_attr(feature = "tracing", tracing::instrument(skip_all))]
36 fn from_reader(read: &mut impl Read) -> Result<Self, Self::Error> {
37 #[cfg(feature = "tracing")]
38 tracing::trace!("Parse obsidian file from reader");
39
40 let mut buf = String::new();
41 read.read_to_string(&mut buf)?;
42
43 Self::from_string(&buf)
44 }
45}
46
47#[cfg(not(target_family = "wasm"))]
49pub trait NoteFromFile: Note
50where
51 Self::Properties: DeserializeOwned,
52 Self::Error: From<std::io::Error>,
53{
54 fn from_file(path: impl AsRef<Path>) -> Result<Self, Self::Error>;
59}
60
61#[cfg(test)]
62pub(crate) mod tests {
63 use super::*;
64 use crate::{
65 note::{DefaultProperties, parser},
66 test_utils::is_error,
67 };
68 use std::{
69 borrow::Cow,
70 io::{Cursor, Write},
71 path::PathBuf,
72 };
73 use tempfile::NamedTempFile;
74
75 const TEST_DATA: &str = "---\n\
76topic: life\n\
77created: 2025-03-16\n\
78---\n\
79Test data\n\
80---\n\
81Two test data";
82
83 const BROKEN_DATA: &str = "---\n\
84 asdfv:--fs\n\
85 sfsf\n\
86 ---\n\
87 TestData";
88
89 const UNICODE_DATA: &str = "---\ndata: 💩\n---\nSuper data 💩💩💩";
90
91 const SPACE_DATA: &str = " ---\ntest: test-data\n---\n";
92
93 fn test_data<T>(note: T, path: Option<PathBuf>) -> Result<(), T::Error>
94 where
95 T: Note<Properties = DefaultProperties>,
96 T::Error: From<std::io::Error>,
97 {
98 let path = path.map(|p| Cow::Owned(p));
99 let properties = note.properties()?.unwrap();
100
101 assert_eq!(properties["topic"], "life");
102 assert_eq!(properties["created"], "2025-03-16");
103 assert_eq!(note.content()?, "Test data\n---\nTwo test data");
104 assert_eq!(note.path(), path);
105
106 Ok(())
107 }
108
109 fn without_properties<T>(file: T, text: &str) -> Result<(), T::Error>
110 where
111 T: Note<Properties = DefaultProperties>,
112 T::Error: From<std::io::Error>,
113 {
114 assert_eq!(file.properties().unwrap(), None);
115 assert_eq!(file.content().unwrap(), text);
116
117 Ok(())
118 }
119
120 fn invalid_yaml<T>(result: Result<T, T::Error>) -> Result<(), T::Error>
121 where
122 T: Note<Properties = DefaultProperties>,
123 T::Error: From<std::io::Error>,
124 {
125 let error = result.err().unwrap();
126
127 assert!(is_error::<serde_yml::Error>(error));
128 Ok(())
129 }
130
131 fn invalid_format<T>(result: Result<T, T::Error>) -> Result<(), T::Error>
132 where
133 T: Note<Properties = DefaultProperties>,
134 T::Error: From<std::io::Error>,
135 {
136 let error = result.err().unwrap();
137
138 assert!(is_error::<parser::Error>(error));
139 Ok(())
140 }
141
142 fn with_unicode<T>(file: T) -> Result<(), T::Error>
143 where
144 T: Note<Properties = DefaultProperties>,
145 {
146 let properties = file.properties()?.unwrap();
147
148 assert_eq!(properties["data"], "💩");
149 assert_eq!(file.content().unwrap(), "Super data 💩💩💩");
150
151 Ok(())
152 }
153
154 fn space_with_properties<T>(file: T, content: &str) -> Result<(), T::Error>
155 where
156 T: Note<Properties = DefaultProperties>,
157 {
158 let properties = file.properties()?;
159
160 assert_eq!(file.content().unwrap(), content);
161 assert_eq!(properties, None);
162
163 Ok(())
164 }
165
166 pub(crate) fn from_reader<T>() -> Result<(), T::Error>
167 where
168 T: NoteFromReader<Properties = DefaultProperties>,
169 T::Error: From<std::io::Error>,
170 {
171 let mut reader = Cursor::new(TEST_DATA);
172 let file = T::from_reader(&mut reader)?;
173
174 test_data(file, None)?;
175 Ok(())
176 }
177
178 pub(crate) fn from_reader_without_properties<T>() -> Result<(), T::Error>
179 where
180 T: NoteFromReader<Properties = DefaultProperties>,
181 T::Error: From<std::io::Error>,
182 {
183 let test_data = "TEST_DATA";
184 let file = T::from_reader(&mut Cursor::new(test_data))?;
185
186 without_properties(file, test_data)?;
187 Ok(())
188 }
189
190 pub(crate) fn from_reader_invalid_yaml<T>() -> Result<(), T::Error>
191 where
192 T: NoteFromReader<Properties = DefaultProperties>,
193 T::Error: From<std::io::Error>,
194 {
195 let result = T::from_reader(&mut Cursor::new(BROKEN_DATA));
196
197 invalid_yaml(result)?;
198 Ok(())
199 }
200
201 pub(crate) fn from_reader_invalid_format<T>() -> Result<(), T::Error>
202 where
203 T: NoteFromReader<Properties = DefaultProperties>,
204 T::Error: From<std::io::Error>,
205 {
206 let broken_data = "---\n";
207 let result = T::from_reader(&mut Cursor::new(broken_data));
208
209 invalid_format(result)?;
210 Ok(())
211 }
212
213 pub(crate) fn from_reader_with_unicode<T>() -> Result<(), T::Error>
214 where
215 T: NoteFromReader<Properties = DefaultProperties>,
216 T::Error: From<std::io::Error>,
217 {
218 let file = T::from_reader(&mut Cursor::new(UNICODE_DATA))?;
219
220 with_unicode(file)?;
221 Ok(())
222 }
223
224 pub(crate) fn from_reader_space_with_properties<T>() -> Result<(), T::Error>
225 where
226 T: NoteFromReader<Properties = DefaultProperties>,
227 T::Error: From<std::io::Error>,
228 {
229 let file = T::from_reader(&mut Cursor::new(SPACE_DATA))?;
230
231 space_with_properties(file, SPACE_DATA)?;
232 Ok(())
233 }
234
235 pub(crate) fn from_string<T>() -> Result<(), T::Error>
236 where
237 T: NoteFromString<Properties = DefaultProperties>,
238 T::Error: From<std::io::Error>,
239 {
240 let file = T::from_string(TEST_DATA)?;
241
242 test_data(file, None)?;
243 Ok(())
244 }
245
246 pub(crate) fn from_string_without_properties<T>() -> Result<(), T::Error>
247 where
248 T: NoteFromString<Properties = DefaultProperties>,
249 T::Error: From<std::io::Error>,
250 {
251 let test_data = "TEST_DATA";
252 let file = T::from_string(test_data)?;
253
254 without_properties(file, test_data)?;
255 Ok(())
256 }
257
258 pub(crate) fn from_string_with_invalid_yaml<T>() -> Result<(), T::Error>
259 where
260 T: NoteFromString<Properties = DefaultProperties>,
261 T::Error: From<std::io::Error> + From<serde_yml::Error> + 'static,
262 {
263 let result = T::from_string(BROKEN_DATA);
264
265 invalid_yaml(result)?;
266 Ok(())
267 }
268
269 pub(crate) fn from_string_invalid_format<T>() -> Result<(), T::Error>
270 where
271 T: NoteFromString<Properties = DefaultProperties>,
272 T::Error: From<std::io::Error> + From<parser::Error>,
273 {
274 let broken_data = "---\n";
275
276 let result = T::from_string(broken_data);
277 invalid_format(result)?;
278
279 Ok(())
280 }
281
282 pub(crate) fn from_string_with_unicode<T>() -> Result<(), T::Error>
283 where
284 T: NoteFromString<Properties = DefaultProperties>,
285 {
286 let file = T::from_string(UNICODE_DATA)?;
287
288 with_unicode(file)?;
289 Ok(())
290 }
291
292 pub(crate) fn from_string_space_with_properties<T>() -> Result<(), T::Error>
293 where
294 T: NoteFromString<Properties = DefaultProperties>,
295 {
296 let file = T::from_string(SPACE_DATA)?;
297
298 space_with_properties(file, SPACE_DATA)?;
299 Ok(())
300 }
301
302 pub(crate) fn from_file<T>() -> Result<(), T::Error>
303 where
304 T: NoteFromFile<Properties = DefaultProperties>,
305 T::Error: From<std::io::Error>,
306 {
307 let mut temp_file = NamedTempFile::new().unwrap();
308 temp_file.write_all(TEST_DATA.as_bytes()).unwrap();
309
310 let file = T::from_file(temp_file.path()).unwrap();
311
312 test_data(file, Some(temp_file.path().to_path_buf()))?;
313 Ok(())
314 }
315
316 pub(crate) fn from_file_note_name<T>() -> Result<(), T::Error>
317 where
318 T: NoteFromFile<Properties = DefaultProperties>,
319 T::Error: From<std::io::Error>,
320 {
321 let mut temp_file = NamedTempFile::new().unwrap();
322 temp_file.write_all(b"TEST_DATA").unwrap();
323
324 let name_temp_file = temp_file
325 .path()
326 .file_stem()
327 .unwrap()
328 .to_string_lossy()
329 .to_string();
330
331 let file = T::from_file(temp_file.path())?;
332
333 assert_eq!(file.note_name(), Some(name_temp_file));
334 Ok(())
335 }
336
337 pub(crate) fn from_file_without_properties<T>() -> Result<(), T::Error>
338 where
339 T: NoteFromFile<Properties = DefaultProperties>,
340 T::Error: From<std::io::Error>,
341 {
342 let test_data = "TEST_DATA";
343 let mut test_file = NamedTempFile::new().unwrap();
344 test_file.write_all(test_data.as_bytes()).unwrap();
345
346 let file = T::from_file(test_file.path())?;
347
348 without_properties(file, test_data)?;
349 Ok(())
350 }
351
352 pub(crate) fn from_file_with_invalid_yaml<T>() -> Result<(), T::Error>
353 where
354 T: NoteFromFile<Properties = DefaultProperties>,
355 T::Error: From<std::io::Error> + From<serde_yml::Error>,
356 {
357 let mut test_file = NamedTempFile::new().unwrap();
358 test_file.write_all(BROKEN_DATA.as_bytes()).unwrap();
359
360 let result = T::from_file(test_file.path());
361
362 invalid_yaml(result)?;
363 Ok(())
364 }
365
366 pub(crate) fn from_file_invalid_format<T>() -> Result<(), T::Error>
367 where
368 T: NoteFromFile<Properties = DefaultProperties>,
369 T::Error: From<std::io::Error> + From<parser::Error>,
370 {
371 let broken_data = "---\n";
372 let mut test_file = NamedTempFile::new().unwrap();
373 test_file.write_all(broken_data.as_bytes()).unwrap();
374
375 let result = T::from_file(test_file.path());
376
377 invalid_format(result)?;
378 Ok(())
379 }
380
381 pub(crate) fn from_file_with_unicode<T>() -> Result<(), T::Error>
382 where
383 T: NoteFromFile<Properties = DefaultProperties>,
384 T::Error: From<std::io::Error>,
385 {
386 let mut test_file = NamedTempFile::new().unwrap();
387 test_file.write_all(UNICODE_DATA.as_bytes()).unwrap();
388
389 let file = T::from_file(test_file.path())?;
390
391 with_unicode(file)?;
392 Ok(())
393 }
394
395 pub(crate) fn from_file_space_with_properties<T>() -> Result<(), T::Error>
396 where
397 T: NoteFromFile<Properties = DefaultProperties>,
398 T::Error: From<std::io::Error>,
399 {
400 let data = " ---\ntest: test-data\n---\n";
401 let mut test_file = NamedTempFile::new().unwrap();
402 test_file.write_all(data.as_bytes()).unwrap();
403
404 let file = T::from_file(test_file.path())?;
405
406 space_with_properties(file, data)?;
407 Ok(())
408 }
409
410 macro_rules! impl_all_tests_from_reader {
411 ($impl_note:path) => {
412 #[allow(unused_imports)]
413 use $crate::note::note_read::tests::*;
414
415 impl_test_for_note!(impl_from_reader, from_reader, $impl_note);
416
417 impl_test_for_note!(
418 impl_from_reader_without_properties,
419 from_reader_without_properties,
420 $impl_note
421 );
422 impl_test_for_note!(
423 impl_from_reader_with_invalid_yaml,
424 from_reader_invalid_yaml,
425 $impl_note
426 );
427 impl_test_for_note!(
428 impl_from_reader_invalid_format,
429 from_reader_invalid_format,
430 $impl_note
431 );
432 impl_test_for_note!(
433 impl_from_reader_with_unicode,
434 from_reader_with_unicode,
435 $impl_note
436 );
437 impl_test_for_note!(
438 impl_from_reader_space_with_properties,
439 from_reader_space_with_properties,
440 $impl_note
441 );
442 };
443 }
444
445 macro_rules! impl_all_tests_from_string {
446 ($impl_note:path) => {
447 #[allow(unused_imports)]
448 use $crate::note::note_read::tests::*;
449
450 impl_test_for_note!(impl_from_string, from_string, $impl_note);
451
452 impl_test_for_note!(
453 impl_from_string_without_properties,
454 from_string_without_properties,
455 $impl_note
456 );
457 impl_test_for_note!(
458 impl_from_string_with_invalid_yaml,
459 from_string_with_invalid_yaml,
460 $impl_note
461 );
462 impl_test_for_note!(
463 impl_from_string_invalid_format,
464 from_string_invalid_format,
465 $impl_note
466 );
467 impl_test_for_note!(
468 impl_from_string_with_unicode,
469 from_string_with_unicode,
470 $impl_note
471 );
472 impl_test_for_note!(
473 impl_from_string_space_with_properties,
474 from_string_space_with_properties,
475 $impl_note
476 );
477 };
478 }
479
480 macro_rules! impl_all_tests_from_file {
481 ($impl_note:path) => {
482 #[allow(unused_imports)]
483 use $crate::note::impl_tests::*;
484
485 impl_test_for_note!(impl_from_file, from_file, $impl_note);
486 impl_test_for_note!(impl_from_file_note_name, from_file_note_name, $impl_note);
487
488 impl_test_for_note!(
489 impl_from_file_without_properties,
490 from_file_without_properties,
491 $impl_note
492 );
493 impl_test_for_note!(
494 impl_from_file_with_invalid_yaml,
495 from_file_with_invalid_yaml,
496 $impl_note
497 );
498 impl_test_for_note!(
499 impl_from_file_invalid_format,
500 from_file_invalid_format,
501 $impl_note
502 );
503 impl_test_for_note!(
504 impl_from_file_with_unicode,
505 from_file_with_unicode,
506 $impl_note
507 );
508 impl_test_for_note!(
509 impl_from_file_space_with_properties,
510 from_file_space_with_properties,
511 $impl_note
512 );
513 };
514 }
515
516 pub(crate) use impl_all_tests_from_file;
517 pub(crate) use impl_all_tests_from_reader;
518 pub(crate) use impl_all_tests_from_string;
519}