use secrecy::{ExposeSecret, SecretString};
use serde::Serialize;
#[derive(Debug, Serialize)]
pub(crate) struct SdkInvocation<P: Serialize> {
pub invocation: InvocationPayload<P>,
}
impl<P: Serialize> SdkInvocation<P> {
pub fn new(client_id: u64, method: &'static str, parameters: P) -> Self {
Self {
invocation: InvocationPayload {
client_id,
parameters: MethodCall {
name: method,
parameters,
},
},
}
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct InvocationPayload<P: Serialize> {
pub client_id: u64,
pub parameters: MethodCall<P>,
}
#[derive(Debug, Serialize)]
pub(crate) struct MethodCall<P: Serialize> {
pub name: &'static str,
pub parameters: P,
}
fn serialize_secret<S>(secret: &SecretString, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(secret.expose_secret())
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct InitClientParams {
#[serde(serialize_with = "serialize_secret")]
pub service_account_token: SecretString,
pub programming_language: String,
pub sdk_version: String,
pub integration_name: String,
pub integration_version: String,
pub request_library_name: String,
pub request_library_version: String,
pub os: String,
pub os_version: String,
pub architecture: String,
}
#[derive(Debug, Serialize)]
pub(crate) struct ResolveSecretParams {
pub secret_reference: String,
}
pub(crate) mod methods {
pub const SECRETS_RESOLVE: &str = "SecretsResolve";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_invocation_serialization() {
let params = ResolveSecretParams {
secret_reference: "op://vault/item/field".to_string(),
};
let invocation = SdkInvocation::new(123, methods::SECRETS_RESOLVE, params);
let json = invocation.to_json().unwrap();
assert!(json.contains("\"invocation\""));
assert!(json.contains("\"clientId\":123")); assert!(json.contains("\"parameters\""));
assert!(json.contains("\"name\":\"SecretsResolve\""));
assert!(json.contains("\"secret_reference\":\"op://vault/item/field\""));
}
#[test]
fn test_invocation_structure() {
let params = ResolveSecretParams {
secret_reference: "op://test/test/test".to_string(),
};
let invocation = SdkInvocation::new(42, methods::SECRETS_RESOLVE, params);
let json = invocation.to_json().unwrap();
let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
assert!(parsed.get("invocation").is_some());
let inv = &parsed["invocation"];
assert_eq!(inv["clientId"], 42); assert_eq!(inv["parameters"]["name"], "SecretsResolve");
assert_eq!(
inv["parameters"]["parameters"]["secret_reference"],
"op://test/test/test"
);
}
#[test]
fn test_init_client_params_serialization() {
let params = InitClientParams {
service_account_token: SecretString::from("ops_test".to_string()),
programming_language: "Rust".to_string(),
sdk_version: "0.1.0".to_string(),
integration_name: "test-app".to_string(),
integration_version: "1.0.0".to_string(),
request_library_name: "reqwest".to_string(),
request_library_version: "0.11".to_string(),
os: "linux".to_string(),
os_version: "0.0.0".to_string(),
architecture: "aarch64".to_string(),
};
let json = serde_json::to_string(¶ms).unwrap();
assert!(json.contains("\"serviceAccountToken\":\"ops_test\""));
assert!(json.contains("\"programmingLanguage\":\"Rust\""));
assert!(json.contains("\"sdkVersion\":\"0.1.0\""));
assert!(json.contains("\"integrationName\":\"test-app\""));
assert!(json.contains("\"integrationVersion\":\"1.0.0\""));
assert!(json.contains("\"requestLibraryName\":\"reqwest\""));
assert!(json.contains("\"os\":\"linux\""));
assert!(json.contains("\"architecture\":\"aarch64\""));
}
#[test]
fn test_init_client_params_debug_redacts_token() {
let params = InitClientParams {
service_account_token: SecretString::from("ops_secret_token_12345".to_string()),
programming_language: "Rust".to_string(),
sdk_version: "0.1.0".to_string(),
integration_name: "test-app".to_string(),
integration_version: "1.0.0".to_string(),
request_library_name: "reqwest".to_string(),
request_library_version: "0.11".to_string(),
os: "linux".to_string(),
os_version: "0.0.0".to_string(),
architecture: "aarch64".to_string(),
};
let debug_output = format!("{params:?}");
assert!(!debug_output.contains("ops_secret_token_12345"));
assert!(!debug_output.contains("secret_token"));
assert!(debug_output.contains("[REDACTED]") || debug_output.contains("Secret"));
assert!(debug_output.contains("Rust"));
assert!(debug_output.contains("test-app"));
}
}