Skip to content

Commit be15335

Browse files
romtsnclaude
andauthored
feat(code-mappings): Wire up API integration for code-mappings upload (#3209)
_#skip-changelog_ Connect the `code-mappings upload` command to the bulk code mappings API endpoint (`POST /api/0/organizations/{org}/code-mappings/bulk/`). Adds: - `bulk_upload_code_mappings()` method on `AuthenticatedApi` - Request/response data types in `src/api/data_types/code_mappings.rs` - Summary table and error reporting in the command output - Happy-path integration test with mock endpoint Stack: #3207#3208 → **#3209** → #3210 Backend PRs: getsentry/sentry#109783, getsentry/sentry#109785, getsentry/sentry#109786 Closes getsentry/sentry-android-gradle-plugin#1079 --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
1 parent bee85dc commit be15335

File tree

9 files changed

+149
-12
lines changed

9 files changed

+149
-12
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! Data types for the bulk code mappings API.
2+
3+
use serde::{Deserialize, Serialize};
4+
5+
#[derive(Debug, Serialize)]
6+
#[serde(rename_all = "camelCase")]
7+
pub struct BulkCodeMappingsRequest<'a> {
8+
pub project: &'a str,
9+
pub repository: &'a str,
10+
pub default_branch: &'a str,
11+
pub mappings: &'a [BulkCodeMapping],
12+
}
13+
14+
#[derive(Debug, Deserialize, Serialize)]
15+
#[serde(rename_all = "camelCase")]
16+
pub struct BulkCodeMapping {
17+
pub stack_root: String,
18+
pub source_root: String,
19+
}
20+
21+
#[derive(Debug, Deserialize)]
22+
pub struct BulkCodeMappingsResponse {
23+
pub created: u64,
24+
pub updated: u64,
25+
pub errors: u64,
26+
pub mappings: Vec<BulkCodeMappingResult>,
27+
}
28+
29+
#[derive(Debug, Deserialize)]
30+
#[serde(rename_all = "camelCase")]
31+
pub struct BulkCodeMappingResult {
32+
pub stack_root: String,
33+
pub source_root: String,
34+
pub status: String,
35+
#[serde(default)]
36+
pub detail: Option<String>,
37+
}

src/api/data_types/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
//! Data types used in the api module
22
33
mod chunking;
4+
mod code_mappings;
45
mod deploy;
56
mod snapshots;
67

78
pub use self::chunking::*;
9+
pub use self::code_mappings::*;
810
pub use self::deploy::*;
911
pub use self::snapshots::*;

src/api/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,17 @@ impl AuthenticatedApi<'_> {
978978
Ok(rv)
979979
}
980980

981+
/// Bulk uploads code mappings for an organization.
982+
pub fn bulk_upload_code_mappings(
983+
&self,
984+
org: &str,
985+
body: &BulkCodeMappingsRequest,
986+
) -> ApiResult<BulkCodeMappingsResponse> {
987+
let path = format!("/organizations/{}/code-mappings/bulk/", PathArg(org));
988+
self.post(&path, body)?
989+
.convert_rnf(ApiErrorKind::OrganizationNotFound)
990+
}
991+
981992
/// Creates a preprod snapshot artifact for the given project.
982993
pub fn create_preprod_snapshot<S: Serialize>(
983994
&self,

src/commands/code_mappings/upload.rs

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,12 @@ use std::fs;
33
use anyhow::{bail, Context as _, Result};
44
use clap::{Arg, ArgMatches, Command};
55
use log::debug;
6-
use serde::Deserialize;
76

7+
use crate::api::{Api, BulkCodeMapping, BulkCodeMappingResult, BulkCodeMappingsRequest};
88
use crate::config::Config;
9+
use crate::utils::formatting::Table;
910
use crate::utils::vcs;
1011

11-
#[derive(Debug, Deserialize)]
12-
#[serde(rename_all = "camelCase")]
13-
struct CodeMapping {
14-
stack_root: String,
15-
source_root: String,
16-
}
17-
1812
pub fn make_command(command: Command) -> Command {
1913
command
2014
.about("Upload code mappings for a project from a JSON file. Each mapping pairs a stack trace root (e.g. com/example/module) with the corresponding source path in your repository (e.g. modules/module/src/main/java/com/example/module).")
@@ -39,12 +33,16 @@ pub fn make_command(command: Command) -> Command {
3933
}
4034

4135
pub fn execute(matches: &ArgMatches) -> Result<()> {
36+
let config = Config::current();
37+
let org = config.get_org(matches)?;
38+
let project = config.get_project(matches)?;
39+
4240
let path = matches
4341
.get_one::<String>("path")
4442
.expect("path is a required argument");
4543
let data = fs::read(path).with_context(|| format!("Failed to read mappings file '{path}'"))?;
4644

47-
let mappings: Vec<CodeMapping> =
45+
let mappings: Vec<BulkCodeMapping> =
4846
serde_json::from_slice(&data).context("Failed to parse mappings JSON")?;
4947

5048
if mappings.is_empty() {
@@ -73,9 +71,33 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
7371
git_repo.as_ref(),
7472
)?;
7573

76-
println!("Found {} code mapping(s) in {path}", mappings.len());
77-
println!("Repository: {repo_name}");
78-
println!("Default branch: {default_branch}");
74+
let mapping_count = mappings.len();
75+
let request = BulkCodeMappingsRequest {
76+
project: &project,
77+
repository: &repo_name,
78+
default_branch: &default_branch,
79+
mappings: &mappings,
80+
};
81+
82+
println!("Uploading {mapping_count} code mapping(s)...");
83+
84+
let api = Api::current();
85+
let response = api
86+
.authenticated()?
87+
.bulk_upload_code_mappings(&org, &request)?;
88+
89+
print_results_table(response.mappings);
90+
println!(
91+
"Created: {}, Updated: {}, Errors: {}",
92+
response.created, response.updated, response.errors
93+
);
94+
95+
if response.errors > 0 {
96+
bail!(
97+
"{} mapping(s) failed to upload. See errors above.",
98+
response.errors
99+
);
100+
}
79101

80102
Ok(())
81103
}
@@ -174,6 +196,30 @@ fn infer_default_branch(git_repo: Option<&git2::Repository>, remote_name: Option
174196
})
175197
}
176198

