mod builder;
pub use builder::AadContextBuilder;
use crate::canon::{canonicalize_context, canonicalize_context_string};
use crate::error::AadError;
use crate::parse::{ParsedAad, CURRENT_VERSION};
use crate::types::{ExtensionValue, Extensions, FieldKey, Purpose, Resource, SafeInt, Tenant};
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
use std::collections::BTreeMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AadContext {
version: SafeInt,
tenant: Tenant,
resource: Resource,
purpose: Purpose,
timestamp: Option<SafeInt>,
extensions: Extensions,
}
impl AadContext {
pub fn new(
tenant: impl Into<String>,
resource: impl Into<String>,
purpose: impl Into<String>,
) -> Result<Self, AadError> {
Ok(Self {
version: SafeInt::new_for_field(CURRENT_VERSION, "v")?,
tenant: Tenant::new(tenant)?,
resource: Resource::new(resource)?,
purpose: Purpose::new(purpose)?,
timestamp: None,
extensions: BTreeMap::new(),
})
}
#[must_use]
pub fn builder() -> AadContextBuilder {
AadContextBuilder::new()
}
pub fn with_timestamp(mut self, ts: u64) -> Result<Self, AadError> {
self.timestamp = Some(SafeInt::new_for_field(ts, "ts")?);
Ok(self)
}
pub fn with_extension(
mut self,
key: impl Into<String>,
value: ExtensionValue,
) -> Result<Self, AadError> {
let key = FieldKey::new(key.into())?;
if key.is_reserved() {
return Err(AadError::ReservedKeyAsExtension { key: key.as_str().to_owned() });
}
key.validate_as_extension()?;
self.extensions.insert(key, value);
Ok(self)
}
pub fn with_string_extension(
self,
key: impl Into<String>,
value: impl Into<String>,
) -> Result<Self, AadError> {
self.with_extension(key, ExtensionValue::string(value)?)
}
pub fn with_int_extension(self, key: impl Into<String>, value: u64) -> Result<Self, AadError> {
self.with_extension(key, ExtensionValue::integer(value)?)
}
#[must_use]
pub const fn version(&self) -> u64 {
self.version.value()
}
#[must_use]
pub fn tenant(&self) -> &str {
self.tenant.as_str()
}
#[must_use]
pub fn resource(&self) -> &str {
self.resource.as_str()
}
#[must_use]
pub fn purpose(&self) -> &str {
self.purpose.as_str()
}
#[must_use]
pub fn timestamp(&self) -> Option<u64> {
self.timestamp.map(|ts| ts.value())
}
#[must_use]
pub const fn extensions(&self) -> &Extensions {
&self.extensions
}
pub fn canonicalize(&self) -> Result<Vec<u8>, AadError> {
canonicalize_context(self)
}
pub fn canonicalize_string(&self) -> Result<String, AadError> {
canonicalize_context_string(self)
}
}
impl Serialize for AadContext {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut field_count = 4;
if self.timestamp.is_some() {
field_count += 1;
}
field_count += self.extensions.len();
let mut map = serializer.serialize_map(Some(field_count))?;
map.serialize_entry("purpose", self.purpose.as_str())?;
map.serialize_entry("resource", self.resource.as_str())?;
map.serialize_entry("tenant", self.tenant.as_str())?;
if let Some(ts) = &self.timestamp {
map.serialize_entry("ts", &ts.value())?;
}
map.serialize_entry("v", &self.version.value())?;
for (key, value) in &self.extensions {
match value {
ExtensionValue::String(s) => map.serialize_entry(key.as_str(), s)?,
ExtensionValue::Integer(i) => map.serialize_entry(key.as_str(), &i.value())?,
}
}
map.end()
}
}
impl TryFrom<ParsedAad> for AadContext {
type Error = AadError;
fn try_from(parsed: ParsedAad) -> Result<Self, Self::Error> {
Ok(Self {
version: parsed.version,
tenant: parsed.tenant,
resource: parsed.resource,
purpose: parsed.purpose,
timestamp: parsed.timestamp,
extensions: parsed.extensions,
})
}
}
#[cfg(test)]
mod tests;