Skip to content

feat(snapshots): Upload image metadata json data as part of snapshots job#3163

Merged
rbro112 merged 11 commits intomasterfrom
ryan/upload_image_metadata_json_data_as_part_of_snapshots_job
Mar 4, 2026
Merged

feat(snapshots): Upload image metadata json data as part of snapshots job#3163
rbro112 merged 11 commits intomasterfrom
ryan/upload_image_metadata_json_data_as_part_of_snapshots_job

Conversation

@rbro112
Copy link
Member

@rbro112 rbro112 commented Feb 27, 2026

Description

First-class supported platforms (web library, iOS/Android in future) will be outputting a "sidecar" json file with each image containing custom metadata set by the framework and provided by the user. This will contain things like the "display name" among other items.

This updates our snapshots command to process these sidecar files if present and upload them as part of the image manifest data in the POST body.

@rbro112 rbro112 requested review from a team and szokeasaurusrex as code owners February 27, 2026 19:43
Copy link
Member Author

rbro112 commented Feb 27, 2026

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 27, 2026

Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Generated by 🚫 dangerJS against 23fd272

@szokeasaurusrex
Copy link
Member

Hey @rbro112, can you please resolve or mark as irrelevant all of the Seer code review comments before I review in more depth?

@rbro112 rbro112 changed the title Upload image metadata json data as part of snapshots job feat(snapshots): Upload image metadata json data as part of snapshots job Mar 2, 2026
@rbro112
Copy link
Member Author

rbro112 commented Mar 2, 2026

@szokeasaurusrex this is ready for review. I'm ignoring the Danger comment as the last item is Added experimental sentry-cli build snapshots command, so there's no need to explicitly mention this change assuming it makes it in before the next release

const RESERVED_KEYS: &[&str] = &["image_file_name", "width", "height"];

impl Serialize for ImageMetadata {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
Copy link
Member Author

Choose a reason for hiding this comment

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

My rust knowledge isn't robust enough, but basically this is what I believe should be equivalent of the typescript "splat". Essentially what we want to do here is always override "image_file_name", "width", "height" if the user has provided those values. These are first-class fields we will set (will be documented) and users cannot be allowed to override those.

In typescript, this would be how I'd override:

const mergedMetadata = {
   ...extras, // User's fields
   width: ourWidth,
   height: ourHeight
   image_file_name: ourImageFileName,
}

It seems rust doesn't have an "override"/"splat" functionality like this, so the serializer/reserved keys is our workaround. Open to more elegant solutions here nonetheless.

Copy link
Member

Choose a reason for hiding this comment

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

m: @rbro112 I made a suggestion in #3178; basically, I would just have the struct only contain the HashMap and control field precedence by implementing a custom constructor.

@rbro112 rbro112 enabled auto-merge (squash) March 3, 2026 17:13
szokeasaurusrex added a commit that referenced this pull request Mar 3, 2026
szokeasaurusrex added a commit that referenced this pull request Mar 3, 2026
szokeasaurusrex added a commit that referenced this pull request Mar 3, 2026
@rbro112 rbro112 disabled auto-merge March 3, 2026 17:41
@rbro112 rbro112 force-pushed the ryan/upload_image_metadata_json_data_as_part_of_snapshots_job branch from 3a87a25 to 93bd92d Compare March 3, 2026 18:05
Copy link
Member

@szokeasaurusrex szokeasaurusrex left a comment

Choose a reason for hiding this comment

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

Looks good to me, I left some minor optional suggestions, please address prior to merge!

Comment on lines +196 to +224
fn read_sidecar_metadata(image_path: &Path) -> HashMap<String, serde_json::Value> {
let sidecar_path = image_path.with_extension("json");
if !sidecar_path.is_file() {
return HashMap::new();
}

debug!("Reading sidecar metadata: {}", sidecar_path.display());
let contents = match fs::read_to_string(&sidecar_path) {
Ok(c) => c,
Err(err) => {
warn!(
"Failed to read sidecar file {}: {err}",
sidecar_path.display()
);
return HashMap::new();
}
};

match serde_json::from_str(&contents) {
Ok(map) => map,
Err(err) => {
warn!(
"Failed to parse sidecar file {}: {err}",
sidecar_path.display()
);
HashMap::new()
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

l: A few minor suggestions in one.

Suggested change
fn read_sidecar_metadata(image_path: &Path) -> HashMap<String, serde_json::Value> {
let sidecar_path = image_path.with_extension("json");
if !sidecar_path.is_file() {
return HashMap::new();
}
debug!("Reading sidecar metadata: {}", sidecar_path.display());
let contents = match fs::read_to_string(&sidecar_path) {
Ok(c) => c,
Err(err) => {
warn!(
"Failed to read sidecar file {}: {err}",
sidecar_path.display()
);
return HashMap::new();
}
};
match serde_json::from_str(&contents) {
Ok(map) => map,
Err(err) => {
warn!(
"Failed to parse sidecar file {}: {err}",
sidecar_path.display()
);
HashMap::new()
}
}
}
fn read_sidecar_metadata(image_path: &Path) -> Result<HashMap<String, Value>> {
let sidecar_path = image_path.with_extension("json");
if !sidecar_path.is_file() {
return Ok(HashMap::new());
}
debug!("Reading sidecar metadata: {}", sidecar_path.display());
let sidecar_file = File::open(&sidecar_path)
.with_context(|| format!("Failed to open sidecar file {}", sidecar_path.display()))?;
serde_json::from_reader(BufReader::new(sidecar_file)).with_context(|| {
format!(
"Failed to read sidecar file {} as JSON",
sidecar_path.display()
)
})
}

The notable changes in order of importance:

  1. Use serde_json::from_reader to avoid reading the entire sidecar file to a string (should reduce memory usage for large sidecar files).
  2. I'd return the error and handle it in the calling code; this allows you to use the ? operator in the function, and factor out common error handling code (see associated comment at the call site)
  3. Style change: import serde_json::Value so you can reference it as Value directly, this is the style recommended for types in Rust's style guide.

You need to add the following imports at the beginning of the file, as well.

// std imports
use std::fs::{self, File};
use std::io::BufReader;

// crate imports
use serde_json::Value;

.to_string_lossy()
.into_owned();

let extra = read_sidecar_metadata(&image.path);
Copy link
Member

Choose a reason for hiding this comment

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

This comment is associated with item (2) in my previous comment

Suggested change
let extra = read_sidecar_metadata(&image.path);
let extra = read_sidecar_metadata(&image.path).unwrap_or_else(|err| {
warn!("Error reading sidecar metadata, ignoring it instead: {err:#}");
HashMap::new()
});```

@rbro112 rbro112 force-pushed the ryan/upload_image_metadata_json_data_as_part_of_snapshots_job branch from 93bd92d to 36a8cca Compare March 4, 2026 17:59
@rbro112 rbro112 merged commit 1a0e6ef into master Mar 4, 2026
25 checks passed
@rbro112 rbro112 deleted the ryan/upload_image_metadata_json_data_as_part_of_snapshots_job branch March 4, 2026 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants