areev_trigger/
connector.rs1use serde::{Deserialize, Serialize};
26
27#[derive(Debug, Clone, Serialize)]
29pub struct PollRequest<'a> {
30 pub trigger: &'a str,
31 pub connector: &'a str,
32 #[serde(skip_serializing_if = "Option::is_none")]
33 pub scope: Option<&'a str>,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub cursor: Option<&'a str>,
37 pub max_items: usize,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub config: Option<&'a serde_json::Value>,
40}
41
42#[derive(Debug, Clone, Default, Serialize, Deserialize)]
44#[serde(default)]
45pub struct PollItem {
46 pub id: String,
51 pub payload: serde_json::Value,
52}
53
54#[derive(Debug, Clone, Default, Serialize, Deserialize)]
56#[serde(default)]
57pub struct PollResponse {
58 pub items: Vec<PollItem>,
59 pub cursor: Option<String>,
63 pub more: bool,
65}
66
67pub fn dedup_value(item: &PollItem, pointers: &[String]) -> Option<String> {
75 if pointers.is_empty() {
76 return if item.id.trim().is_empty() { None } else { Some(item.id.clone()) };
77 }
78 let mut parts = Vec::with_capacity(pointers.len());
79 for p in pointers {
80 let v = item.payload.pointer(p)?;
81 parts.push(match v {
82 serde_json::Value::String(s) => s.clone(),
83 other => other.to_string(),
84 });
85 }
86 Some(parts.join("\u{1f}"))
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn item(id: &str, payload: serde_json::Value) -> PollItem {
94 PollItem { id: id.into(), payload }
95 }
96
97 #[test]
98 fn falls_back_to_the_connectors_own_id() {
99 let i = item("abc", serde_json::json!({}));
100 assert_eq!(dedup_value(&i, &[]), Some("abc".into()));
101 }
102
103 #[test]
104 fn an_item_with_no_identity_at_all_yields_none() {
105 let i = item("", serde_json::json!({}));
108 assert_eq!(dedup_value(&i, &[]), None);
109 assert_eq!(dedup_value(&i, &["/missing".into()]), None);
110 }
111
112 #[test]
113 fn a_declared_pointer_beats_the_connectors_id() {
114 let i = item("connector-chosen", serde_json::json!({ "message_id": "m1" }));
115 assert_eq!(dedup_value(&i, &["/message_id".into()]), Some("m1".into()));
116 }
117
118 #[test]
119 fn composite_pointers_identify_the_occurrence_not_the_entity() {
120 let pointers = vec!["/id".to_string(), "/updated_at".to_string()];
121 let first = item("x", serde_json::json!({ "id": "42", "updated_at": 100 }));
122 let edited = item("x", serde_json::json!({ "id": "42", "updated_at": 200 }));
123 let a = dedup_value(&first, &pointers).unwrap();
124 let b = dedup_value(&edited, &pointers).unwrap();
125 assert_ne!(a, b, "an edit is a new occurrence of the same entity");
126 }
127
128 #[test]
129 fn the_joiner_cannot_be_forged_from_field_contents() {
130 let pointers = vec!["/a".to_string(), "/b".to_string()];
133 let x = item("", serde_json::json!({ "a": "1-2", "b": "3" }));
134 let y = item("", serde_json::json!({ "a": "1", "b": "2-3" }));
135 assert_ne!(dedup_value(&x, &pointers), dedup_value(&y, &pointers));
136 }
137
138 #[test]
139 fn absent_cursor_in_a_response_means_leave_it_alone() {
140 let r: PollResponse = serde_json::from_str(r#"{"items":[]}"#).unwrap();
141 assert!(r.cursor.is_none(), "not Some(\"null\") and not an error");
142 assert!(!r.more);
143 }
144
145 #[test]
146 fn unknown_response_fields_are_ignored() {
147 let r: PollResponse =
149 serde_json::from_str(r#"{"items":[],"cursor":"c","more":true,"future":1}"#).unwrap();
150 assert_eq!(r.cursor.as_deref(), Some("c"));
151 assert!(r.more);
152 }
153}