From 5e14a6a3f7f3c37e6852d100fc2b3ec000f9b41d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Tue, 20 Jan 2026 16:08:03 +0100 Subject: [PATCH 1/2] feat: report runtime info Propagate more information about the SDK's runtime including the API version, package version, etc. to get more observability into the SDKs usage. Also removes the generation of the client/client.go file as it doesn't depend on the OpenAPI specs and can be maintained manually. --- codegen/src/client.rs | 7 +++++++ codegen/src/lib.rs | 24 ++++++++++++++++++++++ codegen/src/operation.rs | 9 +++++++++ sdk/build.rs | 22 +++++++++++++++++++++ sdk/src/api_version.rs | 4 ++++ sdk/src/client.rs | 6 ++++++ sdk/src/lib.rs | 1 + sdk/src/resources/checkouts.rs | 18 +++++++++++++++++ sdk/src/resources/customers.rs | 15 ++++++++++++++ sdk/src/resources/members.rs | 15 ++++++++++++++ sdk/src/resources/memberships.rs | 3 +++ sdk/src/resources/merchant.rs | 12 +++++++++++ sdk/src/resources/merchants.rs | 9 +++++++++ sdk/src/resources/payouts.rs | 6 ++++++ sdk/src/resources/readers.rs | 24 ++++++++++++++++++++++ sdk/src/resources/receipts.rs | 3 +++ sdk/src/resources/roles.rs | 15 ++++++++++++++ sdk/src/resources/subaccounts.rs | 15 ++++++++++++++ sdk/src/resources/transactions.rs | 15 ++++++++++++++ sdk/src/version.rs | 33 +++++++++++++++++++++++++++++++ 20 files changed, 256 insertions(+) create mode 100644 sdk/build.rs create mode 100644 sdk/src/api_version.rs diff --git a/codegen/src/client.rs b/codegen/src/client.rs index 298f887..060c30f 100644 --- a/codegen/src/client.rs +++ b/codegen/src/client.rs @@ -66,6 +66,7 @@ pub fn generate_client_file( base_url: String, authorization_token: Option, timeout: std::time::Duration, + runtime_info: Vec<(&'static str, String)>, } impl Client { @@ -79,6 +80,7 @@ pub fn generate_client_file( base_url: "https://api.sumup.com".to_string(), authorization_token, timeout: std::time::Duration::from_secs(10), + runtime_info: crate::version::runtime_info(), } } @@ -130,6 +132,11 @@ pub fn generate_client_file( self.timeout } + /// Returns the runtime headers sent with each request. + pub(crate) fn runtime_headers(&self) -> &[(&'static str, String)] { + &self.runtime_info + } + #(#tag_methods)* } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 4462018..01d6d94 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -74,6 +74,7 @@ impl Generator { )); self.ensure_directories()?; + self.generate_api_version_file()?; self.generate_common_module()?; self.generate_tag_modules()?; self.generate_client_module()?; @@ -181,6 +182,29 @@ impl Generator { generate_client_file(&self.out_path, &self.spec, &self.schemas_by_tag.tag_schemas) } + fn generate_api_version_file(&self) -> Result<(), String> { + let api_version = self.spec.info.version.trim().to_string(); + + if api_version.is_empty() { + return Err("OpenAPI spec version is empty".to_string()); + } + + let mut version_path = self.out_path.clone(); + version_path.push("api_version.rs"); + + let api_version_literal = syn::LitStr::new(&api_version, Span::call_site()); + let tokens = quote! { + /// The version of the SumUp API spec. + pub const API_VERSION: &str = #api_version_literal; + }; + + let contents = format_generated_code(tokens); + std::fs::write(&version_path, &contents) + .map_err(|e| format!("Failed to write api_version.rs: {}", e))?; + + Ok(()) + } + fn generate_mod_rs(&self) -> Result<(), String> { generate_mod_file(&self.out_path, &self.schemas_by_tag) } diff --git a/codegen/src/operation.rs b/codegen/src/operation.rs index cb74da0..d5ce450 100644 --- a/codegen/src/operation.rs +++ b/codegen/src/operation.rs @@ -411,6 +411,9 @@ fn generate_operation_method( if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } #query_additions if let Some(body) = body { request = request.json(&body); @@ -428,6 +431,9 @@ fn generate_operation_method( if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } #query_additions let response = request.send().await?; } @@ -442,6 +448,9 @@ fn generate_operation_method( if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } #query_additions let response = request.send().await?; } diff --git a/sdk/build.rs b/sdk/build.rs new file mode 100644 index 0000000..a449c7e --- /dev/null +++ b/sdk/build.rs @@ -0,0 +1,22 @@ +#![forbid(unsafe_code)] + +use std::process::Command; + +fn main() { + let rustc_version = Command::new("rustc") + .arg("--version") + .output() + .ok() + .and_then(|output| { + if output.status.success() { + String::from_utf8(output.stdout).ok() + } else { + None + } + }) + .map(|version| version.trim().to_string()); + + if let Some(version) = rustc_version { + println!("cargo:rustc-env=SUMUP_RUSTC_VERSION={}", version); + } +} diff --git a/sdk/src/api_version.rs b/sdk/src/api_version.rs new file mode 100644 index 0000000..7d3c72b --- /dev/null +++ b/sdk/src/api_version.rs @@ -0,0 +1,4 @@ +// The contents of this file are generated; do not modify them. + +/// The version of the SumUp API spec. +pub const API_VERSION: &str = "1.0.0"; diff --git a/sdk/src/client.rs b/sdk/src/client.rs index e45bb13..33306cb 100644 --- a/sdk/src/client.rs +++ b/sdk/src/client.rs @@ -9,6 +9,7 @@ pub struct Client { base_url: String, authorization_token: Option, timeout: std::time::Duration, + runtime_info: Vec<(&'static str, String)>, } impl Client { /// Creates a new SumUp API client with the default base URL. @@ -21,6 +22,7 @@ impl Client { base_url: "https://api.sumup.com".to_string(), authorization_token, timeout: std::time::Duration::from_secs(10), + runtime_info: crate::version::runtime_info(), } } /// Overrides the underlying HTTP client used for requests. @@ -63,6 +65,10 @@ impl Client { pub fn timeout(&self) -> std::time::Duration { self.timeout } + /// Returns the runtime headers sent with each request. + pub(crate) fn runtime_headers(&self) -> &[(&'static str, String)] { + &self.runtime_info + } pub fn checkouts(&self) -> crate::resources::checkouts::CheckoutsClient<'_> { crate::resources::checkouts::CheckoutsClient::new(self) } diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index fa3cc64..a4fa520 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -149,6 +149,7 @@ #![forbid(unsafe_code)] +pub mod api_version; pub mod client; pub mod datetime; pub mod error; diff --git a/sdk/src/resources/checkouts.rs b/sdk/src/resources/checkouts.rs index 3be1e04..5bf678d 100644 --- a/sdk/src/resources/checkouts.rs +++ b/sdk/src/resources/checkouts.rs @@ -532,6 +532,9 @@ impl<'a> CheckoutsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.checkout_reference { request = request.query(&[("checkout_reference", value)]); } @@ -578,6 +581,9 @@ impl<'a> CheckoutsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -632,6 +638,9 @@ impl<'a> CheckoutsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -682,6 +691,9 @@ impl<'a> CheckoutsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -728,6 +740,9 @@ impl<'a> CheckoutsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -789,6 +804,9 @@ impl<'a> CheckoutsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.amount { request = request.query(&[("amount", value)]); } diff --git a/sdk/src/resources/customers.rs b/sdk/src/resources/customers.rs index 4fe1455..6fe2884 100644 --- a/sdk/src/resources/customers.rs +++ b/sdk/src/resources/customers.rs @@ -110,6 +110,9 @@ impl<'a> CustomersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -158,6 +161,9 @@ impl<'a> CustomersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -208,6 +214,9 @@ impl<'a> CustomersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -257,6 +266,9 @@ impl<'a> CustomersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -312,6 +324,9 @@ impl<'a> CustomersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { diff --git a/sdk/src/resources/members.rs b/sdk/src/resources/members.rs index 7e6fa42..6683317 100644 --- a/sdk/src/resources/members.rs +++ b/sdk/src/resources/members.rs @@ -190,6 +190,9 @@ impl<'a> MembersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.offset { request = request.query(&[("offset", value)]); } @@ -249,6 +252,9 @@ impl<'a> MembersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -302,6 +308,9 @@ impl<'a> MembersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -340,6 +349,9 @@ impl<'a> MembersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -383,6 +395,9 @@ impl<'a> MembersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { diff --git a/sdk/src/resources/memberships.rs b/sdk/src/resources/memberships.rs index 59b3fe3..1f593e2 100644 --- a/sdk/src/resources/memberships.rs +++ b/sdk/src/resources/memberships.rs @@ -136,6 +136,9 @@ impl<'a> MembershipsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.offset { request = request.query(&[("offset", value)]); } diff --git a/sdk/src/resources/merchant.rs b/sdk/src/resources/merchant.rs index a90fb64..2ff0aff 100644 --- a/sdk/src/resources/merchant.rs +++ b/sdk/src/resources/merchant.rs @@ -462,6 +462,9 @@ impl<'a> MerchantClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.include { request = request.query(&[("include[]", value)]); } @@ -502,6 +505,9 @@ impl<'a> MerchantClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -545,6 +551,9 @@ impl<'a> MerchantClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -582,6 +591,9 @@ impl<'a> MerchantClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { diff --git a/sdk/src/resources/merchants.rs b/sdk/src/resources/merchants.rs index 80b931c..8c5b036 100644 --- a/sdk/src/resources/merchants.rs +++ b/sdk/src/resources/merchants.rs @@ -433,6 +433,9 @@ impl<'a> MerchantsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.version { request = request.query(&[("version", value)]); } @@ -472,6 +475,9 @@ impl<'a> MerchantsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.version { request = request.query(&[("version", value)]); } @@ -519,6 +525,9 @@ impl<'a> MerchantsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.version { request = request.query(&[("version", value)]); } diff --git a/sdk/src/resources/payouts.rs b/sdk/src/resources/payouts.rs index 5f9326d..c049931 100644 --- a/sdk/src/resources/payouts.rs +++ b/sdk/src/resources/payouts.rs @@ -68,6 +68,9 @@ impl<'a> PayoutsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } request = request.query(&[("start_date", ¶ms.start_date)]); request = request.query(&[("end_date", ¶ms.end_date)]); if let Some(ref value) = params.format { @@ -118,6 +121,9 @@ impl<'a> PayoutsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } request = request.query(&[("start_date", ¶ms.start_date)]); request = request.query(&[("end_date", ¶ms.end_date)]); if let Some(ref value) = params.format { diff --git a/sdk/src/resources/readers.rs b/sdk/src/resources/readers.rs index 62d48f1..361b42e 100644 --- a/sdk/src/resources/readers.rs +++ b/sdk/src/resources/readers.rs @@ -437,6 +437,9 @@ impl<'a> ReadersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -471,6 +474,9 @@ impl<'a> ReadersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -522,6 +528,9 @@ impl<'a> ReadersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -560,6 +569,9 @@ impl<'a> ReadersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -603,6 +615,9 @@ impl<'a> ReadersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -662,6 +677,9 @@ impl<'a> ReadersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -753,6 +771,9 @@ impl<'a> ReadersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -839,6 +860,9 @@ impl<'a> ReadersClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { diff --git a/sdk/src/resources/receipts.rs b/sdk/src/resources/receipts.rs index ffcf7a7..d0bbf21 100644 --- a/sdk/src/resources/receipts.rs +++ b/sdk/src/resources/receipts.rs @@ -219,6 +219,9 @@ impl<'a> ReceiptsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } request = request.query(&[("mid", ¶ms.mid)]); if let Some(ref value) = params.tx_event_id { request = request.query(&[("tx_event_id", value)]); diff --git a/sdk/src/resources/roles.rs b/sdk/src/resources/roles.rs index 0ddf5c0..1a4bf8f 100644 --- a/sdk/src/resources/roles.rs +++ b/sdk/src/resources/roles.rs @@ -106,6 +106,9 @@ impl<'a> RolesClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -144,6 +147,9 @@ impl<'a> RolesClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -191,6 +197,9 @@ impl<'a> RolesClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -235,6 +244,9 @@ impl<'a> RolesClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -278,6 +290,9 @@ impl<'a> RolesClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { diff --git a/sdk/src/resources/subaccounts.rs b/sdk/src/resources/subaccounts.rs index b4c6b0f..e974e1f 100644 --- a/sdk/src/resources/subaccounts.rs +++ b/sdk/src/resources/subaccounts.rs @@ -127,6 +127,9 @@ impl<'a> SubaccountsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.query { request = request.query(&[("query", value)]); } @@ -166,6 +169,9 @@ impl<'a> SubaccountsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -204,6 +210,9 @@ impl<'a> SubaccountsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -236,6 +245,9 @@ impl<'a> SubaccountsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { @@ -270,6 +282,9 @@ impl<'a> SubaccountsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } let response = request.send().await?; let status = response.status(); match status { diff --git a/sdk/src/resources/transactions.rs b/sdk/src/resources/transactions.rs index 28ade14..a540196 100644 --- a/sdk/src/resources/transactions.rs +++ b/sdk/src/resources/transactions.rs @@ -536,6 +536,9 @@ impl<'a> TransactionsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(body) = body { request = request.json(&body); } @@ -582,6 +585,9 @@ impl<'a> TransactionsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.id { request = request.query(&[("id", value)]); } @@ -635,6 +641,9 @@ impl<'a> TransactionsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.transaction_code { request = request.query(&[("transaction_code", value)]); } @@ -716,6 +725,9 @@ impl<'a> TransactionsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.id { request = request.query(&[("id", value)]); } @@ -777,6 +789,9 @@ impl<'a> TransactionsClient<'a> { if let Some(token) = self.client.authorization_token() { request = request.header("Authorization", format!("Bearer {}", token)); } + for (header_name, header_value) in self.client.runtime_headers() { + request = request.header(*header_name, header_value); + } if let Some(ref value) = params.transaction_code { request = request.query(&[("transaction_code", value)]); } diff --git a/sdk/src/version.rs b/sdk/src/version.rs index d291a79..d4aa498 100644 --- a/sdk/src/version.rs +++ b/sdk/src/version.rs @@ -5,3 +5,36 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub fn user_agent() -> String { format!("sumup-rs/v{}", VERSION) } + +/// Returns the runtime headers for SDK requests +pub fn runtime_info() -> Vec<(&'static str, String)> { + vec![ + ( + "X-Sumup-Api-Version", + crate::api_version::API_VERSION.to_string(), + ), + ("X-SumUp-Lang", "rust".to_string()), + ("X-SumUp-Package-Version", VERSION.to_string()), + ("X-SumUp-Os", std::env::consts::OS.to_string()), + ("X-SumUp-Arch", runtime_arch()), + ("X-SumUp-Runtime", "rust".to_string()), + ("X-SumUp-Runtime-Version", rustc_version()), + ] +} + +fn rustc_version() -> String { + option_env!("SUMUP_RUSTC_VERSION") + .unwrap_or("unknown") + .to_string() +} + +fn runtime_arch() -> String { + match std::env::consts::ARCH { + "x86_64" => "x86_64", + "x86" | "i686" => "x86", + "aarch64" => "arm64", + "arm" => "arm", + other => other, + } + .to_string() +} From 3ea161837198015dcec20072ee0a01d89b326cf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Tue, 20 Jan 2026 18:17:22 +0100 Subject: [PATCH 2/2] feat: test the runtime info --- sdk/src/version.rs | 38 ++++++++++++++++++++++++++++++++++++++ sdk/tests/client.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/sdk/src/version.rs b/sdk/src/version.rs index d4aa498..5542eb5 100644 --- a/sdk/src/version.rs +++ b/sdk/src/version.rs @@ -38,3 +38,41 @@ fn runtime_arch() -> String { } .to_string() } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn runtime_info_includes_expected_headers() { + let headers = runtime_info(); + + let names: Vec<&str> = headers.iter().map(|(name, _)| *name).collect(); + assert!(names.contains(&"X-Sumup-Api-Version")); + assert!(names.contains(&"X-SumUp-Lang")); + assert!(names.contains(&"X-SumUp-Package-Version")); + assert!(names.contains(&"X-SumUp-Os")); + assert!(names.contains(&"X-SumUp-Arch")); + assert!(names.contains(&"X-SumUp-Runtime")); + assert!(names.contains(&"X-SumUp-Runtime-Version")); + } + + #[test] + fn runtime_info_uses_normalized_arch_value() { + let expected = match std::env::consts::ARCH { + "x86_64" => "x86_64", + "x86" | "i686" => "x86", + "aarch64" => "arm64", + "arm" => "arm", + other => other, + }; + + let arch = runtime_info() + .into_iter() + .find(|(name, _)| *name == "X-SumUp-Arch") + .map(|(_, value)| value) + .expect("runtime info should include X-SumUp-Arch"); + + assert_eq!(arch, expected); + } +} diff --git a/sdk/tests/client.rs b/sdk/tests/client.rs index cb061da..8e7e094 100644 --- a/sdk/tests/client.rs +++ b/sdk/tests/client.rs @@ -105,6 +105,36 @@ async fn client_requests_include_user_agent_and_custom_authorization() { .expect("request should succeed"); } +#[tokio::test] +#[serial] +async fn client_requests_include_runtime_headers() { + let server = MockServer::start().await; + let _guard = EnvVarGuard::set("SUMUP_API_KEY", "env-token"); + let expected_user_agent = version::user_agent(); + + let mut mock = Mock::given(method("GET")) + .and(path("/v0.1/checkouts")) + .and(header("User-Agent", expected_user_agent.as_str())); + + for (header_name, header_value) in version::runtime_info() { + mock = mock.and(header(header_name, header_value.as_str())); + } + + let _mock = mock + .respond_with(ResponseTemplate::new(200).set_body_json(json!([]))) + .expect(1) + .mount_as_scoped(&server) + .await; + + let client = Client::new().with_base_url(server.uri()); + + client + .checkouts() + .list(sumup::resources::checkouts::ListParams::default()) + .await + .expect("request should succeed"); +} + #[test] #[serial] fn client_returns_none_when_authorization_missing() {