postrust_graphql/subscription/
mod.rs1pub mod broker;
34
35pub use broker::{
36 create_notify_trigger_sql, drop_notify_trigger_sql, table_channel_name, BrokerError,
37 NotifyBroker, PgNotification,
38};
39
40use crate::schema::GeneratedSchema;
41use postrust_core::schema_cache::SchemaCache;
42
43#[derive(Debug, Clone)]
45pub struct SubscriptionField {
46 pub name: String,
48 pub table_name: String,
50 pub schema_name: String,
52 pub return_type: String,
54 pub description: Option<String>,
56}
57
58impl SubscriptionField {
59 pub fn for_table(schema: &str, table: &str, type_name: &str) -> Self {
61 Self {
62 name: table.to_string(),
63 table_name: table.to_string(),
64 schema_name: schema.to_string(),
65 return_type: type_name.to_string(),
66 description: Some(format!("Subscribe to changes on the {} table", table)),
67 }
68 }
69
70 pub fn channel_name(&self) -> String {
72 table_channel_name(&self.schema_name, &self.table_name)
73 }
74}
75
76pub fn generate_subscription_fields(
78 _schema_cache: &SchemaCache,
79 generated: &GeneratedSchema,
80) -> Vec<SubscriptionField> {
81 let mut fields = Vec::new();
82
83 for (type_name, obj_type) in &generated.object_types {
84 let table = &obj_type.table;
85
86 if !table.is_view {
88 fields.push(SubscriptionField::for_table(
89 &table.schema,
90 &table.name,
91 type_name,
92 ));
93 }
94 }
95
96 fields
97}
98
99#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
101pub struct TableChangePayload {
102 pub operation: String,
104 pub table: String,
106 pub schema: String,
108 #[serde(skip_serializing_if = "Option::is_none")]
110 pub old: Option<serde_json::Value>,
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub new: Option<serde_json::Value>,
114}
115
116impl TableChangePayload {
117 pub fn from_payload(payload: &str) -> Result<Self, serde_json::Error> {
119 serde_json::from_str(payload)
120 }
121
122 pub fn data(&self) -> Option<&serde_json::Value> {
127 match self.operation.as_str() {
128 "DELETE" => self.old.as_ref(),
129 _ => self.new.as_ref(),
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn test_subscription_field_channel_name() {
140 let field = SubscriptionField::for_table("public", "users", "Users");
141 assert_eq!(field.channel_name(), "postrust_public_users");
142 }
143
144 #[test]
145 fn test_table_change_payload_parsing() {
146 let json = r#"{
147 "operation": "INSERT",
148 "table": "users",
149 "schema": "public",
150 "new": {"id": 1, "name": "Alice"}
151 }"#;
152
153 let payload = TableChangePayload::from_payload(json).unwrap();
154 assert_eq!(payload.operation, "INSERT");
155 assert_eq!(payload.table, "users");
156 assert!(payload.new.is_some());
157 assert!(payload.old.is_none());
158 }
159
160 #[test]
161 fn test_table_change_payload_data() {
162 let insert_payload = TableChangePayload {
163 operation: "INSERT".to_string(),
164 table: "users".to_string(),
165 schema: "public".to_string(),
166 old: None,
167 new: Some(serde_json::json!({"id": 1})),
168 };
169 assert!(insert_payload.data().is_some());
170
171 let delete_payload = TableChangePayload {
172 operation: "DELETE".to_string(),
173 table: "users".to_string(),
174 schema: "public".to_string(),
175 old: Some(serde_json::json!({"id": 1})),
176 new: None,
177 };
178 assert!(delete_payload.data().is_some());
179 }
180}