use crate::Topic;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct HatId(String);
impl HatId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl From<&str> for HatId {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for HatId {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl std::fmt::Display for HatId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Hat {
pub id: HatId,
pub name: String,
pub description: String,
pub subscriptions: Vec<Topic>,
pub publishes: Vec<Topic>,
pub instructions: String,
}
impl Hat {
pub fn new(id: impl Into<HatId>, name: impl Into<String>) -> Self {
Self {
id: id.into(),
name: name.into(),
description: String::new(),
subscriptions: Vec::new(),
publishes: Vec::new(),
instructions: String::new(),
}
}
#[must_use]
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
#[deprecated(note = "Use default_planner() and default_builder() instead")]
pub fn default_single() -> Self {
Self {
id: HatId::new("default"),
name: "Default".to_string(),
description: "Default single-hat mode handler".to_string(),
subscriptions: vec![Topic::new("*")],
publishes: vec![Topic::new("task.done")],
instructions: String::new(),
}
}
pub fn default_planner() -> Self {
Self {
id: HatId::new("planner"),
name: "Planner".to_string(),
description: "Plans and prioritizes tasks, delegates to Builder".to_string(),
subscriptions: vec![
Topic::new("task.start"),
Topic::new("task.resume"),
Topic::new("build.done"),
Topic::new("build.blocked"),
],
publishes: vec![Topic::new("build.task")],
instructions: String::new(),
}
}
pub fn default_builder() -> Self {
Self {
id: HatId::new("builder"),
name: "Builder".to_string(),
description: "Implements code changes, runs backpressure".to_string(),
subscriptions: vec![Topic::new("build.task")],
publishes: vec![Topic::new("build.done"), Topic::new("build.blocked")],
instructions: String::new(),
}
}
#[must_use]
pub fn subscribe(mut self, topic: impl Into<Topic>) -> Self {
self.subscriptions.push(topic.into());
self
}
#[must_use]
pub fn with_instructions(mut self, instructions: impl Into<String>) -> Self {
self.instructions = instructions.into();
self
}
#[must_use]
pub fn with_publishes(mut self, publishes: Vec<Topic>) -> Self {
self.publishes = publishes;
self
}
pub fn is_subscribed(&self, topic: &Topic) -> bool {
self.is_subscribed_str(topic.as_str())
}
pub fn is_subscribed_str(&self, topic: &str) -> bool {
self.subscriptions.iter().any(|sub| sub.matches_str(topic))
}
pub fn has_specific_subscription(&self, topic: &Topic) -> bool {
self.subscriptions
.iter()
.any(|sub| !sub.is_global_wildcard() && sub.matches(topic))
}
pub fn is_fallback_only(&self) -> bool {
!self.subscriptions.is_empty() && self.subscriptions.iter().all(Topic::is_global_wildcard)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_subscription_matching() {
let hat = Hat::new("impl", "Implementer")
.subscribe("impl.*")
.subscribe("task.start");
assert!(hat.is_subscribed(&Topic::new("impl.done")));
assert!(hat.is_subscribed(&Topic::new("task.start")));
assert!(!hat.is_subscribed(&Topic::new("review.done")));
}
#[test]
#[allow(deprecated)]
fn test_default_single_hat() {
let hat = Hat::default_single();
assert!(hat.is_subscribed(&Topic::new("anything")));
assert!(hat.is_subscribed(&Topic::new("impl.done")));
}
#[test]
fn test_default_planner_hat() {
let hat = Hat::default_planner();
assert_eq!(hat.id.as_str(), "planner");
assert!(hat.is_subscribed(&Topic::new("task.start")));
assert!(hat.is_subscribed(&Topic::new("task.resume"))); assert!(hat.is_subscribed(&Topic::new("build.done")));
assert!(hat.is_subscribed(&Topic::new("build.blocked")));
assert!(!hat.is_subscribed(&Topic::new("build.task")));
}
#[test]
fn test_default_builder_hat() {
let hat = Hat::default_builder();
assert_eq!(hat.id.as_str(), "builder");
assert!(hat.is_subscribed(&Topic::new("build.task")));
assert!(!hat.is_subscribed(&Topic::new("task.start")));
assert!(!hat.is_subscribed(&Topic::new("build.done")));
}
}