#![forbid(unsafe_code)]
#![doc = include_str!("../Documentation.md")]
use std::sync::Arc;
use kcode_k1_access_kmap::K1AccessKmap;
use kcode_k1_chat_thread_actions::ChatThreadActions;
pub use kcode_k1_chat_thread_actions::{AccessContext, AccessPolicy, ProfileId};
use kcode_k1_ktool_social::SocialKtools;
const DIFFERENT_AUTHORIZATION: &str =
"different Kmap access context, profile ID, or policy is already active";
const NO_SOCIAL_AUTHORIZATION: &str = "social authorization is not active";
const NO_SOCIAL_KTOOLS: &str = "social Ktools are unavailable";
#[derive(Clone, Eq, PartialEq)]
struct Authorization {
context: AccessContext,
profile_id: ProfileId,
policy: AccessPolicy,
}
pub struct ChatThreadKtools {
actions: ChatThreadActions,
social: Option<SocialKtools>,
authorization: Option<Authorization>,
}
impl ChatThreadKtools {
pub fn new(kmap: Arc<K1AccessKmap>) -> Self {
Self {
actions: ChatThreadActions::new(kmap),
social: None,
authorization: None,
}
}
pub fn new_with_social(kmap: Arc<K1AccessKmap>, social: SocialKtools) -> Self {
Self {
actions: ChatThreadActions::new(kmap),
social: Some(social),
authorization: None,
}
}
pub fn bind_authorization(
&mut self,
context: AccessContext,
profile_id: ProfileId,
policy: AccessPolicy,
) -> Result<(), String> {
let candidate = Authorization {
context,
profile_id,
policy,
};
if self
.authorization
.as_ref()
.is_some_and(|active| active != &candidate)
{
return Err(DIFFERENT_AUTHORIZATION.to_owned());
}
self.actions.bind_authorization(
candidate.context.clone(),
candidate.profile_id,
candidate.policy.clone(),
)?;
self.authorization = Some(candidate);
Ok(())
}
pub fn clear_authorization(&mut self) {
self.actions.clear_authorization();
self.authorization = None;
}
pub fn launch(&mut self, name: &str, arguments: &str) -> Result<String, String> {
if matches!(name, "ListContacts" | "ListGroups" | "GetGroup") {
let social = self
.social
.as_ref()
.ok_or_else(|| NO_SOCIAL_KTOOLS.to_owned())?;
let context = self
.authorization
.as_ref()
.map(|authorization| &authorization.context)
.ok_or_else(|| NO_SOCIAL_AUTHORIZATION.to_owned())?;
return social.launch(context, name, arguments);
}
self.actions.launch(name, arguments)
}
}
#[cfg(test)]
mod tests {
#[test]
fn social_names_are_the_only_intercepted_names() {
for name in ["ListContacts", "ListGroups", "GetGroup"] {
assert!(matches!(name, "ListContacts" | "ListGroups" | "GetGroup"));
}
for name in ["CurrentTime", "KmapOpenNode", "WebSearch", "SetLaunchNode"] {
assert!(!matches!(name, "ListContacts" | "ListGroups" | "GetGroup"));
}
}
#[test]
fn complete_package_stays_below_the_managed_limit() {
let files = [
include_str!("../Cargo.toml"),
include_str!("../Documentation.md"),
include_str!("lib.rs"),
];
let count = files
.iter()
.flat_map(|file| file.lines())
.filter(|line| !line.trim().is_empty())
.count();
assert!(count < 500, "complete package has {count} nonblank lines");
}
}