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