argumentation/
framework.rs1use petgraph::Direction;
4use petgraph::graph::{DiGraph, NodeIndex};
5use std::collections::HashMap;
6use std::hash::Hash;
7
8#[derive(Debug, Clone)]
10pub struct ArgumentationFramework<A: Clone + Eq + Hash> {
11 graph: DiGraph<A, ()>,
12 index: HashMap<A, NodeIndex>,
13}
14
15impl<A: Clone + Eq + Hash> ArgumentationFramework<A> {
16 pub fn new() -> Self {
18 Self {
19 graph: DiGraph::new(),
20 index: HashMap::new(),
21 }
22 }
23
24 pub fn add_argument(&mut self, a: A) {
26 if !self.index.contains_key(&a) {
27 let idx = self.graph.add_node(a.clone());
28 self.index.insert(a, idx);
29 }
30 }
31
32 pub fn add_attack(&mut self, attacker: &A, target: &A) -> Result<(), crate::Error>
41 where
42 A: std::fmt::Debug,
43 {
44 let a = *self
45 .index
46 .get(attacker)
47 .ok_or_else(|| crate::Error::ArgumentNotFound(format!("{:?}", attacker)))?;
48 let t = *self
49 .index
50 .get(target)
51 .ok_or_else(|| crate::Error::ArgumentNotFound(format!("{:?}", target)))?;
52 if self.graph.find_edge(a, t).is_none() {
55 self.graph.add_edge(a, t, ());
56 }
57 Ok(())
58 }
59
60 pub fn arguments(&self) -> impl Iterator<Item = &A> {
62 self.graph.node_weights()
63 }
64
65 #[must_use]
70 pub fn len(&self) -> usize {
71 self.graph.node_count()
72 }
73
74 #[must_use]
76 pub fn is_empty(&self) -> bool {
77 self.graph.node_count() == 0
78 }
79
80 pub fn attackers(&self, a: &A) -> Vec<&A> {
82 let Some(&idx) = self.index.get(a) else {
83 return Vec::new();
84 };
85 self.graph
86 .neighbors_directed(idx, Direction::Incoming)
87 .map(|n| &self.graph[n])
88 .collect()
89 }
90
91 pub fn attacked_by(&self, a: &A) -> Vec<&A> {
93 let Some(&idx) = self.index.get(a) else {
94 return Vec::new();
95 };
96 self.graph
97 .neighbors_directed(idx, Direction::Outgoing)
98 .map(|n| &self.graph[n])
99 .collect()
100 }
101}
102
103impl<A: Clone + Eq + Hash> Default for ArgumentationFramework<A> {
104 fn default() -> Self {
105 Self::new()
106 }
107}
108
109const _: fn() = || {
113 fn assert_send<T: Send>() {}
114 fn assert_sync<T: Sync>() {}
115 assert_send::<ArgumentationFramework<String>>();
116 assert_sync::<ArgumentationFramework<String>>();
117};
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn empty_framework_has_no_arguments() {
125 let af: ArgumentationFramework<&str> = ArgumentationFramework::new();
126 assert_eq!(af.arguments().count(), 0);
127 }
128
129 #[test]
130 fn add_argument_adds_one() {
131 let mut af = ArgumentationFramework::new();
132 af.add_argument("a");
133 assert_eq!(af.arguments().count(), 1);
134 }
135
136 #[test]
137 fn add_attack_creates_edge() {
138 let mut af = ArgumentationFramework::new();
139 af.add_argument("a");
140 af.add_argument("b");
141 af.add_attack(&"a", &"b").unwrap();
142 assert_eq!(af.attackers(&"b").len(), 1);
143 assert!(af.attackers(&"b").iter().any(|x| **x == "a"));
144 }
145
146 #[test]
147 fn add_attack_is_idempotent() {
148 let mut af = ArgumentationFramework::new();
149 af.add_argument("a");
150 af.add_argument("b");
151 af.add_attack(&"a", &"b").unwrap();
152 af.add_attack(&"a", &"b").unwrap();
153 assert_eq!(af.attackers(&"b").len(), 1);
155 }
156
157 #[test]
158 fn add_attack_on_missing_argument_reports_which() {
159 let mut af: ArgumentationFramework<&str> = ArgumentationFramework::new();
160 af.add_argument("a");
161 let err = af.add_attack(&"a", &"missing").unwrap_err();
162 let msg = err.to_string();
164 assert!(
165 msg.contains("missing"),
166 "error should mention the missing argument, got: {}",
167 msg
168 );
169 }
170
171 #[test]
172 fn self_attack_is_allowed() {
173 let mut af = ArgumentationFramework::new();
174 af.add_argument("a");
175 af.add_attack(&"a", &"a").unwrap();
176 assert_eq!(af.attackers(&"a").len(), 1);
177 }
178
179 #[test]
180 fn add_argument_is_idempotent() {
181 let mut af = ArgumentationFramework::new();
182 af.add_argument("a");
183 af.add_argument("a");
184 assert_eq!(af.arguments().count(), 1);
185 }
186}