From 815f8058415b30c007165881dbffcb47f5733bf8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 4 Nov 2025 11:49:36 +0000 Subject: [PATCH 1/2] Checkpoint before follow-up message Co-authored-by: daniel.szoke --- src/api/mod.rs | 3 +++ src/commands/api.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/commands/api.rs diff --git a/src/api/mod.rs b/src/api/mod.rs index 93c0e0f2b0..7776e6a9db 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -102,6 +102,7 @@ pub enum Method { Post, Put, Delete, + Patch, } impl fmt::Display for Method { @@ -111,6 +112,7 @@ impl fmt::Display for Method { Method::Post => write!(f, "POST"), Method::Put => write!(f, "PUT"), Method::Delete => write!(f, "DELETE"), + Method::Patch => write!(f, "PATCH"), } } } @@ -1747,6 +1749,7 @@ impl ApiRequest { Method::Post => handle.custom_request("POST")?, Method::Put => handle.custom_request("PUT")?, Method::Delete => handle.custom_request("DELETE")?, + Method::Patch => handle.custom_request("PATCH")?, } handle.url(url)?; diff --git a/src/commands/api.rs b/src/commands/api.rs new file mode 100644 index 0000000000..786c3ba432 --- /dev/null +++ b/src/commands/api.rs @@ -0,0 +1,54 @@ +use anyhow::Result; +use clap::{Arg, ArgAction, ArgMatches, Command, ValueHint}; + +use crate::api::{Api, Method}; + +pub fn make_command(command: Command) -> Command { + command + .about("Make a raw API request to the Sentry API.") + .arg( + Arg::new("endpoint") + .value_name("ENDPOINT") + .required(true) + .value_hint(ValueHint::Url) + .help( + "The API endpoint to request (e.g., 'organizations/' or '/projects/my-org/my-project/releases/').{n}\ + The endpoint will be prefixed with '/api/0/' automatically.", + ), + ) + .arg( + Arg::new("method") + .short('m') + .long("method") + .value_name("METHOD") + .value_parser(["GET", "POST", "PUT", "DELETE", "PATCH"]) + .default_value("GET") + .action(ArgAction::Set) + .help("The HTTP method to use for the request."), + ) +} + +pub fn execute(matches: &ArgMatches) -> Result<()> { + let endpoint = matches.get_one::("endpoint").unwrap(); + let method_str = matches.get_one::("method").unwrap(); + + let method = match method_str.as_str() { + "GET" => Method::Get, + "POST" => Method::Post, + "PUT" => Method::Put, + "DELETE" => Method::Delete, + "PATCH" => Method::Patch, + _ => unreachable!("Invalid method value"), + }; + + let api = Api::current(); + let resp = api.request(method, endpoint, None)?.send()?; + + // Print the response body as-is to stdout + if let Some(body) = resp.body { + let body_str = String::from_utf8_lossy(&body); + println!("{}", body_str); + } + + Ok(()) +} From 7eb50422b4e190f563ec032afa7baaf6b6280309 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 4 Nov 2025 11:57:34 +0000 Subject: [PATCH 2/2] feat: Add raw API request command This commit introduces a new `api` command that allows users to make raw HTTP requests to the Sentry API. It removes the `Patch` method from the `Method` enum and updates the `api` command to use the authenticated API. Co-authored-by: daniel.szoke --- src/api/mod.rs | 14 +++++++---- src/commands/api.rs | 19 ++++++++------- src/commands/mod.rs | 2 ++ .../api/api-get-with-leading-slash.trycmd | 17 +++++++++++++ tests/integration/_cases/api/api-get.trycmd | 17 +++++++++++++ tests/integration/_cases/api/api-help.trycmd | 24 +++++++++++++++++++ tests/integration/_cases/api/api-post.trycmd | 14 +++++++++++ tests/integration/_cases/help/help.trycmd | 1 + .../_responses/api/get-organizations.json | 12 ++++++++++ .../_responses/api/post-releases.json | 9 +++++++ 10 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 tests/integration/_cases/api/api-get-with-leading-slash.trycmd create mode 100644 tests/integration/_cases/api/api-get.trycmd create mode 100644 tests/integration/_cases/api/api-help.trycmd create mode 100644 tests/integration/_cases/api/api-post.trycmd create mode 100644 tests/integration/_responses/api/get-organizations.json create mode 100644 tests/integration/_responses/api/post-releases.json diff --git a/src/api/mod.rs b/src/api/mod.rs index 7776e6a9db..f8a69451b9 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -102,7 +102,6 @@ pub enum Method { Post, Put, Delete, - Patch, } impl fmt::Display for Method { @@ -112,7 +111,6 @@ impl fmt::Display for Method { Method::Post => write!(f, "POST"), Method::Put => write!(f, "PUT"), Method::Delete => write!(f, "DELETE"), - Method::Patch => write!(f, "PATCH"), } } } @@ -471,7 +469,7 @@ impl<'a> AuthenticatedApi<'a> { } /// Convenience method to call self.api.request. - fn request(&self, method: Method, url: &str) -> ApiResult { + pub fn request(&self, method: Method, url: &str) -> ApiResult { self.api.request(method, url, None) } @@ -1749,7 +1747,6 @@ impl ApiRequest { Method::Post => handle.custom_request("POST")?, Method::Put => handle.custom_request("PUT")?, Method::Delete => handle.custom_request("DELETE")?, - Method::Patch => handle.custom_request("PATCH")?, } handle.url(url)?; @@ -1902,6 +1899,15 @@ impl ApiRequest { } } +impl fmt::Display for ApiResponse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.body { + Some(ref body) => write!(f, "{}", String::from_utf8_lossy(body)), + None => Ok(()), + } + } +} + impl ApiResponse { /// Returns the status code of the response pub fn status(&self) -> u32 { diff --git a/src/commands/api.rs b/src/commands/api.rs index 786c3ba432..b0b3175ffc 100644 --- a/src/commands/api.rs +++ b/src/commands/api.rs @@ -21,7 +21,7 @@ pub fn make_command(command: Command) -> Command { .short('m') .long("method") .value_name("METHOD") - .value_parser(["GET", "POST", "PUT", "DELETE", "PATCH"]) + .value_parser(["GET", "POST", "PUT", "DELETE"]) .default_value("GET") .action(ArgAction::Set) .help("The HTTP method to use for the request."), @@ -29,26 +29,27 @@ pub fn make_command(command: Command) -> Command { } pub fn execute(matches: &ArgMatches) -> Result<()> { - let endpoint = matches.get_one::("endpoint").unwrap(); - let method_str = matches.get_one::("method").unwrap(); + let endpoint = matches + .get_one::("endpoint") + .expect("endpoint is required"); + let method_str = matches + .get_one::("method") + .expect("method has a default value"); let method = match method_str.as_str() { "GET" => Method::Get, "POST" => Method::Post, "PUT" => Method::Put, "DELETE" => Method::Delete, - "PATCH" => Method::Patch, _ => unreachable!("Invalid method value"), }; let api = Api::current(); - let resp = api.request(method, endpoint, None)?.send()?; + let authenticated_api = api.authenticated()?; + let resp = authenticated_api.request(method, endpoint)?.send()?; // Print the response body as-is to stdout - if let Some(body) = resp.body { - let body_str = String::from_utf8_lossy(&body); - println!("{}", body_str); - } + println!("{resp}"); Ok(()) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index d4f83c6fe7..001a073443 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -19,6 +19,7 @@ use crate::utils::system::{load_dotenv, print_error, set_panic_hook, QuietExit}; use crate::utils::update::run_sentrycli_update_nagger; use crate::utils::value_parsers::auth_token_parser; +mod api; mod bash_hook; mod build; mod dart_symbol_map; @@ -51,6 +52,7 @@ mod upload_proguard; macro_rules! each_subcommand { ($mac:ident) => { + $mac!(api); $mac!(bash_hook); $mac!(build); $mac!(debug_files); diff --git a/tests/integration/_cases/api/api-get-with-leading-slash.trycmd b/tests/integration/_cases/api/api-get-with-leading-slash.trycmd new file mode 100644 index 0000000000..c9ff14a551 --- /dev/null +++ b/tests/integration/_cases/api/api-get-with-leading-slash.trycmd @@ -0,0 +1,17 @@ +``` +$ sentry-cli api /organizations/ +? success +[ + { + "id": "1", + "slug": "sentry", + "name": "Sentry", + "dateCreated": "2014-12-15T12:00:00.000Z", + "isEarlyAdopter": true, + "require2FA": false, + "requireEmailVerification": false, + "features": [] + } +] + +``` diff --git a/tests/integration/_cases/api/api-get.trycmd b/tests/integration/_cases/api/api-get.trycmd new file mode 100644 index 0000000000..de0279f7cd --- /dev/null +++ b/tests/integration/_cases/api/api-get.trycmd @@ -0,0 +1,17 @@ +``` +$ sentry-cli api organizations/ +? success +[ + { + "id": "1", + "slug": "sentry", + "name": "Sentry", + "dateCreated": "2014-12-15T12:00:00.000Z", + "isEarlyAdopter": true, + "require2FA": false, + "requireEmailVerification": false, + "features": [] + } +] + +``` diff --git a/tests/integration/_cases/api/api-help.trycmd b/tests/integration/_cases/api/api-help.trycmd new file mode 100644 index 0000000000..7ad1241761 --- /dev/null +++ b/tests/integration/_cases/api/api-help.trycmd @@ -0,0 +1,24 @@ +``` +$ sentry-cli api --help +? success +Make a raw API request to the Sentry API. + +Usage: sentry-cli[EXE] api [OPTIONS] + +Arguments: + + The API endpoint to request (e.g., 'organizations/' or + '/projects/my-org/my-project/releases/'). + The endpoint will be prefixed with '/api/0/' automatically. + +Options: + -m, --method + The HTTP method to use for the request. + + [default: GET] + [possible values: GET, POST, PUT, DELETE] + + -h, --help + Print help (see a summary with '-h') + +``` diff --git a/tests/integration/_cases/api/api-post.trycmd b/tests/integration/_cases/api/api-post.trycmd new file mode 100644 index 0000000000..1fb25d0ce4 --- /dev/null +++ b/tests/integration/_cases/api/api-post.trycmd @@ -0,0 +1,14 @@ +``` +$ sentry-cli api --method POST organizations/wat-org/releases/ +? success +{ + "version": "1.0.0", + "url": null, + "dateCreated": "2024-01-15T12:00:00.000Z", + "dateReleased": null, + "lastEvent": null, + "newGroups": 0, + "projects": [] +} + +``` diff --git a/tests/integration/_cases/help/help.trycmd b/tests/integration/_cases/help/help.trycmd index a7fcba86c7..90d2a0ebaf 100644 --- a/tests/integration/_cases/help/help.trycmd +++ b/tests/integration/_cases/help/help.trycmd @@ -11,6 +11,7 @@ Usage: sentry-cli[EXE] [OPTIONS] Commands: completions Generate completions for the specified shell. + api Make a raw API request to the Sentry API. debug-files Locate, analyze or upload debug information files. [aliases: dif] deploys Manage deployments for Sentry releases. events Manage events on Sentry. diff --git a/tests/integration/_responses/api/get-organizations.json b/tests/integration/_responses/api/get-organizations.json new file mode 100644 index 0000000000..5acdd118a0 --- /dev/null +++ b/tests/integration/_responses/api/get-organizations.json @@ -0,0 +1,12 @@ +[ + { + "id": "1", + "slug": "sentry", + "name": "Sentry", + "dateCreated": "2014-12-15T12:00:00.000Z", + "isEarlyAdopter": true, + "require2FA": false, + "requireEmailVerification": false, + "features": [] + } +] diff --git a/tests/integration/_responses/api/post-releases.json b/tests/integration/_responses/api/post-releases.json new file mode 100644 index 0000000000..b80b6177a0 --- /dev/null +++ b/tests/integration/_responses/api/post-releases.json @@ -0,0 +1,9 @@ +{ + "version": "1.0.0", + "url": null, + "dateCreated": "2024-01-15T12:00:00.000Z", + "dateReleased": null, + "lastEvent": null, + "newGroups": 0, + "projects": [] +}