sugar_cli/validate/
process.rs1use std::{
2 fs::File,
3 path::Path,
4 sync::{Arc, Mutex},
5};
6
7use anyhow::Result;
8use console::{style, Style};
9use dialoguer::{theme::ColorfulTheme, Confirm};
10use glob::glob;
11use rayon::prelude::*;
12
13use crate::{common::*, utils::*, validate::*};
14
15pub struct ValidateArgs {
16 pub assets_dir: String,
17 pub strict: bool,
18 pub skip_collection_prompt: bool,
19}
20
21pub fn process_validate(args: ValidateArgs) -> Result<()> {
22 println!(
24 "{} {}Loading assets",
25 style("[1/1]").bold().dim(),
26 ASSETS_EMOJI
27 );
28
29 let assets_dir = Path::new(&args.assets_dir);
30
31 if !assets_dir.exists() || assets_dir.read_dir()?.next().is_none() {
33 info!("Assets directory is missing or empty.");
34 return Err(ValidateParserError::MissingOrEmptyAssetsDirectory.into());
35 }
36
37 if !args.skip_collection_prompt {
38 let collection_path = assets_dir.join("collection.json");
39 if !collection_path.is_file() {
40 let warning = format!(
41 "+----------------------------------------------+\n\
42 | {} MISSING COLLECTION FILES IN ASSETS FOLDER |\n\
43 +----------------------------------------------+",
44 WARNING_EMOJI
45 );
46 println!(
47 "\n{}\n{}\n",
48 style(warning).bold().yellow(),
49 style(
50 "Check https://docs.metaplex.com/developer-tools/sugar/guides/preparing-assets for the collection file requirements \
51 if you want a collection to be set automatically."
52 )
53 .italic()
54 .yellow()
55 );
56
57 let theme = ColorfulTheme {
58 success_prefix: style("✔".to_string()).yellow().force_styling(true),
59 values_style: Style::new().yellow(),
60 ..get_dialoguer_theme()
61 };
62
63 if !Confirm::with_theme(&theme).with_prompt("Do you want to continue without automatically setting the candy machine collection?").interact()? {
64 return Err(anyhow!("Operation aborted"));
65 }
66 println!();
67 }
68 }
69
70 let errors = Arc::new(Mutex::new(Vec::new()));
71
72 let path = assets_dir.join("*.json");
73 let pattern = path
74 .to_str()
75 .ok_or(ValidateParserError::InvalidAssetsDirectory)?;
76
77 let paths: Vec<PathBuf> = glob(pattern).unwrap().map(Result::unwrap).collect();
80
81 validate_continuous_assets(&paths)?;
83
84 let pb = spinner_with_style();
85 pb.enable_steady_tick(120);
86 pb.set_message(format!("Validating {} metadata file(s)...", paths.len()));
87
88 paths.par_iter().for_each(|path| {
89 let errors = errors.clone();
90 let f = match File::open(path) {
91 Ok(f) => f,
92 Err(error) => {
93 error!("{}: {}", path.display(), error);
94 errors.lock().unwrap().push(ValidateError {
95 path,
96 error: error.to_string(),
97 });
98 return;
99 }
100 };
101
102 let mut metadata = match serde_json::from_reader::<File, Metadata>(f) {
103 Ok(metadata) => metadata,
104 Err(error) => {
105 error!("{}: {}", path.display(), error);
106 errors.lock().unwrap().push(ValidateError {
107 path,
108 error: error.to_string(),
109 });
110 return;
111 }
112 };
113
114 if args.strict {
116 match metadata.validate() {
117 Ok(()) => {}
118 Err(e) => {
119 error!("{}: {}", path.display(), e);
120 errors.lock().unwrap().push(ValidateError {
121 path,
122 error: e.to_string(),
123 });
124 }
125 }
126 } else {
127 match metadata.validate() {
128 Ok(()) => {}
129 Err(e) => {
130 error!("{}: {}", path.display(), e);
131 errors.lock().unwrap().push(ValidateError {
132 path,
133 error: e.to_string(),
134 });
135 }
136 }
137 }
138 });
139
140 pb.finish();
141
142 if !errors.lock().unwrap().is_empty() {
143 log_errors("validate_errors", errors)?;
144 return Err(anyhow!(
145 "Validation error: see 'validate_errors.json' file for details"
146 ));
147 }
148
149 let message = "Validation complete, your metadata file(s) look good.";
150 info!("{message}");
151 println!("\n{message}");
152
153 Ok(())
154}