1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! `Hooks` resource — outbound event webhooks on memory mutations.
//!
//! Mirrors the Python SDK's `client.hooks.*` surface.
//!
//! Requires: Scale or Custom plan. The server returns `FTR-E001`
//! (surfaced as [`crate::AreevError::FeatureNotAvailable`]) when the
//! org's tier does not include hooks — the SDK never gates client-side.
use serde_json::{Map, Value};
use crate::error::Result;
use crate::http::HttpClient;
/// Event-hook lifecycle.
///
/// Access via [`crate::Areev::hooks`].
pub struct Hooks<'a> {
http: &'a HttpClient,
memory_id: String,
}
impl<'a> Hooks<'a> {
/// Internal constructor — use [`crate::Areev::hooks`].
pub(crate) fn new(http: &'a HttpClient, memory_id: String) -> Self {
Self { http, memory_id }
}
/// Start a [`CreateHookBuilder`] for a new event hook.
///
/// When `generate_secret` is set the response carries a one-time
/// signing secret — shown once, never retrievable; do not log it.
/// Requires: Scale or Custom plan. Emits an audit event.
pub fn create<'b>(&'b self, name: &str) -> CreateHookBuilder<'b, 'a> {
CreateHookBuilder::new(self, name)
}
/// List configured hooks. Requires: Scale or Custom plan.
pub async fn list(&self) -> Result<Value> {
let path = format!("/memories/{}/hooks", self.memory_id);
self.http._get(&path, None).await
}
/// Hook delivery statistics. Requires: Scale or Custom plan.
pub async fn stats(&self) -> Result<Value> {
let path = format!("/memories/{}/hooks/stats", self.memory_id);
self.http._get(&path, None).await
}
/// Delete a hook. Requires: Scale or Custom plan. Emits an audit
/// event.
pub async fn delete(&self, hook_id: &str) -> Result<()> {
let path = format!("/memories/{}/hooks/{}", self.memory_id, hook_id);
self.http._delete(&path).await.map(|_| ())
}
}
/// Builder for [`Hooks::create`].
pub struct CreateHookBuilder<'b, 'a> {
hooks: &'b Hooks<'a>,
body: Map<String, Value>,
}
impl<'b, 'a> CreateHookBuilder<'b, 'a> {
fn new(hooks: &'b Hooks<'a>, name: &str) -> Self {
let mut body = Map::new();
body.insert("name".into(), Value::String(name.to_string()));
Self { hooks, body }
}
/// Delivery URL the hook POSTs to.
pub fn url(mut self, url: &str) -> Self {
self.body
.insert("url".into(), Value::String(url.to_string()));
self
}
/// Event types to fire on (e.g. `grain.added`, `grain.forgotten`).
pub fn event_filters(mut self, filters: &[&str]) -> Self {
self.body.insert(
"event_filters".into(),
Value::Array(
filters
.iter()
.map(|f| Value::String(f.to_string()))
.collect(),
),
);
self
}
/// Delivery mode — e.g. `sync`, `async`.
pub fn delivery_mode(mut self, mode: &str) -> Self {
self.body
.insert("delivery_mode".into(), Value::String(mode.to_string()));
self
}
/// Redaction mode applied to the payload before delivery.
pub fn redaction_mode(mut self, mode: &str) -> Self {
self.body
.insert("redaction_mode".into(), Value::String(mode.to_string()));
self
}
/// Request a one-time signing secret in the response.
pub fn generate_secret(mut self, generate: bool) -> Self {
self.body
.insert("generate_secret".into(), Value::Bool(generate));
self
}
/// Acknowledge that an un-redacted hook may deliver PII off-platform.
pub fn acknowledge_pii_risk(mut self, ack: bool) -> Self {
self.body
.insert("acknowledge_pii_risk".into(), Value::Bool(ack));
self
}
/// Issue the create request.
pub async fn send(self) -> Result<Value> {
let path = format!("/memories/{}/hooks", self.hooks.memory_id);
self.hooks
.http
._post(&path, Some(&Value::Object(self.body)))
.await
}
}