199+
fn print_results_table(mappings: Vec<BulkCodeMappingResult>) {
200+
let mut table = Table::new();
201+
table
202+
.title_row()
203+
.add("Stack Root")
204+
.add("Source Root")
205+
.add("Status");
206+
207+
for result in mappings {
208+
let status = match result.detail {
209+
Some(detail) if result.status == "error" => format!("error: {detail}"),
210+
_ => result.status,
211+
};
212+
table
213+
.add_row()
214+
.add(&result.stack_root)
215+
.add(&result.source_root)
216+
.add(&status);
217+
}
218+
219+
table.print();
220+
println!();
221+
}
222+
177223
#[cfg(test)]
178224
mod tests {
179225
use super::*;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
```
2+
$ sentry-cli code-mappings upload tests/integration/_fixtures/code_mappings/mappings.json --org wat-org --project wat-project --repo owner/repo --default-branch main
3+
? success
4+
Uploading 2 code mapping(s)...
5+
+------------------+---------------------------------------------+---------+
6+
| Stack Root | Source Root | Status |
7+
+------------------+---------------------------------------------+---------+
8+
| com/example/core | modules/core/src/main/java/com/example/core | created |
9+
| com/example/maps | modules/maps/src/main/java/com/example/maps | created |
10+
+------------------+---------------------------------------------+---------+
11+
12+
Created: 2, Updated: 0, Errors: 0
13+
14+
```
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
{"stackRoot": "com/example/core", "sourceRoot": "modules/core/src/main/java/com/example/core"},
3+
{"stackRoot": "com/example/maps", "sourceRoot": "modules/maps/src/main/java/com/example/maps"}
4+
]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"created": 2,
3+
"updated": 0,
4+
"errors": 0,
5+
"mappings": [
6+
{"stackRoot": "com/example/core", "sourceRoot": "modules/core/src/main/java/com/example/core", "status": "created"},
7+
{"stackRoot": "com/example/maps", "sourceRoot": "modules/maps/src/main/java/com/example/maps", "status": "created"}
8+
]
9+
}

tests/integration/code_mappings/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::integration::TestManager;
22

3+
mod upload;
4+
35
#[test]
46
fn command_code_mappings_help() {
57
TestManager::new().register_trycmd_test("code_mappings/code-mappings-help.trycmd");
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use crate::integration::{MockEndpointBuilder, TestManager};
2+
3+
#[test]
4+
fn command_code_mappings_upload() {
5+
TestManager::new()
6+
.mock_endpoint(
7+
MockEndpointBuilder::new("POST", "/api/0/organizations/wat-org/code-mappings/bulk/")
8+
.with_response_file("code_mappings/post-bulk.json"),
9+
)
10+
.register_trycmd_test("code_mappings/code-mappings-upload.trycmd")
11+
.with_default_token();
12+
}

0 commit comments

Comments
 (0)