Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions src/commands/build/snapshots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::str::FromStr as _;
use anyhow::{Context as _, Result};
use clap::{Arg, ArgMatches, Command};
use console::style;
use itertools::Itertools as _;
use log::{debug, info, warn};
use objectstore_client::{ClientBuilder, ExpirationPolicy, Usecase};
use secrecy::ExposeSecret as _;
Expand All @@ -21,6 +22,7 @@ const EXPERIMENTAL_WARNING: &str =
The command is subject to breaking changes, including removal, in any Sentry CLI release.";

const IMAGE_EXTENSIONS: &[&str] = &["png", "jpg", "jpeg"];
const MAX_PIXELS_PER_IMAGE: u64 = 40_000_000;

pub fn make_command(command: Command) -> Command {
command
Expand Down Expand Up @@ -52,6 +54,12 @@ struct ImageInfo {
height: u32,
}

impl ImageInfo {
fn pixels(&self) -> u64 {
u64::from(self.width) * u64::from(self.height)
}
}

pub fn execute(matches: &ArgMatches) -> Result<()> {
eprintln!("{EXPERIMENTAL_WARNING}");

Expand Down Expand Up @@ -88,6 +96,8 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
if images.len() == 1 { "file" } else { "files" }
);

validate_image_sizes(&images)?;

// Upload image files to objectstore
println!(
"{} Uploading {} image {}",
Expand Down Expand Up @@ -166,6 +176,31 @@ fn collect_image_info(dir: &Path, path: &Path) -> Option<ImageInfo> {
})
}

fn validate_image_sizes(images: &[ImageInfo]) -> Result<()> {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just do this inline on line 161 and return None/error log if the size is larger than 40M?

Copy link
Copy Markdown
Contributor Author

@NicoHinderling NicoHinderling Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The separate validate_image_sizes function is intentional. It collects and reports ALL oversized images in a single error message, so the user sees every violation at once.

Inlining with return None would silently skip oversized images and produce an incomplete snapshot without a hard failure.

We want to fail loudly here rather than let users unknowingly upload partial snapshots.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping the separate validation function intentionally — it collects ALL violations and reports them in a single error message, so the user sees every oversized image at once rather than discovering them one-by-one. The reviewer's refactoring commit (4ec1b22) already refined this function with the cleaner peekable iterator approach.

  • Claude Code

let mut violations = images
.iter()
.filter(|img| img.pixels() > MAX_PIXELS_PER_IMAGE)
.map(|img| {
let path = img.relative_path.display();
let width = img.width;
let height = img.height;
let pixels = img.pixels();

format!(" {path} ({width}x{height} = {pixels} pixels)")
})
.peekable();

if violations.peek().is_some() {
let violation_messages = violations.join("\n");

anyhow::bail!(
"The following images exceed the maximum pixel limit of {MAX_PIXELS_PER_IMAGE}:\n{violation_messages}",
);
}

Ok(())
}
Comment thread
NicoHinderling marked this conversation as resolved.

fn compute_sha256_hash(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
Expand Down Expand Up @@ -280,3 +315,28 @@ fn upload_images(
}
}
}

#[cfg(test)]
mod tests {
use super::*;

fn make_image(width: u32, height: u32) -> ImageInfo {
ImageInfo {
path: PathBuf::from("img.png"),
relative_path: PathBuf::from("img.png"),
width,
height,
}
}

#[test]
fn test_validate_image_sizes_at_limit_passes() {
assert!(validate_image_sizes(&[make_image(8000, 5000)]).is_ok());
}

#[test]
fn test_validate_image_sizes_over_limit_fails() {
let err = validate_image_sizes(&[make_image(8001, 5000)]).unwrap_err();
assert!(err.to_string().contains("exceed the maximum pixel limit"));
}
}