eipw_lint/lints/preamble/
date.rs1use eipw_snippets::Snippet;
8
9use chrono::NaiveDate;
10
11use crate::{
12 lints::{Context, Error, Lint},
13 LevelExt, SnippetExt,
14};
15
16use serde::{Deserialize, Serialize};
17
18use std::fmt::{Debug, Display};
19
20#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
21#[serde(transparent)]
22pub struct Date<S>(pub S);
23
24impl<S> Lint for Date<S>
25where
26 S: Debug + Display + AsRef<str>,
27{
28 fn lint<'a>(&self, slug: &'a str, ctx: &Context<'a, '_>) -> Result<(), Error> {
29 let field = match ctx.preamble().by_name(self.0.as_ref()) {
30 None => return Ok(()),
31 Some(s) => s,
32 };
33
34 let value = field.value().trim();
35
36 let mut error = None;
37
38 let lengths: Vec<_> = value.split('-').map(str::len).collect();
39 if lengths != [4, 2, 2] {
40 error = Some("invalid length".to_string());
41 }
42
43 if let Err(e) = NaiveDate::parse_from_str(value, "%Y-%m-%d") {
44 error = Some(e.to_string());
45 }
46
47 let slice_label = match error {
48 Some(e) => e,
49 None => return Ok(()),
50 };
51
52 let label = format!(
53 "preamble header `{}` is not a date in the `YYYY-MM-DD` format",
54 self.0
55 );
56
57 let name_count = field.name().len();
58 let value_count = field.value().len();
59
60 ctx.report(
61 ctx.annotation_level().title(&label).id(slug).snippet(
62 Snippet::source(field.source())
63 .fold(false)
64 .line_start(field.line_start())
65 .origin_opt(ctx.origin())
66 .annotation(
67 ctx.annotation_level()
68 .span_utf8(field.source(), name_count + 1, value_count)
69 .label(&slice_label),
70 ),
71 ),
72 )?;
73
74 Ok(())
75 }
76}