1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value as JsonValue;
5
6#[derive(Debug, Serialize, Deserialize, Clone)]
7#[serde(rename_all = "camelCase")]
8pub struct ZoneId {
9 pub zone_name: String,
10}
11
12impl Default for ZoneId {
13 fn default() -> Self {
14 Self {
15 zone_name: "Notes".into(),
16 }
17 }
18}
19
20#[derive(Debug, Serialize, Deserialize, Clone)]
22pub struct CkField {
23 #[serde(rename = "type")]
24 pub kind: String,
25 pub value: JsonValue,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub is_encrypted: Option<bool>,
28}
29
30impl CkField {
31 pub fn string(v: impl Into<String>) -> Self {
32 Self {
33 kind: "STRING".into(),
34 value: JsonValue::String(v.into()),
35 is_encrypted: None,
36 }
37 }
38
39 pub fn string_encrypted(v: impl Into<String>) -> Self {
40 Self {
41 kind: "STRING".into(),
42 value: JsonValue::String(v.into()),
43 is_encrypted: Some(true),
44 }
45 }
46
47 pub fn string_null() -> Self {
48 Self {
49 kind: "STRING".into(),
50 value: JsonValue::Null,
51 is_encrypted: None,
52 }
53 }
54
55 pub fn string_list(v: Vec<String>) -> Self {
56 Self {
57 kind: "STRING_LIST".into(),
58 value: serde_json::to_value(v).unwrap(),
59 is_encrypted: None,
60 }
61 }
62
63 pub fn string_list_null() -> Self {
64 Self {
65 kind: "STRING_LIST".into(),
66 value: JsonValue::Null,
67 is_encrypted: None,
68 }
69 }
70
71 pub fn int64(v: i64) -> Self {
72 Self {
73 kind: "INT64".into(),
74 value: JsonValue::Number(v.into()),
75 is_encrypted: None,
76 }
77 }
78
79 pub fn timestamp(ms: i64) -> Self {
80 Self {
81 kind: "TIMESTAMP".into(),
82 value: JsonValue::Number(ms.into()),
83 is_encrypted: None,
84 }
85 }
86
87 pub fn timestamp_null() -> Self {
88 Self {
89 kind: "TIMESTAMP".into(),
90 value: JsonValue::Null,
91 is_encrypted: None,
92 }
93 }
94
95 pub fn bytes(b64: impl Into<String>) -> Self {
96 Self {
97 kind: "BYTES".into(),
98 value: JsonValue::String(b64.into()),
99 is_encrypted: None,
100 }
101 }
102
103 pub fn asset_id(receipt: AssetReceipt) -> Self {
104 Self {
105 kind: "ASSETID".into(),
106 value: serde_json::to_value(receipt).unwrap(),
107 is_encrypted: None,
108 }
109 }
110}
111
112#[derive(Debug, Serialize, Deserialize, Clone)]
113#[serde(rename_all = "camelCase")]
114pub struct AssetToken {
115 pub record_type: String,
116 pub record_name: String,
117 pub field_name: String,
118}
119
120#[derive(Debug, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct AssetUploadRequest {
123 pub zone_id: ZoneId,
124 pub tokens: Vec<AssetToken>,
125}
126
127#[derive(Debug, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct AssetUploadResponse {
130 pub tokens: Vec<AssetUploadToken>,
131}
132
133#[derive(Debug, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct AssetUploadToken {
136 pub record_name: String,
137 pub field_name: String,
138 pub url: String,
139}
140
141#[derive(Debug, Serialize, Deserialize, Clone)]
142#[serde(rename_all = "camelCase")]
143pub struct AssetReceipt {
144 pub file_checksum: String,
145 pub receipt: String,
146 pub size: i64,
147 #[serde(skip_serializing_if = "Option::is_none")]
148 pub wrapping_key: Option<String>,
149 #[serde(skip_serializing_if = "Option::is_none")]
150 pub reference_checksum: Option<String>,
151}
152
153#[derive(Debug, Serialize, Deserialize)]
154#[serde(rename_all = "camelCase")]
155pub struct AssetUploadResult {
156 pub single_file: AssetReceipt,
157}
158
159pub type Fields = HashMap<String, CkField>;
160
161#[derive(Debug, Serialize, Deserialize)]
162#[serde(rename_all = "camelCase")]
163pub struct CkRecord {
164 pub record_name: String,
165 pub record_type: String,
166 pub fields: Fields,
167 #[serde(skip_serializing_if = "Option::is_none")]
168 pub record_change_tag: Option<String>,
169 #[serde(default)]
170 pub deleted: bool,
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub server_error_code: Option<String>,
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub reason: Option<String>,
176}
177
178#[derive(Debug, Serialize)]
179#[serde(rename_all = "camelCase")]
180pub struct ModifyOperation {
181 pub operation_type: String, pub record: CkRecord,
183}
184
185#[derive(Debug, Serialize)]
186#[serde(rename_all = "camelCase")]
187pub struct ModifyRequest {
188 pub operations: Vec<ModifyOperation>,
189 pub zone_id: ZoneId,
190}
191
192#[derive(Debug, Deserialize)]
193#[serde(rename_all = "camelCase")]
194pub struct ModifyResponse {
195 pub records: Vec<CkRecord>,
196}
197
198#[derive(Debug, Serialize, Clone)]
199#[serde(rename_all = "camelCase")]
200pub struct CkQuery {
201 pub record_type: String,
202 #[serde(skip_serializing_if = "Vec::is_empty")]
203 pub filter_by: Vec<CkFilter>,
204 #[serde(skip_serializing_if = "Vec::is_empty")]
205 pub sort_by: Vec<CkSort>,
206}
207
208#[derive(Debug, Serialize, Clone)]
209#[serde(rename_all = "camelCase")]
210pub struct CkFilter {
211 pub field_name: String,
212 pub comparator: String,
213 pub field_value: CkFilterValue,
214}
215
216#[derive(Debug, Serialize, Clone)]
217pub struct CkFilterValue {
218 pub value: JsonValue,
219 #[serde(rename = "type")]
220 pub kind: String,
221}
222
223#[derive(Debug, Serialize, Clone)]
224#[serde(rename_all = "camelCase")]
225pub struct CkSort {
226 pub field_name: String,
227 pub ascending: bool,
228}
229
230#[derive(Debug, Serialize)]
231#[serde(rename_all = "camelCase")]
232pub struct QueryRequest {
233 pub zone_id: ZoneId,
234 pub query: CkQuery,
235 #[serde(skip_serializing_if = "Option::is_none")]
236 pub results_limit: Option<usize>,
237 #[serde(skip_serializing_if = "Option::is_none")]
238 pub desired_keys: Option<Vec<String>>,
239 #[serde(skip_serializing_if = "Option::is_none")]
240 pub continuation_marker: Option<String>,
241}
242
243#[derive(Debug, Deserialize)]
244#[serde(rename_all = "camelCase")]
245pub struct QueryResponse {
246 pub records: Vec<CkRecord>,
247 #[serde(default)]
248 pub continuation_marker: Option<String>,
249}
250
251#[derive(Debug, Serialize)]
252#[serde(rename_all = "camelCase")]
253pub struct LookupRequest {
254 pub records: Vec<LookupRecord>,
255 pub zone_id: ZoneId,
256}
257
258#[derive(Debug, Serialize)]
259#[serde(rename_all = "camelCase")]
260pub struct LookupRecord {
261 pub record_name: String,
262}
263
264#[derive(Debug, Deserialize)]
265#[serde(rename_all = "camelCase")]
266pub struct LookupResponse {
267 pub records: Vec<CkRecord>,
268}
269
270impl CkRecord {
271 pub fn str_field(&self, name: &str) -> Option<&str> {
272 self.fields.get(name)?.value.as_str()
273 }
274
275 pub fn i64_field(&self, name: &str) -> Option<i64> {
276 self.fields.get(name)?.value.as_i64()
277 }
278
279 pub fn bool_field(&self, name: &str) -> Option<bool> {
280 self.i64_field(name).map(|v| v != 0)
281 }
282
283 pub fn string_list_field(&self, name: &str) -> Vec<String> {
284 self.fields
285 .get(name)
286 .and_then(|f| f.value.as_array())
287 .map(|arr| {
288 arr.iter()
289 .filter_map(|v| v.as_str().map(str::to_string))
290 .collect()
291 })
292 .unwrap_or_default()
293 }
294}