mod.rs
1 use std::path::{Path, PathBuf}; 2 3 use apibara_sink_common::{ScriptOptions, StreamOptions}; 4 use clap::Args; 5 use error_stack::{Result, ResultExt}; 6 use tracing::warn; 7 8 use crate::error::CliError; 9 10 mod error; 11 mod run; 12 mod snapshot; 13 14 pub const SNAPSHOTS_DIR: &str = "snapshots"; 15 16 #[derive(Args, Debug)] 17 pub struct TestArgs { 18 /// An indexer script (.js/.ts), a snapshot file (.json) or a folder of snapshots. 19 path: Option<PathBuf>, 20 /// The number of blocks to stream. 21 #[arg(long, short = 'b')] 22 num_batches: Option<usize>, 23 /// Regenerate the snapshot even if it already exists. 24 #[arg(long, short = 'o', default_value_t = false)] 25 r#override: bool, 26 /// The name of the snapshot. 27 #[arg(long, short = 'n')] 28 name: Option<String>, 29 /// Override the starting block from the script. 30 #[arg(long, short, env)] 31 starting_block: Option<u64>, 32 #[clap(flatten)] 33 stream_options: StreamOptions, 34 #[clap(flatten)] 35 dotenv_options: ScriptOptions, 36 } 37 38 fn validate_args(args: &TestArgs) -> Result<(), CliError> { 39 if let Some(name) = &args.name { 40 let is_invalid = name.contains(|c| { 41 c == '/' 42 || c == '.' 43 || c == '\\' 44 || c == ':' 45 || c == '*' 46 || c == '?' 47 || c == '"' 48 || c == '<' 49 || c == '>' 50 || c == '|' 51 }); 52 if is_invalid { 53 return Err(CliError).attach_printable( 54 r#"Invalid name `{name}`, name should not contain /, ., \, :, *, ?, ", <, >, or | as it'll be used to construct the snapshot path"# 55 ); 56 } 57 } 58 59 if let Some(num_batches) = args.num_batches { 60 if num_batches < 1 { 61 return Err(CliError).attach_printable_lazy(|| { 62 format!("Invalid number of blocks `{num_batches}`, it should be > 0") 63 }); 64 } 65 } 66 67 if let Some(path) = &args.path { 68 // Think about using try_exists instead 69 if !path.exists() { 70 return Err(CliError).attach_printable_lazy(|| { 71 format!( 72 "Invalid path: `{}`, no such file or directory", 73 &path.display() 74 ) 75 }); 76 } 77 } 78 79 Ok(()) 80 } 81 82 pub fn warn_ignored_args(args: &TestArgs) { 83 if args.starting_block.is_some() 84 || args.num_batches.is_some() 85 || args.r#override 86 || args.stream_options.stream_url.is_some() 87 || args.stream_options.max_message_size.is_some() 88 || args.stream_options.metadata.is_some() 89 { 90 warn!( 91 "The following arguments are ignored: --starting-block, --num-batches, \ 92 --override, --stream-url, --max-message-size, --metadata when running tests, \ 93 if you want to generate a snapshot with different options, \ 94 use the --override flag or give it a name with --name" 95 ) 96 } 97 } 98 99 pub async fn run(args: TestArgs) -> Result<(), CliError> { 100 validate_args(&args)?; 101 102 match &args.path { 103 Some(path) => { 104 // Think about using try_exists instead 105 if !path.exists() { 106 return Err(CliError).attach_printable_lazy(|| { 107 format!( 108 "Invalid path: `{}`, no such file or directory", 109 &path.display() 110 ) 111 }); 112 } 113 114 if path.is_dir() { 115 warn_ignored_args(&args); 116 run::run_all_tests(&path, &args.dotenv_options, None).await?; 117 return Ok(()); 118 } 119 120 let extension = Path::new(&path) 121 .extension() 122 .ok_or(CliError) 123 .attach_printable_lazy(|| format!("Invalid path: `{}`", path.display()))?; 124 125 match extension.to_str().unwrap() { 126 "json" => { 127 warn_ignored_args(&args); 128 run::run_single_test(path, None, None, &args.dotenv_options).await?; 129 }, 130 "js" | "ts" => { 131 let snapshot_path = args.name.clone() 132 .map(|name| Path::new(SNAPSHOTS_DIR).join(name).with_extension("json")) 133 .unwrap_or(snapshot::get_snapshot_path(path)?); 134 135 if args.r#override || !snapshot_path.exists() { 136 run::run_generate_snapshot( 137 path, 138 &snapshot_path, 139 args.starting_block, 140 args.num_batches, 141 &args.stream_options, 142 &args.dotenv_options, 143 ).await?; 144 } else { 145 warn_ignored_args(&args); 146 if args.name.is_some() { 147 run::run_single_test(&snapshot_path, None, Some(path), &args.dotenv_options).await?; 148 } else { 149 run::run_all_tests(SNAPSHOTS_DIR, &args.dotenv_options, Some(path)).await?; 150 } 151 } 152 } 153 _ => return Err(CliError).attach_printable_lazy(|| format!( 154 "Invalid file extension: `{}`, must be a .json for snapshots or .js / .ts for scripts", 155 path.display() 156 )), 157 } 158 } 159 None => { 160 warn_ignored_args(&args); 161 run::run_all_tests(SNAPSHOTS_DIR, &args.dotenv_options, None).await?; 162 } 163 } 164 165 Ok(()) 166 }