dittolive_ditto/ditto/init/config.rs
1use std::{collections::HashMap, path::PathBuf};
2
3use serde::{Deserialize, Serialize};
4use serde_with::{base64::Base64, serde_as};
5use url::Url;
6
7/// Configuration options required to initialize a [`Ditto`] instance.
8///
9/// # Example
10///
11/// ```
12/// # use dittolive_ditto::prelude::*;
13/// const YOUR_DATABASE_ID: &str = "REPLACE ME WITH YOUR APP/DATABASE ID";
14/// const YOUR_AUTH_URL: &str = "https://your-app.cloud.ditto.live";
15/// const YOUR_PRIVATE_KEY: &str = "REPLACE ME WITH YOUR PRIVATE KEY";
16///
17/// let connect = DittoConfigConnect::Server {
18/// url: YOUR_AUTH_URL.parse().unwrap(),
19/// };
20/// let connect = DittoConfigConnect::SmallPeersOnly {
21/// private_key: Some(YOUR_PRIVATE_KEY.as_bytes().to_vec()),
22/// };
23/// let connect = DittoConfigConnect::SmallPeersOnly { private_key: None };
24/// let config = DittoConfig::new(YOUR_DATABASE_ID, connect);
25/// ```
26///
27/// [`Ditto`]: crate::Ditto
28#[non_exhaustive]
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct DittoConfig {
31 /// An ID used to distinguish between Ditto database instantiations.
32 ///
33 /// **Note**: This was formerly known as `appId`.
34 pub database_id: String,
35
36 /// The connection configuration this device uses to connect to a Ditto mesh.
37 pub connect: DittoConfigConnect,
38
39 /// The directory where Ditto will store its data.
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub persistence_directory: Option<PathBuf>,
42
43 #[doc(hidden)]
44 #[serde(default)]
45 pub experimental: DittoConfigExperimental,
46
47 /// System Parameters that should be set on Ditto initialization.
48 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
49 pub system_parameters: HashMap<String, serde_json::Value>,
50}
51
52#[derive(Debug, Serialize, Deserialize)]
53pub(crate) struct InternalConfig {
54 /// SDK-computed v4 default persistence directory path for v4→v5 migration fallback.
55 ///
56 /// This is set automatically by `Ditto::open()` / `Ditto::open_sync()` when
57 /// `persistence_directory` is `None`. It should not normally be set by users.
58 ///
59 /// See: SDKS-3187
60 #[serde(default, skip_serializing_if = "Option::is_none")]
61 pub(crate) legacy_persistence_directory: Option<PathBuf>,
62}
63
64#[derive(Debug, Serialize, Deserialize)]
65pub(crate) struct ActualConfig {
66 #[serde(flatten)]
67 pub(crate) customer_facing: DittoConfig,
68 #[serde(flatten)]
69 pub(crate) internal: InternalConfig,
70}
71
72impl DittoConfig {
73 /// Create a new [`DittoConfig`] instance from a database ID and connection configuration.
74 pub fn new<S: Into<String>>(database_id: S, connect: DittoConfigConnect) -> Self {
75 DittoConfig {
76 database_id: database_id.into(),
77 connect,
78 persistence_directory: None,
79 experimental: Default::default(),
80 system_parameters: Default::default(),
81 }
82 }
83
84 /// Optionally specify a custom persistence directory.
85 pub fn with_persistence_directory<P: Into<PathBuf>>(mut self, path: P) -> Self {
86 self.persistence_directory = Some(path.into());
87 self
88 }
89}
90
91/// Specifies the connection mechanism to be used by the [`Ditto`] instance.
92///
93/// # Example
94///
95/// ```
96/// use dittolive_ditto::prelude::*;
97/// let connect_config = DittoConfigConnect::Server {
98/// url: "https://example.com/your-server-url".parse().unwrap(),
99/// };
100/// let connect_config = DittoConfigConnect::SmallPeersOnly {
101/// private_key: Some("REPLACE_ME_WITH_YOUR_PRIVATE_KEY".bytes().collect()),
102/// };
103/// let connect_config = DittoConfigConnect::SmallPeersOnly { private_key: None };
104/// ```
105///
106/// [`Ditto`]: crate::Ditto
107#[serde_as]
108#[non_exhaustive]
109#[derive(Debug, Clone, Serialize, Deserialize)]
110#[serde(tag = "type", rename_all = "snake_case")]
111pub enum DittoConfigConnect {
112 /// Connect to a Ditto "Big Peer" using the provided URL.
113 ///
114 /// When using `Server` mode, you _must_ provide an authentication expiration
115 /// handler using [`ditto.auth()?.set_expiration_handler(...)`][0].
116 ///
117 /// [0]: crate::prelude::DittoAuthenticator::set_expiration_handler
118 Server {
119 /// The "Auth URL" from the Ditto Portal, if using a Ditto-hosted Big Peer.
120 ///
121 /// If using a self-hosted Big Peer, provide your custom authentication URL.
122 ///
123 /// # Example
124 ///
125 /// ```text
126 /// // Where this subdomain is your Ditto `databaseId` (formerly `AppId`)
127 /// https://00000000-0000-0000-0000-000000000000.cloud.ditto.live
128 /// ```
129 url: Url,
130 },
131 /// Connect only to other Ditto small peers, using the provided private key.
132 ///
133 /// If no private key is provided, sync traffic will be UNENCRYPTED. This
134 /// mode should NOT be used in production, only for development and testing
135 /// purposes.
136 SmallPeersOnly {
137 /// Optional private key for encrypting sync traffic between small peers.
138 ///
139 /// If no private key is provided, sync traffic will be unencrypted. This
140 /// is meant only for development and testing use-cases. All production
141 /// use-cases should provide a private key so traffic will be encrypted.
142 #[serde_as(as = "Option<Base64>")]
143 #[serde(skip_serializing_if = "Option::is_none")]
144 private_key: Option<Vec<u8>>,
145 },
146}
147
148#[doc(hidden)]
149#[non_exhaustive]
150#[derive(Debug, Default, Clone, Serialize, Deserialize)]
151pub struct DittoConfigExperimental {
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub passphrase: Option<String>,
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_config_json_serialization_basic() {
162 let config = DittoConfig::new(
163 "test-database-id",
164 DittoConfigConnect::SmallPeersOnly { private_key: None },
165 );
166
167 let json = serde_json::to_string(&config).unwrap();
168 let deserialized: DittoConfig = serde_json::from_str(&json).unwrap();
169
170 assert_eq!(config.database_id, deserialized.database_id);
171 assert!(deserialized.system_parameters.is_empty());
172 assert!(deserialized.persistence_directory.is_none());
173 }
174
175 #[test]
176 fn test_config_json_with_system_parameters() {
177 let json = r#"{
178 "database_id": "test-database-id",
179 "connect": {
180 "type": "small_peers_only"
181 },
182 "system_parameters": {
183 "my_system_parameter": 42
184 }
185 }"#;
186 let deserialized: DittoConfig = serde_json::from_str(json).unwrap();
187
188 assert_eq!(deserialized.database_id, "test-database-id");
189 assert_eq!(
190 deserialized.system_parameters.get("my_system_parameter"),
191 Some(&serde_json::json!(42))
192 );
193 }
194
195 #[test]
196 fn test_config_cbor_from_default() {
197 let payload = ffi_sdk::dittoffi_ditto_config_default();
198 let _deserialized: DittoConfig = serde_cbor::from_slice(&payload).unwrap();
199 }
200}