use serde::{Deserialize, Serialize};
use crate::keys::KeyType;
#[derive(Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct ImportKeyBody {
#[serde(alias = "key_type")]
pub key_type: KeyType,
#[serde(
default,
alias = "private_key_sealed",
skip_serializing_if = "Option::is_none"
)]
pub private_key_sealed: Option<String>,
#[serde(
default,
alias = "private_key_jwe",
skip_serializing_if = "Option::is_none"
)]
pub private_key_jwe: Option<String>,
#[serde(
default,
alias = "private_key_multibase",
skip_serializing_if = "Option::is_none"
)]
pub private_key_multibase: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, alias = "context_id", skip_serializing_if = "Option::is_none")]
pub context_id: Option<String>,
}
impl std::fmt::Debug for ImportKeyBody {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ImportKeyBody")
.field("key_type", &self.key_type)
.field(
"private_key_sealed",
&self.private_key_sealed.as_ref().map(|_| "<redacted>"),
)
.field(
"private_key_jwe",
&self.private_key_jwe.as_ref().map(|_| "<redacted>"),
)
.field(
"private_key_multibase",
&self.private_key_multibase.as_ref().map(|_| "<redacted>"),
)
.field("label", &self.label)
.field("context_id", &self.context_id)
.finish()
}
}
pub use super::create::CreateKeyResponseBody as ImportKeyResponseBody;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn accepts_both_spellings_on_intake() {
let canonical = serde_json::json!({
"keyType": "ed25519",
"privateKeySealed": "-----BEGIN SEALED TRANSFER-----",
"contextId": "app"
});
let body: ImportKeyBody = serde_json::from_value(canonical).unwrap();
assert_eq!(body.context_id.as_deref(), Some("app"));
assert!(body.private_key_sealed.is_some());
let legacy = serde_json::json!({
"key_type": "ed25519",
"private_key_sealed": "-----BEGIN SEALED TRANSFER-----",
"context_id": "app"
});
let body: ImportKeyBody = serde_json::from_value(legacy).unwrap();
assert_eq!(body.context_id.as_deref(), Some("app"));
}
#[test]
fn emits_camel_case() {
let body = ImportKeyBody {
key_type: KeyType::Ed25519,
private_key_sealed: Some("sealed".into()),
private_key_jwe: None,
private_key_multibase: None,
label: None,
context_id: Some("app".into()),
};
let v = serde_json::to_value(&body).unwrap();
assert!(v.get("keyType").is_some(), "{v}");
assert!(v.get("privateKeySealed").is_some(), "{v}");
assert!(v.get("key_type").is_none(), "{v}");
}
#[test]
fn debug_redacts_every_carrier() {
let body = ImportKeyBody {
key_type: KeyType::Ed25519,
private_key_sealed: Some("SEALED-SECRET".into()),
private_key_jwe: Some("JWE-SECRET".into()),
private_key_multibase: Some("z-MULTIBASE-SECRET".into()),
label: None,
context_id: None,
};
let rendered = format!("{body:?}");
assert!(!rendered.contains("SEALED-SECRET"), "{rendered}");
assert!(!rendered.contains("JWE-SECRET"), "{rendered}");
assert!(!rendered.contains("MULTIBASE-SECRET"), "{rendered}");
assert!(rendered.contains("<redacted>"), "{rendered}");
}
}