use std::time::Duration;
use std::{collections::BTreeMap, fmt};
use super::{RequestId, lifecycle::CancellationToken};
#[derive(Clone, Eq, PartialEq)]
pub struct InvocationExtension {
key: String,
value: Vec<u8>,
}
impl fmt::Debug for InvocationExtension {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("InvocationExtension")
.field("key", &self.key)
.field("value", &"<redacted>")
.finish()
}
}
impl InvocationExtension {
pub fn new(key: impl Into<String>, value: Vec<u8>) -> Self {
Self {
key: key.into(),
value,
}
}
pub fn key(&self) -> &str {
&self.key
}
pub fn value(&self) -> &[u8] {
&self.value
}
}
#[derive(Clone, Eq, PartialEq)]
pub struct SealedInvocationExtension {
key: String,
issuer: String,
audience: Vec<String>,
value: Vec<u8>,
proof: String,
}
impl SealedInvocationExtension {
pub fn signed(
key: impl Into<String>,
issuer: impl Into<String>,
audience: impl IntoIterator<Item = impl Into<String>>,
value: Vec<u8>,
proof: impl Into<String>,
) -> Self {
Self {
key: key.into(),
issuer: issuer.into(),
audience: audience.into_iter().map(Into::into).collect(),
value,
proof: proof.into(),
}
}
pub fn key(&self) -> &str {
&self.key
}
pub fn issuer(&self) -> &str {
&self.issuer
}
pub fn audience(&self) -> &[String] {
&self.audience
}
pub fn value(&self) -> &[u8] {
&self.value
}
pub fn proof(&self) -> &str {
&self.proof
}
pub fn covers(&self, capability_id: &str, operation: &str) -> bool {
let target = format!("{capability_id}:{operation}");
self.audience.iter().any(|audience| audience == &target)
}
}
impl fmt::Debug for SealedInvocationExtension {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("SealedInvocationExtension")
.field("key", &self.key)
.field("issuer", &self.issuer)
.field("audience", &self.audience)
.field("value", &"<redacted>")
.field("proof", &"<redacted>")
.finish()
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InvocationContextError {
EmptyExtensionKey,
ExtensionAlreadySet { key: String },
SealedExtensionAlreadySet { key: String },
InvalidSealedExtension { key: String },
}
impl std::fmt::Display for InvocationContextError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::EmptyExtensionKey => {
formatter.write_str("Invocation Context extension key is empty")
}
Self::ExtensionAlreadySet { key } => {
write!(
formatter,
"Invocation Context extension `{key}` is already set"
)
}
Self::SealedExtensionAlreadySet { key } => {
write!(
formatter,
"sealed Invocation Context extension `{key}` is already set"
)
}
Self::InvalidSealedExtension { key } => {
write!(
formatter,
"sealed Invocation Context extension `{key}` has invalid provenance"
)
}
}
}
}
#[derive(Clone, Debug)]
pub struct InvocationContext {
pub(super) caller_instance: Option<String>,
pub(super) request_id: RequestId,
pub(super) deadline: Option<Duration>,
pub(super) cancellation: CancellationToken,
pub(super) extensions: BTreeMap<String, InvocationExtension>,
pub(super) sealed_extensions: BTreeMap<String, SealedInvocationExtension>,
}
impl InvocationContext {
pub fn new(
request_id: RequestId,
deadline: Option<Duration>,
cancellation: CancellationToken,
) -> Self {
Self {
caller_instance: None,
request_id,
deadline,
cancellation,
extensions: BTreeMap::new(),
sealed_extensions: BTreeMap::new(),
}
}
#[must_use]
pub fn with_caller_instance(mut self, caller_instance: impl Into<String>) -> Self {
self.caller_instance = Some(caller_instance.into());
self
}
pub fn caller_instance(&self) -> Option<&str> {
self.caller_instance.as_deref()
}
pub const fn request_id(&self) -> RequestId {
self.request_id
}
pub const fn deadline(&self) -> Option<Duration> {
self.deadline
}
pub fn cancellation(&self) -> CancellationToken {
self.cancellation.clone()
}
pub fn with_extension(
mut self,
key: impl Into<String>,
value: Vec<u8>,
) -> Result<Self, InvocationContextError> {
let extension = InvocationExtension::new(key, value);
if extension.key().is_empty() {
return Err(InvocationContextError::EmptyExtensionKey);
}
if self.sealed_extensions.contains_key(extension.key()) {
return Err(InvocationContextError::SealedExtensionAlreadySet {
key: extension.key().to_owned(),
});
}
if self.extensions.contains_key(extension.key()) {
return Err(InvocationContextError::ExtensionAlreadySet {
key: extension.key().to_owned(),
});
}
self.extensions
.insert(extension.key().to_owned(), extension);
Ok(self)
}
pub fn with_sealed_extension(
mut self,
extension: SealedInvocationExtension,
) -> Result<Self, InvocationContextError> {
if extension.key().is_empty() {
return Err(InvocationContextError::EmptyExtensionKey);
}
if extension.issuer().is_empty()
|| extension.audience().is_empty()
|| extension.proof().is_empty()
|| extension
.audience()
.iter()
.any(|audience| audience.is_empty())
{
return Err(InvocationContextError::InvalidSealedExtension {
key: extension.key().to_owned(),
});
}
if self.sealed_extensions.contains_key(extension.key())
|| self.extensions.contains_key(extension.key())
{
return Err(InvocationContextError::SealedExtensionAlreadySet {
key: extension.key().to_owned(),
});
}
self.sealed_extensions
.insert(extension.key().to_owned(), extension);
Ok(self)
}
pub fn extension(&self, key: &str) -> Option<&[u8]> {
self.extensions.get(key).map(InvocationExtension::value)
}
pub fn extensions(&self) -> impl Iterator<Item = &InvocationExtension> {
self.extensions.values()
}
pub fn sealed_extension(&self, key: &str) -> Option<&SealedInvocationExtension> {
self.sealed_extensions.get(key)
}
pub fn sealed_extensions(&self) -> impl Iterator<Item = &SealedInvocationExtension> {
self.sealed_extensions.values()
}
#[must_use]
pub fn for_target(mut self, capability_id: &str, operation: &str) -> Self {
self.sealed_extensions
.retain(|_, extension| extension.covers(capability_id, operation));
self
}
pub fn is_cancelled(&self) -> bool {
self.cancellation.is_cancelled()
}
pub fn is_expired(&self, now: Duration) -> bool {
self.deadline.is_some_and(|deadline| deadline <= now)
}
}