1use serde::{Deserialize, Serialize};
4use serde_with::skip_serializing_none;
5use std::collections::HashMap;
6
7use crate::hooks::HookImplementation;
8
9#[skip_serializing_none]
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ActionDefinition {
13 pub r#type: String,
15 pub version: String,
17 pub description: String,
19
20 pub metadata: Option<ActionMetadata>,
22
23 pub subtypes: Option<HashMap<String, String>>,
25
26 pub fields: FieldConstraints,
28
29 pub schema: Option<ContentSchemaWrapper>,
31
32 pub behavior: BehaviorFlags,
34
35 pub key_pattern: Option<String>,
37
38 pub hooks: ActionHooks,
40
41 pub permissions: Option<PermissionRules>,
43}
44
45#[skip_serializing_none]
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ActionMetadata {
49 pub category: Option<String>,
50 pub tags: Option<Vec<String>>,
51 pub deprecated: Option<bool>,
52 pub experimental: Option<bool>,
53}
54
55#[skip_serializing_none]
57#[derive(Debug, Clone, Serialize, Deserialize, Default)]
58pub struct FieldConstraints {
59 pub content: Option<FieldConstraint>,
61 pub audience: Option<FieldConstraint>,
63 pub parent: Option<FieldConstraint>,
65 pub subject: Option<FieldConstraint>,
67 pub attachments: Option<FieldConstraint>,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
73#[serde(rename_all = "lowercase")]
74pub enum FieldConstraint {
75 Required,
77 Forbidden,
79}
80
81#[skip_serializing_none]
83#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct ContentSchemaWrapper {
85 pub content: Option<ContentSchema>,
86}
87
88#[skip_serializing_none]
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct ContentSchema {
92 #[serde(rename = "type")]
94 pub content_type: ContentType,
95
96 pub min_length: Option<usize>,
98 pub max_length: Option<usize>,
99 pub pattern: Option<String>,
100
101 pub r#enum: Option<Vec<serde_json::Value>>,
103
104 pub properties: Option<HashMap<String, SchemaField>>,
106
107 pub required: Option<Vec<String>>,
109
110 pub description: Option<String>,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "lowercase")]
117pub enum ContentType {
118 String,
119 Number,
120 Boolean,
121 Object,
122 Json,
123}
124
125#[skip_serializing_none]
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct SchemaField {
129 #[serde(rename = "type")]
130 pub field_type: FieldType,
131
132 pub min_length: Option<usize>,
133 pub max_length: Option<usize>,
134 pub r#enum: Option<Vec<serde_json::Value>>,
135 pub items: Option<Box<SchemaField>>,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
140#[serde(rename_all = "lowercase")]
141pub enum FieldType {
142 String,
143 Number,
144 Boolean,
145 Array,
146 Json,
147}
148
149#[skip_serializing_none]
171#[derive(Debug, Clone, Serialize, Deserialize, Default)]
172pub struct BehaviorFlags {
173 pub broadcast: Option<bool>,
177
178 pub allow_unknown: Option<bool>,
181
182 pub ephemeral: Option<bool>,
185
186 pub approvable: Option<bool>,
190
191 pub requires_subscription: Option<bool>,
194
195 pub deliver_subject: Option<bool>,
198
199 pub subscribable: Option<bool>,
203
204 pub deliver_to_subject_owner: Option<bool>,
207
208 pub default_flags: Option<String>,
211
212 pub gated_by_parent_flag: Option<char>,
216
217 pub gated_by_subject_flag: Option<char>,
221
222 pub requires_acceptance: Option<bool>,
226
227 pub local_only: Option<bool>,
230
231 pub ttl: Option<u64>,
234
235 pub sync: Option<bool>,
238
239 pub federated: Option<bool>,
242}
243
244#[derive(Debug, Clone, Default)]
246pub struct ActionHooks {
247 pub on_create: HookImplementation,
249 pub on_receive: HookImplementation,
251 pub on_accept: HookImplementation,
253 pub on_reject: HookImplementation,
255}
256
257impl Serialize for ActionHooks {
259 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
260 where
261 S: serde::Serializer,
262 {
263 use serde::ser::SerializeStruct;
264 let mut state = serializer.serialize_struct("ActionHooks", 4)?;
265 state.serialize_field("on_create", &self.on_create)?;
266 state.serialize_field("on_receive", &self.on_receive)?;
267 state.serialize_field("on_accept", &self.on_accept)?;
268 state.serialize_field("on_reject", &self.on_reject)?;
269 state.end()
270 }
271}
272
273impl<'de> Deserialize<'de> for ActionHooks {
275 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
276 where
277 D: serde::Deserializer<'de>,
278 {
279 use serde::de::{self, MapAccess, Visitor};
280 #[allow(unused_imports)]
281 use std::fmt;
282
283 enum Field {
284 Create,
285 Receive,
286 Accept,
287 Reject,
288 }
289
290 impl<'de> Deserialize<'de> for Field {
291 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
292 where
293 D: serde::Deserializer<'de>,
294 {
295 struct FieldVisitor;
296
297 impl<'de> Visitor<'de> for FieldVisitor {
298 type Value = Field;
299
300 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
301 formatter
302 .write_str("`on_create`, `on_receive`, `on_accept`, or `on_reject`")
303 }
304
305 fn visit_str<E>(self, value: &str) -> Result<Field, E>
306 where
307 E: de::Error,
308 {
309 match value {
310 "on_create" => Ok(Field::Create),
311 "on_receive" => Ok(Field::Receive),
312 "on_accept" => Ok(Field::Accept),
313 "on_reject" => Ok(Field::Reject),
314 _ => Err(de::Error::unknown_field(value, FIELDS)),
315 }
316 }
317 }
318
319 deserializer.deserialize_identifier(FieldVisitor)
320 }
321 }
322
323 struct ActionHooksVisitor;
324
325 impl<'de> Visitor<'de> for ActionHooksVisitor {
326 type Value = ActionHooks;
327
328 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
329 formatter.write_str("struct ActionHooks")
330 }
331
332 fn visit_map<V>(self, mut map: V) -> Result<ActionHooks, V::Error>
333 where
334 V: MapAccess<'de>,
335 {
336 let mut on_create = HookImplementation::None;
337 let mut on_receive = HookImplementation::None;
338 let mut on_accept = HookImplementation::None;
339 let mut on_reject = HookImplementation::None;
340
341 while let Some(key) = map.next_key()? {
342 match key {
343 Field::Create => {
344 if !matches!(on_create, HookImplementation::None) {
345 return Err(de::Error::duplicate_field("on_create"));
346 }
347 on_create = map.next_value()?;
348 }
349 Field::Receive => {
350 if !matches!(on_receive, HookImplementation::None) {
351 return Err(de::Error::duplicate_field("on_receive"));
352 }
353 on_receive = map.next_value()?;
354 }
355 Field::Accept => {
356 if !matches!(on_accept, HookImplementation::None) {
357 return Err(de::Error::duplicate_field("on_accept"));
358 }
359 on_accept = map.next_value()?;
360 }
361 Field::Reject => {
362 if !matches!(on_reject, HookImplementation::None) {
363 return Err(de::Error::duplicate_field("on_reject"));
364 }
365 on_reject = map.next_value()?;
366 }
367 }
368 }
369
370 Ok(ActionHooks { on_create, on_receive, on_accept, on_reject })
371 }
372 }
373
374 const FIELDS: &[&str] = &["on_create", "on_receive", "on_accept", "on_reject"];
375 deserializer.deserialize_struct("ActionHooks", FIELDS, ActionHooksVisitor)
376 }
377}
378
379#[skip_serializing_none]
381#[derive(Debug, Clone, Serialize, Deserialize)]
382pub struct PermissionRules {
383 pub can_create: Option<String>,
384 pub can_receive: Option<String>,
385 pub requires_following: Option<bool>,
386 pub requires_connected: Option<bool>,
387}
388
389#[derive(Debug, Clone, Serialize, Deserialize)]
391#[serde(tag = "op", rename_all = "snake_case")]
392pub enum Operation {
393 UpdateProfile {
395 target: Expression,
396 set: HashMap<String, Expression>,
397 },
398 GetProfile {
399 target: Expression,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 r#as: Option<String>,
402 },
403
404 CreateAction {
406 r#type: String,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 subtype: Option<Expression>,
409 #[serde(skip_serializing_if = "Option::is_none")]
410 audience: Option<Expression>,
411 #[serde(skip_serializing_if = "Option::is_none")]
412 parent: Option<Expression>,
413 #[serde(skip_serializing_if = "Option::is_none")]
414 subject: Option<Expression>,
415 #[serde(skip_serializing_if = "Option::is_none")]
416 content: Option<Expression>,
417 #[serde(skip_serializing_if = "Option::is_none")]
418 attachments: Option<Expression>,
419 },
420 GetAction {
421 #[serde(skip_serializing_if = "Option::is_none")]
422 key: Option<Expression>,
423 #[serde(skip_serializing_if = "Option::is_none")]
424 action_id: Option<Expression>,
425 #[serde(skip_serializing_if = "Option::is_none")]
426 r#as: Option<String>,
427 },
428 UpdateAction {
429 target: Expression,
430 set: HashMap<String, UpdateValue>,
431 },
432 DeleteAction {
433 target: Expression,
434 },
435
436 If {
438 condition: Expression,
439 then: Vec<Operation>,
440 #[serde(skip_serializing_if = "Option::is_none")]
441 r#else: Option<Vec<Operation>>,
442 },
443 Switch {
444 value: Expression,
445 cases: HashMap<String, Vec<Operation>>,
446 #[serde(skip_serializing_if = "Option::is_none")]
447 default: Option<Vec<Operation>>,
448 },
449 Foreach {
450 array: Expression,
451 #[serde(skip_serializing_if = "Option::is_none")]
452 r#as: Option<String>,
453 r#do: Vec<Operation>,
454 },
455 Return {
456 #[serde(skip_serializing_if = "Option::is_none")]
457 value: Option<Expression>,
458 },
459
460 Set {
462 var: String,
463 value: Expression,
464 },
465 Get {
466 var: String,
467 from: Expression,
468 },
469 Merge {
470 objects: Vec<Expression>,
471 r#as: String,
472 },
473
474 BroadcastToFollowers {
476 action_id: Expression,
477 token: Expression,
478 },
479 SendToAudience {
480 action_id: Expression,
481 token: Expression,
482 audience: Expression,
483 },
484
485 CreateNotification {
487 user: Expression,
488 r#type: Expression,
489 action_id: Expression,
490 #[serde(skip_serializing_if = "Option::is_none")]
491 priority: Option<Expression>,
492 },
493
494 Log {
496 #[serde(skip_serializing_if = "Option::is_none")]
497 level: Option<String>,
498 message: Expression,
499 },
500 Abort {
501 error: Expression,
502 #[serde(skip_serializing_if = "Option::is_none")]
503 code: Option<String>,
504 },
505}
506
507#[derive(Debug, Clone, Serialize, Deserialize)]
509#[serde(untagged)]
510pub enum UpdateValue {
511 Direct(Expression),
512 Increment { increment: Expression },
513 Decrement { decrement: Expression },
514 Set { set: Expression },
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize)]
519#[serde(untagged)]
520pub enum Expression {
521 Null,
523 Bool(bool),
524 Number(f64),
525 String(String),
526
527 Comparison(Box<ComparisonExpr>),
529 Logical(Box<LogicalExpr>),
530 Arithmetic(Box<ArithmeticExpr>),
531 StringOp(Box<StringOpExpr>),
532 Ternary(Box<TernaryExpr>),
533 Coalesce(Box<CoalesceExpr>),
534}
535
536#[derive(Debug, Clone, Serialize, Deserialize)]
538#[serde(rename_all = "lowercase")]
539pub enum ComparisonExpr {
540 Eq([Expression; 2]),
541 Ne([Expression; 2]),
542 Gt([Expression; 2]),
543 Gte([Expression; 2]),
544 Lt([Expression; 2]),
545 Lte([Expression; 2]),
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize)]
550#[serde(rename_all = "lowercase")]
551pub enum LogicalExpr {
552 And(Vec<Expression>),
553 Or(Vec<Expression>),
554 Not(Expression),
555}
556
557#[derive(Debug, Clone, Serialize, Deserialize)]
559#[serde(rename_all = "lowercase")]
560pub enum ArithmeticExpr {
561 Add(Vec<Expression>),
562 Subtract([Expression; 2]),
563 Multiply(Vec<Expression>),
564 Divide([Expression; 2]),
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
569#[serde(rename_all = "lowercase")]
570pub enum StringOpExpr {
571 Concat(Vec<Expression>),
572 Contains([Expression; 2]),
573 StartsWith([Expression; 2]),
574 EndsWith([Expression; 2]),
575}
576
577#[derive(Debug, Clone, Serialize, Deserialize)]
579pub struct TernaryExpr {
580 pub r#if: Expression,
581 pub then: Expression,
582 pub r#else: Expression,
583}
584
585#[derive(Debug, Clone, Serialize, Deserialize)]
587pub struct CoalesceExpr {
588 pub coalesce: Vec<Expression>,
589}
590
591