a3s_code_core/state_graph/
behavior.rs1use super::{GraphEvent, GraphEventRecord, GraphPatch, StateGraph};
2use std::fmt;
3use std::sync::Arc;
4use thiserror::Error;
5
6type EventPredicate = dyn Fn(&GraphEventRecord, &StateGraph) -> bool + Send + Sync;
7
8#[derive(Clone, Default)]
9pub struct EventFilter {
10 event_types: Vec<String>,
11 object_types: Vec<String>,
12 relation_types: Vec<String>,
13 predicate: Option<Arc<EventPredicate>>,
14}
15
16impl EventFilter {
17 pub fn new(event_types: impl IntoIterator<Item = impl Into<String>>) -> Self {
18 Self {
19 event_types: event_types.into_iter().map(Into::into).collect(),
20 ..Self::default()
21 }
22 }
23
24 pub fn with_object_types(
25 mut self,
26 object_types: impl IntoIterator<Item = impl Into<String>>,
27 ) -> Self {
28 self.object_types = object_types.into_iter().map(Into::into).collect();
29 self
30 }
31
32 pub fn with_relation_types(
33 mut self,
34 relation_types: impl IntoIterator<Item = impl Into<String>>,
35 ) -> Self {
36 self.relation_types = relation_types.into_iter().map(Into::into).collect();
37 self
38 }
39
40 pub fn where_predicate(
41 mut self,
42 predicate: impl Fn(&GraphEventRecord, &StateGraph) -> bool + Send + Sync + 'static,
43 ) -> Self {
44 self.predicate = Some(Arc::new(predicate));
45 self
46 }
47
48 pub fn matches(&self, event: &GraphEventRecord, graph: &StateGraph) -> bool {
49 let event_type_matches = self.event_types.is_empty()
50 || self
51 .event_types
52 .iter()
53 .any(|candidate| candidate == event.event.event_type());
54 let object_type_matches = self.object_types.is_empty()
55 || event_object_type(event, graph).is_some_and(|object_type| {
56 self.object_types
57 .iter()
58 .any(|candidate| candidate == object_type)
59 });
60 let relation_type_matches = self.relation_types.is_empty()
61 || event_relation_type(event, graph).is_some_and(|relation_type| {
62 self.relation_types
63 .iter()
64 .any(|candidate| candidate == relation_type)
65 });
66 event_type_matches
67 && object_type_matches
68 && relation_type_matches
69 && self
70 .predicate
71 .as_ref()
72 .is_none_or(|predicate| predicate(event, graph))
73 }
74}
75
76impl fmt::Debug for EventFilter {
77 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
78 formatter
79 .debug_struct("EventFilter")
80 .field("event_types", &self.event_types)
81 .field("object_types", &self.object_types)
82 .field("relation_types", &self.relation_types)
83 .field("has_predicate", &self.predicate.is_some())
84 .finish()
85 }
86}
87
88fn event_object_type<'a>(event: &'a GraphEventRecord, graph: &'a StateGraph) -> Option<&'a str> {
89 match &event.event {
90 GraphEvent::ObjectCreated { object_type, .. } => Some(object_type),
91 GraphEvent::ObjectUpdated { id, .. } => {
92 graph.object(id).map(|object| object.object_type.as_str())
93 }
94 GraphEvent::ObjectRemoved { .. } => None,
95 _ => None,
96 }
97}
98
99fn event_relation_type<'a>(event: &'a GraphEventRecord, graph: &'a StateGraph) -> Option<&'a str> {
100 match &event.event {
101 GraphEvent::RelationCreated { relation_type, .. } => Some(relation_type),
102 GraphEvent::RelationUpdated { id, .. } => graph
103 .relation(id)
104 .map(|relation| relation.relation_type.as_str()),
105 GraphEvent::RelationRemoved { .. } => None,
106 _ => None,
107 }
108}
109
110pub struct BehaviorContext<'a> {
111 pub graph: &'a StateGraph,
112 pub event: &'a GraphEventRecord,
113}
114
115#[derive(Debug, Clone, Error, PartialEq, Eq)]
116#[error("{message}")]
117pub struct BehaviorError {
118 pub message: String,
119}
120
121impl BehaviorError {
122 pub fn new(message: impl Into<String>) -> Self {
123 Self {
124 message: message.into(),
125 }
126 }
127}
128
129pub trait Behavior: Send + Sync {
130 fn name(&self) -> &str;
131 fn filter(&self) -> &EventFilter;
132 fn evaluate(&self, context: BehaviorContext<'_>) -> Result<Vec<GraphPatch>, BehaviorError>;
133}
134
135type BehaviorFn =
136 dyn for<'a> Fn(BehaviorContext<'a>) -> Result<Vec<GraphPatch>, BehaviorError> + Send + Sync;
137
138pub struct FnBehavior {
139 name: String,
140 filter: EventFilter,
141 evaluator: Arc<BehaviorFn>,
142}
143
144impl FnBehavior {
145 pub fn new(
146 name: impl Into<String>,
147 filter: EventFilter,
148 evaluator: impl for<'a> Fn(BehaviorContext<'a>) -> Result<Vec<GraphPatch>, BehaviorError>
149 + Send
150 + Sync
151 + 'static,
152 ) -> Self {
153 Self {
154 name: name.into(),
155 filter,
156 evaluator: Arc::new(evaluator),
157 }
158 }
159}
160
161impl Behavior for FnBehavior {
162 fn name(&self) -> &str {
163 &self.name
164 }
165
166 fn filter(&self) -> &EventFilter {
167 &self.filter
168 }
169
170 fn evaluate(&self, context: BehaviorContext<'_>) -> Result<Vec<GraphPatch>, BehaviorError> {
171 (self.evaluator)(context)
172 }
173}