Skip to main content

cccc_sdk/
connect.rs

1use serde::Serialize;
2use serde_json::{Map, Value};
3
4use crate::{CCCCClient, Error, MessageMode, Result};
5
6/// Qualified directory read. This does not refresh peers or start Actors.
7#[derive(Clone, Debug, Serialize)]
8pub struct ConnectCatalogOptions {
9    pub group_id: String,
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub instance_id: Option<String>,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub target_group_id: Option<String>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub by: Option<String>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub after: Option<String>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub limit: Option<u32>,
20}
21
22/// Durable remote acceptance is not proof of delivery. Retain client_id on retry.
23#[derive(Clone, Debug, Serialize)]
24pub struct ConnectSendOptions {
25    pub group_id: String,
26    pub instance_id: String,
27    pub target_group_id: String,
28    pub client_id: String,
29    pub text: String,
30    pub message_mode: MessageMode,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub by: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub to: Option<Vec<String>>,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub format: Option<String>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub insight: Option<String>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub attachments: Option<Vec<Value>>,
41}
42
43impl ConnectSendOptions {
44    fn arguments(&self) -> Result<Map<String, Value>> {
45        if self.client_id.trim().is_empty() || self.client_id.len() > 128 {
46            return Err(Error::InvalidArgument(
47                "client_id must be non-empty and at most 128 UTF-8 bytes".into(),
48            ));
49        }
50        Ok(serde_json::from_value(serde_json::to_value(self)?)?)
51    }
52}
53
54impl CCCCClient {
55    pub fn connect_catalog(&self, options: &ConnectCatalogOptions) -> Result<Map<String, Value>> {
56        self.call(
57            "connect_catalog",
58            serde_json::from_value(serde_json::to_value(options)?)?,
59        )
60    }
61
62    pub fn connect_send(&self, options: &ConnectSendOptions) -> Result<Map<String, Value>> {
63        self.call("connect_send", options.arguments()?)
64    }
65
66    /// Paths are read by the daemon within the source Group's active scope.
67    pub fn connect_send_files(
68        &self,
69        options: &ConnectSendOptions,
70        paths: &[String],
71    ) -> Result<Map<String, Value>> {
72        if paths.is_empty() || paths.iter().any(|path| path.trim().is_empty()) {
73            return Err(Error::InvalidArgument(
74                "paths must contain one or more non-empty paths".into(),
75            ));
76        }
77        if options.attachments.is_some() {
78            return Err(Error::InvalidArgument(
79                "connect_send_files takes paths, not attachments".into(),
80            ));
81        }
82        let mut args = options.arguments()?;
83        args.insert("paths".into(), serde_json::to_value(paths)?);
84        self.call("connect_send_files", args)
85    }
86}