sugar_cli/validate/
process.rs

1use 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    // loading assets
23    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    // missing or empty assets directory
32    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    // Unwrapping here because we know the pattern is valid and GlobErrors should
78    // be rare or impossible to produce.
79    let paths: Vec<PathBuf> = glob(pattern).unwrap().map(Result::unwrap).collect();
80
81    // Validating continuous assets in directory
82    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        // To be replaced with the strict validator once JSON standard is finalized.
115        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}