use serde::Deserialize;
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CallerContext {
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub operation: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub resource: Option<String>,
}
impl CallerContext {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
version: None,
operation: None,
resource: None,
}
}
#[must_use]
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
#[must_use]
pub fn with_operation(mut self, operation: impl Into<String>) -> Self {
self.operation = Some(operation.into());
self
}
#[must_use]
pub fn with_resource(mut self, resource: impl Into<String>) -> Self {
self.resource = Some(resource.into());
self
}
pub(crate) fn normalized(mut self) -> Option<Self> {
self.name = normalize(&self.name)?;
self.version = self.version.as_deref().and_then(normalize);
self.operation = self.operation.as_deref().and_then(normalize);
self.resource = self.resource.as_deref().and_then(normalize);
Some(self)
}
}
fn normalize(value: &str) -> Option<String> {
let trimmed = value.trim();
(!trimmed.is_empty()).then(|| trimmed.to_string())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn normalization_trims_fields_and_drops_blank_options() {
assert_eq!(
CallerContext::new(" git ")
.with_version(" 2.51.0 ")
.with_operation(" credential_get\n")
.with_resource(" ")
.normalized(),
Some(CallerContext {
name: "git".to_string(),
version: Some("2.51.0".to_string()),
operation: Some("credential_get".to_string()),
resource: None,
})
);
assert!(CallerContext::new(" ").normalized().is_none());
}
}