From 7a52232182db6d59db45187cd5308a6967213a96 Mon Sep 17 00:00:00 2001 From: mohiiit Date: Thu, 27 Nov 2025 16:17:30 +0530 Subject: [PATCH 1/5] feat: adding range feature for cli --- crates/core/generate-pie/src/main.rs | 62 +++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/crates/core/generate-pie/src/main.rs b/crates/core/generate-pie/src/main.rs index 8c138efa..51305e19 100644 --- a/crates/core/generate-pie/src/main.rs +++ b/crates/core/generate-pie/src/main.rs @@ -3,6 +3,8 @@ //! This binary demonstrates how to use the generate-pie library to generate //! Cairo PIE files from Starknet blocks. +use std::collections::BTreeSet; + use cairo_vm::types::layout_name::LayoutName; use clap::Parser; use generate_pie::constants::{DEFAULT_SEPOLIA_ETH_FEE_TOKEN, DEFAULT_SEPOLIA_STRK_FEE_TOKEN}; @@ -11,15 +13,43 @@ use generate_pie::utils::load_versioned_constants; use generate_pie::{generate_pie, parse_layout}; use log::{error, info}; +/// Parses a range string in format "start,end" and returns (start, end). +/// Both start and end are inclusive. +fn parse_range(range_str: &str) -> Result<(u64, u64), String> { + let parts: Vec<&str> = range_str.split(',').collect(); + if parts.len() != 2 { + return Err(format!("Invalid range format '{}'. Expected format: start,end (e.g., 1,999)", range_str)); + } + + let start: u64 = parts[0] + .trim() + .parse() + .map_err(|_| format!("Invalid start value '{}'. Must be a positive integer.", parts[0]))?; + let end: u64 = parts[1] + .trim() + .parse() + .map_err(|_| format!("Invalid end value '{}'. Must be a positive integer.", parts[1]))?; + + if start > end { + return Err(format!("Invalid range: start ({}) must be less than or equal to end ({})", start, end)); + } + + Ok((start, end)) +} + #[derive(Parser)] #[command(author, version, about, long_about = None)] #[command(name = "snos")] #[command(about = "SNOS - Starknet OS for block processing")] struct Cli { - /// Block number(s) to process - #[arg(short, long, value_delimiter = ',', required = true, env = "SNOS_BLOCKS")] + /// Block number(s) to process (comma-separated) + #[arg(short, long, value_delimiter = ',', env = "SNOS_BLOCKS")] blocks: Vec, + /// Block range to process (format: start,end - inclusive) + #[arg(short = 'R', long, env = "SNOS_RANGE")] + range: Option, + /// RPC URL to connect to #[arg(short, long, required = true, env = "SNOS_RPC_URL")] rpc_url: String, @@ -82,9 +112,31 @@ async fn main() -> Result<(), Box> { info!("Starting SNOS PIE generation application"); + // Collect blocks from --blocks and --range arguments + let mut blocks: BTreeSet = cli.blocks.into_iter().collect(); + + // Parse and add blocks from range if provided + if let Some(range_str) = &cli.range { + match parse_range(range_str) { + Ok((start, end)) => { + info!("Adding blocks from range {} to {} (inclusive)", start, end); + for block in start..=end { + blocks.insert(block); + } + } + Err(e) => { + error!("Range parsing error: {}", e); + std::process::exit(1); + } + } + } + + // Convert to sorted Vec + let blocks: Vec = blocks.into_iter().collect(); + // Validate that at least one block is provided - if cli.blocks.is_empty() { - error!("At least one block number must be provided"); + if blocks.is_empty() { + error!("At least one block number must be provided. Use --blocks and/or --range."); std::process::exit(1); } @@ -97,7 +149,7 @@ async fn main() -> Result<(), Box> { // Build the input configuration let input = PieGenerationInput { rpc_url: cli.rpc_url.clone(), - blocks: cli.blocks.clone(), + blocks: blocks.clone(), chain_config: ChainConfig::new(&cli.chain, &cli.strk_fee_token_address, &cli.eth_fee_token_address, cli.is_l3), os_hints_config: OsHintsConfiguration::default_with_is_l3(cli.is_l3), output_path: cli.output.clone(), From 543370944f4ea0a72c29a140c576f5964d737760 Mon Sep 17 00:00:00 2001 From: mohiiit Date: Tue, 2 Dec 2025 03:44:58 +0530 Subject: [PATCH 2/5] chore: comment resolution --- crates/core/generate-pie/src/main.rs | 81 +++++++++++++++------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/crates/core/generate-pie/src/main.rs b/crates/core/generate-pie/src/main.rs index 51305e19..5ac3b105 100644 --- a/crates/core/generate-pie/src/main.rs +++ b/crates/core/generate-pie/src/main.rs @@ -4,6 +4,7 @@ //! Cairo PIE files from Starknet blocks. use std::collections::BTreeSet; +use std::str::FromStr; use cairo_vm::types::layout_name::LayoutName; use clap::Parser; @@ -13,28 +14,44 @@ use generate_pie::utils::load_versioned_constants; use generate_pie::{generate_pie, parse_layout}; use log::{error, info}; -/// Parses a range string in format "start,end" and returns (start, end). -/// Both start and end are inclusive. -fn parse_range(range_str: &str) -> Result<(u64, u64), String> { - let parts: Vec<&str> = range_str.split(',').collect(); - if parts.len() != 2 { - return Err(format!("Invalid range format '{}'. Expected format: start,end (e.g., 1,999)", range_str)); - } +/// Represents a range of block numbers (inclusive). +#[derive(Debug, Clone, Copy)] +struct BlockRange { + start: u64, + end: u64, +} - let start: u64 = parts[0] - .trim() - .parse() - .map_err(|_| format!("Invalid start value '{}'. Must be a positive integer.", parts[0]))?; - let end: u64 = parts[1] - .trim() - .parse() - .map_err(|_| format!("Invalid end value '{}'. Must be a positive integer.", parts[1]))?; - - if start > end { - return Err(format!("Invalid range: start ({}) must be less than or equal to end ({})", start, end)); +impl BlockRange { + /// Returns an iterator over all block numbers in this range (inclusive). + fn iter(&self) -> impl Iterator { + self.start..=self.end } +} - Ok((start, end)) +impl FromStr for BlockRange { + type Err = String; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split(',').collect(); + if parts.len() != 2 { + return Err(format!("Invalid range format '{}'. Expected format: start,end (e.g., 1,999).", s)); + } + + let start: u64 = parts[0] + .trim() + .parse() + .map_err(|_| format!("Invalid start value '{}'. Must be a positive integer.", parts[0]))?; + let end: u64 = parts[1] + .trim() + .parse() + .map_err(|_| format!("Invalid end value '{}'. Must be a positive integer.", parts[1]))?; + + if start > end { + return Err(format!("Invalid range: start ({}) must be less than or equal to end ({}).", start, end)); + } + + Ok(BlockRange { start, end }) + } } #[derive(Parser)] @@ -48,7 +65,7 @@ struct Cli { /// Block range to process (format: start,end - inclusive) #[arg(short = 'R', long, env = "SNOS_RANGE")] - range: Option, + range: Option, /// RPC URL to connect to #[arg(short, long, required = true, env = "SNOS_RPC_URL")] @@ -115,31 +132,21 @@ async fn main() -> Result<(), Box> { // Collect blocks from --blocks and --range arguments let mut blocks: BTreeSet = cli.blocks.into_iter().collect(); - // Parse and add blocks from range if provided - if let Some(range_str) = &cli.range { - match parse_range(range_str) { - Ok((start, end)) => { - info!("Adding blocks from range {} to {} (inclusive)", start, end); - for block in start..=end { - blocks.insert(block); - } - } - Err(e) => { - error!("Range parsing error: {}", e); - std::process::exit(1); - } - } + // Add blocks from range if provided + if let Some(range) = cli.range { + info!("Adding blocks from range {} to {} (inclusive)", range.start, range.end); + blocks.extend(range.iter()); } - // Convert to sorted Vec - let blocks: Vec = blocks.into_iter().collect(); - // Validate that at least one block is provided if blocks.is_empty() { error!("At least one block number must be provided. Use --blocks and/or --range."); std::process::exit(1); } + // Convert to sorted Vec + let blocks: Vec = blocks.into_iter().collect(); + // Load versioned constants from file if provided let versioned_constants = load_versioned_constants(cli.versioned_constants_path.as_deref()).map_err(|e| { error!("{}", e); From 7d178342cc4b2682c1faf961c7fbe8ca54749ed3 Mon Sep 17 00:00:00 2001 From: mohiiit Date: Mon, 23 Feb 2026 14:53:26 +0530 Subject: [PATCH 3/5] fix(generate-pie): add block range validation tests --- crates/core/generate-pie/src/main.rs | 136 ++++++++++++++++++++++----- 1 file changed, 112 insertions(+), 24 deletions(-) diff --git a/crates/core/generate-pie/src/main.rs b/crates/core/generate-pie/src/main.rs index 5ac3b105..c8fd310b 100644 --- a/crates/core/generate-pie/src/main.rs +++ b/crates/core/generate-pie/src/main.rs @@ -15,7 +15,7 @@ use generate_pie::{generate_pie, parse_layout}; use log::{error, info}; /// Represents a range of block numbers (inclusive). -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] struct BlockRange { start: u64, end: u64, @@ -32,19 +32,18 @@ impl FromStr for BlockRange { type Err = String; fn from_str(s: &str) -> Result { - let parts: Vec<&str> = s.split(',').collect(); - if parts.len() != 2 { - return Err(format!("Invalid range format '{}'. Expected format: start,end (e.g., 1,999).", s)); - } + let (start_raw, end_raw) = s + .split_once(',') + .ok_or_else(|| format!("Invalid range format '{}'. Expected format: start,end (e.g., 1,999).", s))?; - let start: u64 = parts[0] + let start: u64 = start_raw .trim() .parse() - .map_err(|_| format!("Invalid start value '{}'. Must be a positive integer.", parts[0]))?; - let end: u64 = parts[1] + .map_err(|_| format!("Invalid start value '{}'. Must be a positive integer.", start_raw))?; + let end: u64 = end_raw .trim() .parse() - .map_err(|_| format!("Invalid end value '{}'. Must be a positive integer.", parts[1]))?; + .map_err(|_| format!("Invalid end value '{}'. Must be a positive integer.", end_raw))?; if start > end { return Err(format!("Invalid range: start ({}) must be less than or equal to end ({}).", start, end)); @@ -99,6 +98,22 @@ struct Cli { #[arg(long, env = "SNOS_VERSIONED_CONSTANTS_PATH")] versioned_constants_path: Option, } + +const EMPTY_BLOCK_SELECTION_ERROR: &str = "At least one block number must be provided. Use --blocks and/or --range."; + +fn collect_blocks(blocks: Vec, range: Option) -> Result, &'static str> { + let mut selected_blocks: BTreeSet = blocks.into_iter().collect(); + + if let Some(range) = range { + selected_blocks.extend(range.iter()); + } + + if selected_blocks.is_empty() { + return Err(EMPTY_BLOCK_SELECTION_ERROR); + } + + Ok(selected_blocks.into_iter().collect()) +} /// Main entry point for the generate-pie application. /// /// This function demonstrates the usage of the generate-pie library by: @@ -125,27 +140,21 @@ async fn main() -> Result<(), Box> { // Initialize logging env_logger::init(); - let cli = Cli::parse(); + let mut cli = Cli::parse(); info!("Starting SNOS PIE generation application"); - // Collect blocks from --blocks and --range arguments - let mut blocks: BTreeSet = cli.blocks.into_iter().collect(); - - // Add blocks from range if provided if let Some(range) = cli.range { info!("Adding blocks from range {} to {} (inclusive)", range.start, range.end); - blocks.extend(range.iter()); - } - - // Validate that at least one block is provided - if blocks.is_empty() { - error!("At least one block number must be provided. Use --blocks and/or --range."); - std::process::exit(1); } - // Convert to sorted Vec - let blocks: Vec = blocks.into_iter().collect(); + let blocks = match collect_blocks(std::mem::take(&mut cli.blocks), cli.range) { + Ok(blocks) => blocks, + Err(message) => { + error!("{message}"); + std::process::exit(1); + } + }; // Load versioned constants from file if provided let versioned_constants = load_versioned_constants(cli.versioned_constants_path.as_deref()).map_err(|e| { @@ -156,7 +165,7 @@ async fn main() -> Result<(), Box> { // Build the input configuration let input = PieGenerationInput { rpc_url: cli.rpc_url.clone(), - blocks: blocks.clone(), + blocks, chain_config: ChainConfig::new(&cli.chain, &cli.strk_fee_token_address, &cli.eth_fee_token_address, cli.is_l3), os_hints_config: OsHintsConfiguration::default_with_is_l3(cli.is_l3), output_path: cli.output.clone(), @@ -200,3 +209,82 @@ async fn main() -> Result<(), Box> { info!("SNOS execution completed successfully!"); Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn block_range_parses_valid_input() { + let range = BlockRange::from_str("1,3").expect("valid range should parse"); + + assert_eq!(range.start, 1); + assert_eq!(range.end, 3); + assert_eq!(range.iter().collect::>(), vec![1, 2, 3]); + } + + #[test] + fn block_range_rejects_invalid_format() { + let error = BlockRange::from_str("1").expect_err("single value should fail"); + + assert_eq!(error, "Invalid range format '1'. Expected format: start,end (e.g., 1,999)."); + } + + #[test] + fn block_range_rejects_invalid_start() { + let error = BlockRange::from_str("abc,2").expect_err("non numeric start should fail"); + + assert_eq!(error, "Invalid start value 'abc'. Must be a positive integer."); + } + + #[test] + fn block_range_rejects_invalid_end() { + let error = BlockRange::from_str("1,xyz").expect_err("non numeric end should fail"); + + assert_eq!(error, "Invalid end value 'xyz'. Must be a positive integer."); + } + + #[test] + fn block_range_rejects_start_greater_than_end() { + let error = BlockRange::from_str("9,2").expect_err("start greater than end should fail"); + + assert_eq!(error, "Invalid range: start (9) must be less than or equal to end (2)."); + } + + #[test] + fn collect_blocks_from_blocks_only_deduplicates_and_sorts() { + let blocks = collect_blocks(vec![5, 3, 5, 4], None).expect("blocks should be collected"); + + assert_eq!(blocks, vec![3, 4, 5]); + } + + #[test] + fn collect_blocks_from_range_only() { + let blocks = collect_blocks(vec![], Some(BlockRange { start: 7, end: 9 })).expect("range should be collected"); + + assert_eq!(blocks, vec![7, 8, 9]); + } + + #[test] + fn collect_blocks_combines_range_and_blocks() { + let blocks = collect_blocks(vec![10, 12], Some(BlockRange { start: 11, end: 12 })) + .expect("range and blocks should be combined"); + + assert_eq!(blocks, vec![10, 11, 12]); + } + + #[test] + fn collect_blocks_rejects_empty_input() { + let error = collect_blocks(vec![], None).expect_err("empty input should fail"); + + assert_eq!(error, EMPTY_BLOCK_SELECTION_ERROR); + } + + #[test] + fn cli_parses_range_argument_into_block_range() { + let cli = Cli::try_parse_from(["snos", "--rpc-url", "http://localhost:8545", "--range", "4,6"]) + .expect("cli should parse range argument"); + + assert_eq!(cli.range, Some(BlockRange { start: 4, end: 6 })); + } +} From 888855edecbd9d950244762a18b24f9b7f848962 Mon Sep 17 00:00:00 2001 From: mohiiit Date: Mon, 23 Feb 2026 15:08:04 +0530 Subject: [PATCH 4/5] test(generate-pie): migrate block range tests to rstest --- Cargo.lock | 1 + crates/core/generate-pie/Cargo.toml | 3 + crates/core/generate-pie/src/main.rs | 93 ++++++++++------------------ 3 files changed, 37 insertions(+), 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0bcccfca..c7193b95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3011,6 +3011,7 @@ dependencies = [ "num-traits", "rayon", "rpc-client", + "rstest 0.18.2", "serde", "serde_json", "shared_execution_objects", diff --git a/crates/core/generate-pie/Cargo.toml b/crates/core/generate-pie/Cargo.toml index 3c77248f..bfba57b6 100644 --- a/crates/core/generate-pie/Cargo.toml +++ b/crates/core/generate-pie/Cargo.toml @@ -40,3 +40,6 @@ starknet-core = { workspace = true } starknet-types-core = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } + +[dev-dependencies] +rstest = { workspace = true } diff --git a/crates/core/generate-pie/src/main.rs b/crates/core/generate-pie/src/main.rs index c6a81e32..43b17b3a 100644 --- a/crates/core/generate-pie/src/main.rs +++ b/crates/core/generate-pie/src/main.rs @@ -223,71 +223,44 @@ async fn main() -> Result<(), Box> { #[cfg(test)] mod tests { use super::*; - - #[test] - fn block_range_parses_valid_input() { - let range = BlockRange::from_str("1,3").expect("valid range should parse"); - - assert_eq!(range.start, 1); - assert_eq!(range.end, 3); - assert_eq!(range.iter().collect::>(), vec![1, 2, 3]); - } - - #[test] - fn block_range_rejects_invalid_format() { - let error = BlockRange::from_str("1").expect_err("single value should fail"); - - assert_eq!(error, "Invalid range format '1'. Expected format: start,end (e.g., 1,999)."); - } - - #[test] - fn block_range_rejects_invalid_start() { - let error = BlockRange::from_str("abc,2").expect_err("non numeric start should fail"); - - assert_eq!(error, "Invalid start value 'abc'. Must be a positive integer."); - } - - #[test] - fn block_range_rejects_invalid_end() { - let error = BlockRange::from_str("1,xyz").expect_err("non numeric end should fail"); - - assert_eq!(error, "Invalid end value 'xyz'. Must be a positive integer."); + use rstest::rstest; + + #[rstest] + #[case("1,3", BlockRange { start: 1, end: 3 }, vec![1, 2, 3])] + #[case(" 4 , 5 ", BlockRange { start: 4, end: 5 }, vec![4, 5])] + fn block_range_parses_valid_input( + #[case] input: &str, + #[case] expected_range: BlockRange, + #[case] expected_blocks: Vec, + ) { + let range = BlockRange::from_str(input).expect("valid range should parse"); + + assert_eq!(range, expected_range); + assert_eq!(range.iter().collect::>(), expected_blocks); } - #[test] - fn block_range_rejects_start_greater_than_end() { - let error = BlockRange::from_str("9,2").expect_err("start greater than end should fail"); - - assert_eq!(error, "Invalid range: start (9) must be less than or equal to end (2)."); - } - - #[test] - fn collect_blocks_from_blocks_only_deduplicates_and_sorts() { - let blocks = collect_blocks(vec![5, 3, 5, 4], None).expect("blocks should be collected"); + #[rstest] + #[case("1", "Invalid range format '1'. Expected format: start,end (e.g., 1,999).")] + #[case("abc,2", "Invalid start value 'abc'. Must be a positive integer.")] + #[case("1,xyz", "Invalid end value 'xyz'. Must be a positive integer.")] + #[case("9,2", "Invalid range: start (9) must be less than or equal to end (2).")] + fn block_range_rejects_invalid_input(#[case] input: &str, #[case] expected_error: &str) { + let error = BlockRange::from_str(input).expect_err("invalid range should fail"); - assert_eq!(blocks, vec![3, 4, 5]); + assert_eq!(error, expected_error); } - #[test] - fn collect_blocks_from_range_only() { - let blocks = collect_blocks(vec![], Some(BlockRange { start: 7, end: 9 })).expect("range should be collected"); - - assert_eq!(blocks, vec![7, 8, 9]); - } - - #[test] - fn collect_blocks_combines_range_and_blocks() { - let blocks = collect_blocks(vec![10, 12], Some(BlockRange { start: 11, end: 12 })) - .expect("range and blocks should be combined"); - - assert_eq!(blocks, vec![10, 11, 12]); - } - - #[test] - fn collect_blocks_rejects_empty_input() { - let error = collect_blocks(vec![], None).expect_err("empty input should fail"); - - assert_eq!(error, EMPTY_BLOCK_SELECTION_ERROR); + #[rstest] + #[case(vec![5, 3, 5, 4], None, Ok(vec![3, 4, 5]))] + #[case(vec![], Some(BlockRange { start: 7, end: 9 }), Ok(vec![7, 8, 9]))] + #[case(vec![10, 12], Some(BlockRange { start: 11, end: 12 }), Ok(vec![10, 11, 12]))] + #[case(vec![], None, Err(EMPTY_BLOCK_SELECTION_ERROR))] + fn collect_blocks_handles_inputs( + #[case] blocks: Vec, + #[case] range: Option, + #[case] expected: Result, &'static str>, + ) { + assert_eq!(collect_blocks(blocks, range), expected); } #[test] From a5fc3c63956ee3e9f7a182a2067f8512ff30580c Mon Sep 17 00:00:00 2001 From: mohiiit Date: Mon, 23 Feb 2026 15:21:06 +0530 Subject: [PATCH 5/5] fix(generate-pie): align main cli errors to typed enums --- crates/core/generate-pie/src/main.rs | 121 ++++++++++++++++----------- 1 file changed, 74 insertions(+), 47 deletions(-) diff --git a/crates/core/generate-pie/src/main.rs b/crates/core/generate-pie/src/main.rs index 43b17b3a..b24d6e92 100644 --- a/crates/core/generate-pie/src/main.rs +++ b/crates/core/generate-pie/src/main.rs @@ -21,6 +21,30 @@ struct BlockRange { end: u64, } +#[derive(Debug, thiserror::Error, PartialEq, Eq)] +enum CliInputError { + #[error("Invalid range format '{input}'. Expected format: start,end (e.g., 1,999).")] + InvalidRangeFormat { input: String }, + #[error("Invalid start value '{value}'. Must be a positive integer.")] + InvalidStartValue { value: String }, + #[error("Invalid end value '{value}'. Must be a positive integer.")] + InvalidEndValue { value: String }, + #[error("Invalid range: start ({start}) must be less than or equal to end ({end}).")] + InvalidRangeBounds { start: u64, end: u64 }, + #[error("At least one block number must be provided. Use --blocks and/or --range.")] + EmptyBlockSelection, + #[error("Invalid versioned constants configuration: {message}")] + VersionedConstantsConfig { message: String }, +} + +#[derive(Debug, thiserror::Error)] +enum AppError { + #[error(transparent)] + CliInput(#[from] CliInputError), + #[error(transparent)] + PieGeneration(#[from] generate_pie::error::PieGenerationError), +} + impl BlockRange { /// Returns an iterator over all block numbers in this range (inclusive). fn iter(&self) -> impl Iterator { @@ -29,24 +53,19 @@ impl BlockRange { } impl FromStr for BlockRange { - type Err = String; + type Err = CliInputError; fn from_str(s: &str) -> Result { - let (start_raw, end_raw) = s - .split_once(',') - .ok_or_else(|| format!("Invalid range format '{}'. Expected format: start,end (e.g., 1,999).", s))?; - - let start: u64 = start_raw - .trim() - .parse() - .map_err(|_| format!("Invalid start value '{}'. Must be a positive integer.", start_raw))?; - let end: u64 = end_raw - .trim() - .parse() - .map_err(|_| format!("Invalid end value '{}'. Must be a positive integer.", end_raw))?; + let (start_raw, end_raw) = + s.split_once(',').ok_or_else(|| CliInputError::InvalidRangeFormat { input: s.to_string() })?; + + let start: u64 = + start_raw.trim().parse().map_err(|_| CliInputError::InvalidStartValue { value: start_raw.to_string() })?; + let end: u64 = + end_raw.trim().parse().map_err(|_| CliInputError::InvalidEndValue { value: end_raw.to_string() })?; if start > end { - return Err(format!("Invalid range: start ({}) must be less than or equal to end ({}).", start, end)); + return Err(CliInputError::InvalidRangeBounds { start, end }); } Ok(BlockRange { start, end }) @@ -103,9 +122,7 @@ struct Cli { public_keys: Option>, } -const EMPTY_BLOCK_SELECTION_ERROR: &str = "At least one block number must be provided. Use --blocks and/or --range."; - -fn collect_blocks(blocks: Vec, range: Option) -> Result, &'static str> { +fn collect_blocks(blocks: Vec, range: Option) -> Result, CliInputError> { let mut selected_blocks: BTreeSet = blocks.into_iter().collect(); if let Some(range) = range { @@ -113,7 +130,7 @@ fn collect_blocks(blocks: Vec, range: Option) -> Result, range: Option) -> Result Result<(), Box> { +async fn main() -> Result<(), AppError> { // Initialize logging env_logger::init(); @@ -152,18 +169,16 @@ async fn main() -> Result<(), Box> { info!("Adding blocks from range {} to {} (inclusive)", range.start, range.end); } - let blocks = match collect_blocks(std::mem::take(&mut cli.blocks), cli.range) { - Ok(blocks) => blocks, - Err(message) => { - error!("{message}"); - std::process::exit(1); - } - }; + let blocks = collect_blocks(std::mem::take(&mut cli.blocks), cli.range).map_err(|e| { + error!("{e}"); + e + })?; // Load versioned constants from file if provided let versioned_constants = load_versioned_constants(cli.versioned_constants_path.as_deref()).map_err(|e| { - error!("{}", e); - e + let typed_error = CliInputError::VersionedConstantsConfig { message: e }; + error!("{typed_error}"); + typed_error })?; // Build the input configuration @@ -202,18 +217,15 @@ async fn main() -> Result<(), Box> { } // Call the core PIE generation function - match generate_pie(input).await { - Ok(result) => { - info!("PIE generation completed successfully!"); - info!(" Blocks processed: {:?}", result.blocks_processed); - if let Some(output_path) = result.output_path { - info!(" Output written to: {}", output_path); - } - } - Err(e) => { - error!("PIE generation failed: {}", e); - return Err(e.into()); - } + let result = generate_pie(input).await.map_err(|e| { + error!("PIE generation failed: {}", e); + e + })?; + + info!("PIE generation completed successfully!"); + info!(" Blocks processed: {:?}", result.blocks_processed); + if let Some(output_path) = result.output_path { + info!(" Output written to: {}", output_path); } info!("SNOS execution completed successfully!"); @@ -240,11 +252,26 @@ mod tests { } #[rstest] - #[case("1", "Invalid range format '1'. Expected format: start,end (e.g., 1,999).")] - #[case("abc,2", "Invalid start value 'abc'. Must be a positive integer.")] - #[case("1,xyz", "Invalid end value 'xyz'. Must be a positive integer.")] - #[case("9,2", "Invalid range: start (9) must be less than or equal to end (2).")] - fn block_range_rejects_invalid_input(#[case] input: &str, #[case] expected_error: &str) { + #[case( + "1", + CliInputError::InvalidRangeFormat { + input: "1".to_string() + } + )] + #[case( + "abc,2", + CliInputError::InvalidStartValue { + value: "abc".to_string() + } + )] + #[case( + "1,xyz", + CliInputError::InvalidEndValue { + value: "xyz".to_string() + } + )] + #[case("9,2", CliInputError::InvalidRangeBounds { start: 9, end: 2 })] + fn block_range_rejects_invalid_input(#[case] input: &str, #[case] expected_error: CliInputError) { let error = BlockRange::from_str(input).expect_err("invalid range should fail"); assert_eq!(error, expected_error); @@ -254,11 +281,11 @@ mod tests { #[case(vec![5, 3, 5, 4], None, Ok(vec![3, 4, 5]))] #[case(vec![], Some(BlockRange { start: 7, end: 9 }), Ok(vec![7, 8, 9]))] #[case(vec![10, 12], Some(BlockRange { start: 11, end: 12 }), Ok(vec![10, 11, 12]))] - #[case(vec![], None, Err(EMPTY_BLOCK_SELECTION_ERROR))] + #[case(vec![], None, Err(CliInputError::EmptyBlockSelection))] fn collect_blocks_handles_inputs( #[case] blocks: Vec, #[case] range: Option, - #[case] expected: Result, &'static str>, + #[case] expected: Result, CliInputError>, ) { assert_eq!(collect_blocks(blocks, range), expected); }