1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3
4#[derive(Debug, Clone, Copy, PartialEq)]
6enum FrontmatterFormat {
7 Json,
9 Toml,
11 Yaml,
13}
14
15impl From<FrontmatterFormat> for &'static str {
16 fn from(format: FrontmatterFormat) -> Self {
17 match format {
18 FrontmatterFormat::Json => "JSON",
19 FrontmatterFormat::Toml => "TOML",
20 FrontmatterFormat::Yaml => "YAML",
21 }
22 }
23}
24
25#[derive(Debug, thiserror::Error)]
27pub enum Error {
28 #[error("disabled format {0}, enable corresponding cargo feature")]
30 DisabledFormat(&'static str),
31 #[error("absent closing {0} delimiter")]
33 AbsentClosingDelimiter(&'static str),
34
35 #[cfg(feature = "json")]
36 #[error("invalid JSON syntax")]
38 InvalidJson(#[source] serde_json::Error),
39 #[cfg(feature = "toml")]
40 #[error("invalid TOML syntax")]
42 InvalidToml(#[source] toml::de::Error),
43 #[cfg(feature = "yaml")]
44 #[error("invalid YAML syntax")]
46 InvalidYaml(#[source] serde_yaml::Error),
47
48 #[cfg(feature = "json")]
49 #[error("couldn't deserialize JSON")]
51 DeserializeJson(#[source] serde_json::Error),
52 #[cfg(feature = "toml")]
53 #[error("couldn't deserialize TOML")]
55 DeserializeToml(#[source] toml::de::Error),
56 #[cfg(feature = "yaml")]
57 #[error("couldn't deserialize YAML")]
59 DeserializeYaml(#[source] serde_yaml::Error),
60}
61
62#[cfg(any(feature = "json", feature = "toml", feature = "yaml"))]
63pub fn parse<T: serde::de::DeserializeOwned>(content: &str) -> Result<(T, &str), Error> {
92 let (maybe_frontmatter, body) = split(content)?;
93 let SplitFrontmatter(format, matter_str) = maybe_frontmatter.unwrap_or_default();
94 let frontmatter = format.parse(matter_str)?;
95 Ok((frontmatter, body))
96}
97
98#[derive(Debug, Clone, Copy)]
99struct SplitFrontmatter<'a>(FrontmatterFormat, &'a str);
100
101#[cfg(any(feature = "json", feature = "toml", feature = "yaml"))]
102impl Default for SplitFrontmatter<'_> {
103 fn default() -> Self {
104 #[cfg(feature = "json")]
105 {
106 Self(FrontmatterFormat::Json, "{}")
107 }
108 #[cfg(all(not(feature = "json"), feature = "toml"))]
109 {
110 Self(FrontmatterFormat::Toml, "")
111 }
112 #[cfg(all(not(any(feature = "json", feature = "toml")), feature = "yaml"))]
113 {
114 Self(FrontmatterFormat::Yaml, "{}")
115 }
116 }
117}
118
119fn split(content: &str) -> Result<(Option<SplitFrontmatter<'_>>, &str), Error> {
122 let content = content.trim_start();
123 let mut lines = LineSpan::new(content);
124
125 let Some(span) = lines.next() else {
126 return Ok((None, content));
128 };
129
130 let Some(format) = FrontmatterFormat::detect(span.line) else {
131 return Ok((None, content));
133 };
134
135 let matter_start = match format {
136 FrontmatterFormat::Json => span.start, FrontmatterFormat::Toml | FrontmatterFormat::Yaml => span.next_start,
138 };
139
140 let closing_delimiter = format.delimiter().1;
141 for span in lines {
142 if span.line != closing_delimiter {
143 continue;
144 }
145 let (matter, body) = match format {
146 FrontmatterFormat::Json => (
147 &content[matter_start..span.next_start], &content[span.next_start..],
149 ),
150 FrontmatterFormat::Toml | FrontmatterFormat::Yaml => (
151 &content[matter_start..span.start], &content[span.next_start..],
153 ),
154 };
155 return Ok((Some(SplitFrontmatter(format, matter)), body));
156 }
157 Err(Error::AbsentClosingDelimiter(format.into()))
158}
159
160impl FrontmatterFormat {
161 const VARIANTS: [Self; 3] = [Self::Json, Self::Toml, Self::Yaml];
162
163 fn detect(first_line: &str) -> Option<Self> {
165 Self::VARIANTS
166 .into_iter()
167 .find(|&variant| first_line == variant.delimiter().0)
168 }
169
170 #[cfg(any(feature = "json", feature = "toml", feature = "yaml"))]
171 fn parse<T: serde::de::DeserializeOwned>(&self, matter_str: &str) -> Result<T, Error> {
172 match self {
173 #[cfg(feature = "json")]
174 Self::Json => {
175 let json: serde_json::Value =
176 serde_json::from_str(matter_str).map_err(Error::InvalidJson)?;
177 serde_json::from_value(json).map_err(Error::DeserializeJson)
178 }
179 #[cfg(not(feature = "json"))]
180 Self::Json => Err(Error::DisabledFormat(Self::Json.into())),
181
182 #[cfg(feature = "toml")]
183 Self::Toml => {
184 let toml: toml::Value = toml::from_str(matter_str).map_err(Error::InvalidToml)?;
185 toml.try_into().map_err(Error::DeserializeToml)
186 }
187 #[cfg(not(feature = "toml"))]
188 Self::Toml => Err(Error::DisabledFormat(Self::Toml.into())),
189
190 #[cfg(feature = "yaml")]
191 Self::Yaml => {
192 let yaml: serde_yaml::Value =
193 serde_yaml::from_str(matter_str).map_err(Error::InvalidYaml)?;
194 serde_yaml::from_value(yaml).map_err(Error::DeserializeYaml)
195 }
196 #[cfg(not(feature = "yaml"))]
197 Self::Yaml => Err(Error::DisabledFormat(Self::Yaml.into())),
198 }
199 }
200
201 fn delimiter(&self) -> (&'static str, &'static str) {
202 match self {
203 Self::Json => ("{", "}"),
204 Self::Toml => ("+++", "+++"),
205 Self::Yaml => ("---", "---"),
206 }
207 }
208}
209
210struct LineSpan<'a> {
211 pub start: usize,
212 pub next_start: usize,
213 pub line: &'a str,
214}
215
216impl<'a> LineSpan<'a> {
217 fn new(s: &'a str) -> impl Iterator<Item = LineSpan<'a>> + 'a {
218 let bytes = s.as_bytes();
219 let mut pos = 0;
220 std::iter::from_fn(move || {
221 if pos >= bytes.len() {
222 return None;
223 }
224 let start = pos;
225 let mut i = start;
226 while i < bytes.len() && bytes[i] != b'\n' && bytes[i] != b'\r' {
227 i += 1;
228 }
229 let line_end = i;
230 if i < bytes.len() && bytes[i] == b'\r' {
231 i += 1;
232 if i < bytes.len() && bytes[i] == b'\n' {
233 i += 1;
234 }
235 } else if i < bytes.len() && bytes[i] == b'\n' {
236 i += 1;
237 }
238 let line = &s[start..line_end];
239 let next_start = i;
240 pos = i;
241 Some(LineSpan {
242 start,
243 next_start,
244 line,
245 })
246 })
247 }
248}
249
250#[cfg(test)]
251mod test_line_span {
252 use super::*;
253
254 #[test]
255 fn line_span() {
256 let input = "line 1\r\nline 2\nline 3";
257 let mut lines = LineSpan::new(input);
258
259 let line1 = lines.next().unwrap();
260 assert_eq!(line1.line, "line 1");
261 assert_eq!(line1.start, 0);
262 assert_eq!(line1.next_start, 8);
263
264 let line2 = lines.next().unwrap();
265 assert_eq!(line2.line, "line 2");
266 assert_eq!(line2.start, 8);
267 assert_eq!(line2.next_start, 15);
268
269 let line3 = lines.next().unwrap();
270 assert_eq!(line3.line, "line 3");
271 assert_eq!(line3.start, 15);
272 assert_eq!(line3.next_start, 21);
273
274 assert!(lines.next().is_none());
275 }
276}
277
278#[cfg(test)]
279mod test_split {
280 use super::*;
281
282 #[test]
283 fn empty_document() {
284 let input = "";
285 let (frontmatter, body) = split(input).unwrap();
286 assert!(frontmatter.is_none());
287 assert_eq!(body, "");
288 }
289
290 #[test]
291 fn no_frontmatter() {
292 let input = "hello world";
293 let (frontmatter, body) = split(input).unwrap();
294 assert!(frontmatter.is_none());
295 assert_eq!(body, "hello world");
296 }
297
298 #[test]
299 fn unclosed_json() {
300 let input = "{\n\t\"foo\": \"bar\"\n";
301 let result = split(input);
302 assert!(matches!(
303 result.unwrap_err(),
304 Error::AbsentClosingDelimiter("JSON")
305 ));
306 }
307
308 #[test]
309 fn unclosed_toml() {
310 let input = "+++\nfoo = \"bar\"";
311 let result = split(input);
312 assert!(matches!(
313 result.unwrap_err(),
314 Error::AbsentClosingDelimiter("TOML")
315 ));
316 }
317
318 #[test]
319 fn unclosed_yaml() {
320 let input = "---\nfoo: bar";
321 let result = split(input);
322 assert!(matches!(
323 result.unwrap_err(),
324 Error::AbsentClosingDelimiter("YAML")
325 ));
326 }
327
328 #[test]
329 fn json_singleline() {
330 let input = "{\n\t\"foo\": \"bar\"\n}\nhello world";
331 let (frontmatter, body) = split(input).unwrap();
332 assert_eq!(frontmatter.unwrap().1, "{\n\t\"foo\": \"bar\"\n}\n");
333 assert_eq!(frontmatter.unwrap().0, FrontmatterFormat::Json);
334 assert_eq!(body, "hello world");
335 }
336
337 #[test]
338 fn json_multiline() {
339 let input = "{\n\t\"foo\": \"bar\",\n\t\"baz\": 1\n}\nhello world";
340 let (frontmatter, body) = split(input).unwrap();
341 assert_eq!(
342 frontmatter.unwrap().1,
343 "{\n\t\"foo\": \"bar\",\n\t\"baz\": 1\n}\n"
344 );
345 assert_eq!(frontmatter.unwrap().0, FrontmatterFormat::Json);
346 assert_eq!(body, "hello world");
347 }
348
349 #[test]
350 fn toml_singleline() {
351 let input = "+++\nfoo = \"bar\"\n+++\nhello world";
352 let (frontmatter, body) = split(input).unwrap();
353 assert_eq!(frontmatter.unwrap().1, "foo = \"bar\"\n");
354 assert_eq!(frontmatter.unwrap().0, FrontmatterFormat::Toml);
355 assert_eq!(body, "hello world");
356 }
357
358 #[test]
359 fn toml_multiline() {
360 let input = "+++\nfoo = \"bar\"\nbaz = 1\n+++\nhello world";
361 let (frontmatter, body) = split(input).unwrap();
362 assert_eq!(frontmatter.unwrap().1, "foo = \"bar\"\nbaz = 1\n");
363 assert_eq!(frontmatter.unwrap().0, FrontmatterFormat::Toml);
364 assert_eq!(body, "hello world");
365 }
366
367 #[test]
368 fn yaml_singleline() {
369 let input = "---\nfoo: bar\n---\nhello world";
370 let (frontmatter, body) = split(input).unwrap();
371 assert_eq!(frontmatter.unwrap().1, "foo: bar\n");
372 assert_eq!(frontmatter.unwrap().0, FrontmatterFormat::Yaml);
373 assert_eq!(body, "hello world");
374 }
375
376 #[test]
377 fn yaml_multiline() {
378 let input = "---\nfoo: bar\nbaz: 1\n---\nhello world";
379 let (frontmatter, body) = split(input).unwrap();
380 assert_eq!(frontmatter.unwrap().1, "foo: bar\nbaz: 1\n");
381 assert_eq!(frontmatter.unwrap().0, FrontmatterFormat::Yaml);
382 assert_eq!(body, "hello world");
383 }
384}
385
386#[cfg(all(test, any(feature = "json", feature = "toml", feature = "yaml")))]
387mod test_parse {
388 use serde::Deserialize;
389
390 use super::*;
391
392 #[derive(Debug, PartialEq, Deserialize)]
393 struct OptionalFrontmatter {
394 foo: Option<bool>,
395 }
396
397 #[derive(Debug, PartialEq, Deserialize)]
398 struct RequiredFrontmatter {
399 foo: bool,
400 }
401
402 #[derive(Debug, PartialEq, Deserialize)]
403 struct EmptyFrontmatter {}
404
405 const EMPTY_DOCUMENT: &str = "";
406 const DOCUMENT_WITHOUT_FRONTMATTER: &str = "hello world";
407
408 const EMPTY_FRONTMATTER: EmptyFrontmatter = EmptyFrontmatter {};
409 const OPTIONAL_FRONTMATTER_SOME: OptionalFrontmatter = OptionalFrontmatter { foo: Some(true) };
410 const OPTIONAL_FRONTMATTER_NONE: OptionalFrontmatter = OptionalFrontmatter { foo: None };
411 const REQUIRED_FRONTMATTER: RequiredFrontmatter = RequiredFrontmatter { foo: true };
412
413 #[cfg(feature = "json")]
414 mod json {
415 use super::*;
416
417 const VALID_DOCUMENT: &str = "{\n\t\"foo\": true\n}\nhello world";
418 const INVALID_SYNTAX: &str = "{\n1\n}";
419 const INVALID_TYPE: &str = "{\n\t\"foo\": 0\n}";
420
421 #[test]
422 fn empty_frontmatter_in_empty_document() {
423 let (frontmatter, body) = parse::<EmptyFrontmatter>(EMPTY_DOCUMENT).unwrap();
424 assert_eq!(frontmatter, EmptyFrontmatter {});
425 assert_eq!(body, "");
426 }
427
428 #[test]
429 fn optional_frontmatter_in_empty_document() {
430 let (frontmatter, body) = parse::<OptionalFrontmatter>(EMPTY_DOCUMENT).unwrap();
431 assert_eq!(frontmatter.foo, None);
432 assert_eq!(body, "");
433 }
434
435 #[test]
436 fn required_frontmatter_in_empty_document() {
437 let result = parse::<RequiredFrontmatter>(EMPTY_DOCUMENT);
438 assert!(matches!(result.unwrap_err(), Error::DeserializeJson(..)));
439 }
440
441 #[test]
442 fn empty_frontmatter_in_document_without_frontmatter() {
443 let (frontmatter, body) =
444 parse::<EmptyFrontmatter>(DOCUMENT_WITHOUT_FRONTMATTER).unwrap();
445 assert_eq!(frontmatter, EMPTY_FRONTMATTER);
446 assert_eq!(body, DOCUMENT_WITHOUT_FRONTMATTER);
447 }
448
449 #[test]
450 fn optional_frontmatter_in_document_without_frontmatter() {
451 let (frontmatter, body) =
452 parse::<OptionalFrontmatter>(DOCUMENT_WITHOUT_FRONTMATTER).unwrap();
453 assert_eq!(frontmatter, OPTIONAL_FRONTMATTER_NONE);
454 assert_eq!(body, DOCUMENT_WITHOUT_FRONTMATTER);
455 }
456
457 #[test]
458 fn required_frontmatter_in_document_without_frontmatter() {
459 let result = parse::<RequiredFrontmatter>(DOCUMENT_WITHOUT_FRONTMATTER);
460 assert!(matches!(result.unwrap_err(), Error::DeserializeJson(..)));
461 }
462
463 #[test]
464 fn optional_frontmatter_in_valid_document() {
465 let (frontmatter, body) = parse::<OptionalFrontmatter>(VALID_DOCUMENT).unwrap();
466 assert_eq!(frontmatter, OPTIONAL_FRONTMATTER_SOME);
467 assert_eq!(body, "hello world");
468 }
469
470 #[test]
471 fn required_frontmatter_in_valid_document() {
472 let (frontmatter, body) = parse::<RequiredFrontmatter>(VALID_DOCUMENT).unwrap();
473 assert_eq!(frontmatter, REQUIRED_FRONTMATTER);
474 assert_eq!(body, "hello world");
475 }
476
477 #[test]
478 fn optional_frontmatter_invalid_syntax() {
479 let result = parse::<OptionalFrontmatter>(INVALID_SYNTAX);
480 assert!(matches!(result.unwrap_err(), Error::InvalidJson(..)));
481 }
482
483 #[test]
484 fn required_frontmatter_invalid_syntax() {
485 let result = parse::<RequiredFrontmatter>(INVALID_SYNTAX);
486 assert!(matches!(result.unwrap_err(), Error::InvalidJson(..)));
487 }
488
489 #[test]
490 fn optional_frontmatter_invalid_type() {
491 let result = parse::<OptionalFrontmatter>(INVALID_TYPE);
492 assert!(matches!(result.unwrap_err(), Error::DeserializeJson(..)));
493 }
494
495 #[test]
496 fn required_frontmatter_invalid_type() {
497 let result = parse::<RequiredFrontmatter>(INVALID_TYPE);
498 assert!(matches!(result.unwrap_err(), Error::DeserializeJson(..)));
499 }
500 }
501
502 #[cfg(feature = "toml")]
503 mod toml {
504 use super::*;
505
506 const VALID_DOCUMENT: &str = "+++\nfoo = true\n+++\nhello world";
507 const INVALID_SYNTAX: &str = "+++\nfoobar\n+++\n";
508 const INVALID_TYPE: &str = "+++\nfoo = 123\n+++\n";
509
510 #[cfg(not(any(feature = "json", feature = "yaml")))]
511 mod only {
512 use super::*;
513
514 #[test]
515 fn empty_frontmatter_in_empty_document() {
516 let (frontmatter, body) = parse::<EmptyFrontmatter>(EMPTY_DOCUMENT).unwrap();
517 assert_eq!(frontmatter, EmptyFrontmatter {});
518 assert_eq!(body, "");
519 }
520
521 #[test]
522 fn optional_frontmatter_in_empty_document() {
523 let (frontmatter, body) = parse::<OptionalFrontmatter>(EMPTY_DOCUMENT).unwrap();
524 assert_eq!(frontmatter.foo, None);
525 assert_eq!(body, "");
526 }
527
528 #[test]
529 fn required_frontmatter_in_empty_document() {
530 let result = parse::<RequiredFrontmatter>(EMPTY_DOCUMENT);
531 assert!(matches!(result.unwrap_err(), Error::DeserializeToml(..)));
532 }
533
534 #[test]
535 fn empty_frontmatter_in_document_without_frontmatter() {
536 let (frontmatter, body) =
537 parse::<EmptyFrontmatter>(DOCUMENT_WITHOUT_FRONTMATTER).unwrap();
538 assert_eq!(frontmatter, EMPTY_FRONTMATTER);
539 assert_eq!(body, DOCUMENT_WITHOUT_FRONTMATTER);
540 }
541
542 #[test]
543 fn optional_frontmatter_in_document_without_frontmatter() {
544 let (frontmatter, body) =
545 parse::<OptionalFrontmatter>(DOCUMENT_WITHOUT_FRONTMATTER).unwrap();
546 assert_eq!(frontmatter, OPTIONAL_FRONTMATTER_NONE);
547 assert_eq!(body, DOCUMENT_WITHOUT_FRONTMATTER);
548 }
549
550 #[test]
551 fn required_frontmatter_in_document_without_frontmatter() {
552 let result = parse::<RequiredFrontmatter>(DOCUMENT_WITHOUT_FRONTMATTER);
553 assert!(matches!(result.unwrap_err(), Error::DeserializeToml(..)));
554 }
555 }
556
557 #[test]
558 fn optional_frontmatter_in_valid_document() {
559 let (frontmatter, body) = parse::<OptionalFrontmatter>(VALID_DOCUMENT).unwrap();
560 assert_eq!(frontmatter, OPTIONAL_FRONTMATTER_SOME);
561 assert_eq!(body, "hello world");
562 }
563
564 #[test]
565 fn required_frontmatter_in_valid_document() {
566 let (frontmatter, body) = parse::<RequiredFrontmatter>(VALID_DOCUMENT).unwrap();
567 assert_eq!(frontmatter, REQUIRED_FRONTMATTER);
568 assert_eq!(body, "hello world");
569 }
570
571 #[test]
572 fn optional_frontmatter_invalid_syntax() {
573 let result = parse::<OptionalFrontmatter>(INVALID_SYNTAX);
574 assert!(matches!(result.unwrap_err(), Error::InvalidToml(..)));
575 }
576
577 #[test]
578 fn required_frontmatter_invalid_syntax() {
579 let result = parse::<RequiredFrontmatter>(INVALID_SYNTAX);
580 assert!(matches!(result.unwrap_err(), Error::InvalidToml(..)));
581 }
582
583 #[test]
584 fn optional_frontmatter_invalid_type() {
585 let result = parse::<OptionalFrontmatter>(INVALID_TYPE);
586 assert!(matches!(result.unwrap_err(), Error::DeserializeToml(..)));
587 }
588
589 #[test]
590 fn required_frontmatter_invalid_type() {
591 let result = parse::<RequiredFrontmatter>(INVALID_TYPE);
592 assert!(matches!(result.unwrap_err(), Error::DeserializeToml(..)));
593 }
594 }
595
596 #[cfg(feature = "yaml")]
597 mod yaml {
598 use super::*;
599
600 const VALID_DOCUMENT: &str = "---\nfoo: true\n---\nhello world";
601 const INVALID_SYNTAX: &str = "---\n:\n---\n";
602 const INVALID_TYPE: &str = "---\nfoo: 123\n---\n";
603
604 #[cfg(not(any(feature = "json", feature = "toml")))]
605 mod only {
606 use super::*;
607
608 #[test]
609 fn empty_frontmatter_in_empty_document() {
610 let (frontmatter, body) = parse::<EmptyFrontmatter>(EMPTY_DOCUMENT).unwrap();
611 assert_eq!(frontmatter, EmptyFrontmatter {});
612 assert_eq!(body, "");
613 }
614
615 #[test]
616 fn optional_frontmatter_in_empty_document() {
617 let (frontmatter, body) = parse::<OptionalFrontmatter>(EMPTY_DOCUMENT).unwrap();
618 assert_eq!(frontmatter.foo, None);
619 assert_eq!(body, "");
620 }
621
622 #[test]
623 fn required_frontmatter_in_empty_document() {
624 let result = parse::<RequiredFrontmatter>(EMPTY_DOCUMENT);
625 assert!(matches!(result.unwrap_err(), Error::DeserializeYaml(..)));
626 }
627
628 #[test]
629 fn empty_frontmatter_in_document_without_frontmatter() {
630 let (frontmatter, body) =
631 parse::<EmptyFrontmatter>(DOCUMENT_WITHOUT_FRONTMATTER).unwrap();
632 assert_eq!(frontmatter, EMPTY_FRONTMATTER);
633 assert_eq!(body, DOCUMENT_WITHOUT_FRONTMATTER);
634 }
635
636 #[test]
637 fn optional_frontmatter_in_document_without_frontmatter() {
638 let (frontmatter, body) =
639 parse::<OptionalFrontmatter>(DOCUMENT_WITHOUT_FRONTMATTER).unwrap();
640 assert_eq!(frontmatter, OPTIONAL_FRONTMATTER_NONE);
641 assert_eq!(body, DOCUMENT_WITHOUT_FRONTMATTER);
642 }
643
644 #[test]
645 fn required_frontmatter_in_document_without_frontmatter() {
646 let result = parse::<RequiredFrontmatter>(DOCUMENT_WITHOUT_FRONTMATTER);
647 assert!(matches!(result.unwrap_err(), Error::DeserializeYaml(..)));
648 }
649 }
650
651 #[test]
652 fn optional_frontmatter_in_valid_document() {
653 let (frontmatter, body) = parse::<OptionalFrontmatter>(VALID_DOCUMENT).unwrap();
654 assert_eq!(frontmatter, OPTIONAL_FRONTMATTER_SOME);
655 assert_eq!(body, "hello world");
656 }
657
658 #[test]
659 fn required_frontmatter_in_valid_document() {
660 let (frontmatter, body) = parse::<RequiredFrontmatter>(VALID_DOCUMENT).unwrap();
661 assert_eq!(frontmatter, REQUIRED_FRONTMATTER);
662 assert_eq!(body, "hello world");
663 }
664
665 #[test]
666 fn optional_frontmatter_invalid_syntax() {
667 let result = parse::<OptionalFrontmatter>(INVALID_SYNTAX);
668 assert!(matches!(result.unwrap_err(), Error::InvalidYaml(..)));
669 }
670
671 #[test]
672 fn required_frontmatter_invalid_syntax() {
673 let result = parse::<RequiredFrontmatter>(INVALID_SYNTAX);
674 assert!(matches!(result.unwrap_err(), Error::InvalidYaml(..)));
675 }
676
677 #[test]
678 fn optional_frontmatter_invalid_type() {
679 let result = parse::<OptionalFrontmatter>(INVALID_TYPE);
680 assert!(matches!(result.unwrap_err(), Error::DeserializeYaml(..)));
681 }
682
683 #[test]
684 fn required_frontmatter_invalid_type() {
685 let result = parse::<RequiredFrontmatter>(INVALID_TYPE);
686 assert!(matches!(result.unwrap_err(), Error::DeserializeYaml(..)));
687 }
688 }
689}