1#![doc = include_str!("../README.md")]
2#![allow(unused_assignments)] extern crate alloc;
5
6use std::time::Instant;
7
8use anyhow::Result;
9use bpaf::Bpaf;
10
11use lintel_cli_common::CliCacheOptions;
12
13pub mod catalog;
18pub mod diagnostics;
19pub mod discover;
20pub mod parsers;
21pub mod registry;
22pub mod reporter;
23pub mod validate;
24
25pub use reporter::Reporter;
26
27#[derive(Debug, Clone, Bpaf)]
32pub struct ValidateArgs {
33 #[bpaf(long("exclude"), argument("PATTERN"))]
34 pub exclude: Vec<String>,
35
36 #[bpaf(external(lintel_cli_common::cli_cache_options))]
37 pub cache: CliCacheOptions,
38
39 #[bpaf(positional("PATH"))]
40 pub globs: Vec<String>,
41}
42
43impl From<&ValidateArgs> for validate::ValidateArgs {
44 fn from(args: &ValidateArgs) -> Self {
45 let config_dir = args
48 .globs
49 .iter()
50 .find(|g| std::path::Path::new(g).is_dir())
51 .map(std::path::PathBuf::from);
52
53 validate::ValidateArgs {
54 globs: args.globs.clone(),
55 exclude: args.exclude.clone(),
56 cache_dir: args.cache.cache_dir.clone(),
57 force_schema_fetch: args.cache.force_schema_fetch || args.cache.force,
58 force_validation: args.cache.force_validation || args.cache.force,
59 no_catalog: args.cache.no_catalog,
60 config_dir,
61 schema_cache_ttl: args.cache.schema_cache_ttl,
62 }
63 }
64}
65
66pub fn format_checked_verbose(file: &validate::CheckedFile) -> String {
72 use lintel_schema_cache::CacheStatus;
73 use lintel_validation_cache::ValidationCacheStatus;
74
75 let schema_tag = match file.cache_status {
76 Some(CacheStatus::Hit) => " [cached]",
77 Some(CacheStatus::Miss | CacheStatus::Disabled) => " [fetched]",
78 None => "",
79 };
80 let validation_tag = match file.validation_cache_status {
81 Some(ValidationCacheStatus::Hit) => " [validated:cached]",
82 Some(ValidationCacheStatus::Miss) => " [validated]",
83 None => "",
84 };
85 format!(
86 " {} ({}){schema_tag}{validation_tag}",
87 file.path, file.schema
88 )
89}
90
91pub fn merge_config(args: &mut ValidateArgs) {
97 let search_dir = args
98 .globs
99 .iter()
100 .find(|g| std::path::Path::new(g).is_dir())
101 .map(std::path::PathBuf::from);
102
103 let cfg_result = match &search_dir {
104 Some(dir) => lintel_config::find_and_load(dir).map(Option::unwrap_or_default),
105 None => lintel_config::load(),
106 };
107
108 match cfg_result {
109 Ok(cfg) => {
110 let cli_excludes = core::mem::take(&mut args.exclude);
112 args.exclude = cfg.exclude;
113 args.exclude.extend(cli_excludes);
114 }
115 Err(e) => {
116 eprintln!("warning: failed to load lintel.toml: {e}");
117 }
118 }
119}
120
121pub async fn run(args: &mut ValidateArgs, reporter: &mut dyn Reporter) -> Result<bool> {
133 merge_config(args);
134
135 let lib_args = validate::ValidateArgs::from(&*args);
136 let start = Instant::now();
137 let result = validate::run_with(&lib_args, None, |file| {
138 reporter.on_file_checked(file);
139 })
140 .await?;
141 let had_errors = result.has_errors();
142 let elapsed = start.elapsed();
143
144 reporter.report(result, elapsed);
145
146 Ok(had_errors)
147}