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