1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6#[derive(Debug, Clone, Serialize, Deserialize, Default)]
7#[serde(rename_all = "camelCase")]
8pub struct NotebookSource {
9 #[serde(skip_serializing_if = "Option::is_none")]
10 pub metadata: Option<NotebookSourceMetadata>,
11 pub name: String,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub settings: Option<NotebookSourceSettings>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub source_id: Option<NotebookSourceId>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub title: Option<String>,
18 #[serde(flatten)]
19 pub extra: HashMap<String, Value>,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize, Default)]
23#[serde(rename_all = "camelCase")]
24pub struct NotebookSourceMetadata {
25 #[serde(skip_serializing_if = "Option::is_none")]
26 pub source_added_timestamp: Option<String>,
27 #[serde(skip_serializing_if = "Option::is_none")]
28 pub word_count: Option<u64>,
29 #[serde(skip_serializing_if = "Option::is_none")]
30 pub youtube_metadata: Option<NotebookSourceYoutubeMetadata>,
31 #[serde(flatten)]
32 pub extra: HashMap<String, Value>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, Default)]
36#[serde(rename_all = "camelCase")]
37pub struct NotebookSourceYoutubeMetadata {
38 #[serde(skip_serializing_if = "Option::is_none")]
39 pub channel_name: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub video_id: Option<String>,
42 #[serde(flatten)]
43 pub extra: HashMap<String, Value>,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, Default)]
47#[serde(rename_all = "camelCase")]
48pub struct NotebookSourceSettings {
49 #[serde(skip_serializing_if = "Option::is_none")]
50 pub status: Option<String>,
51 #[serde(flatten)]
52 pub extra: HashMap<String, Value>,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize, Default)]
56#[serde(rename_all = "camelCase")]
57pub struct NotebookSourceId {
58 #[serde(skip_serializing_if = "Option::is_none")]
59 pub id: Option<String>,
60 #[serde(flatten)]
61 pub extra: HashMap<String, Value>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(untagged)]
66pub enum UserContent {
67 Web {
68 #[serde(rename = "webContent")]
69 web_content: WebContent,
70 },
71 Text {
72 #[serde(rename = "textContent")]
73 text_content: TextContent,
74 },
75 GoogleDrive {
76 #[serde(rename = "googleDriveContent")]
77 google_drive_content: GoogleDriveContent,
78 },
79 Video {
80 #[serde(rename = "videoContent")]
81 video_content: VideoContent,
82 },
83}
84
85impl UserContent {
86 pub fn web(url: String, source_name: Option<String>) -> Self {
87 Self::Web {
88 web_content: WebContent { url, source_name },
89 }
90 }
91
92 pub fn text(content: String, source_name: Option<String>) -> Self {
93 Self::Text {
94 text_content: TextContent {
95 content,
96 source_name,
97 },
98 }
99 }
100
101 pub fn google_drive(
102 document_id: String,
103 mime_type: String,
104 source_name: Option<String>,
105 ) -> Self {
106 Self::GoogleDrive {
107 google_drive_content: GoogleDriveContent {
108 document_id,
109 mime_type,
110 source_name,
111 },
112 }
113 }
114
115 pub fn video(url: String) -> Self {
116 Self::Video {
117 video_content: VideoContent { url },
118 }
119 }
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, Default)]
123#[serde(rename_all = "camelCase")]
124pub struct WebContent {
125 pub url: String,
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub source_name: Option<String>,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, Default)]
131#[serde(rename_all = "camelCase")]
132pub struct TextContent {
133 pub content: String,
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub source_name: Option<String>,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize, Default)]
139#[serde(rename_all = "camelCase")]
140pub struct GoogleDriveContent {
141 pub document_id: String,
142 pub mime_type: String,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub source_name: Option<String>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, Default)]
148pub struct VideoContent {
149 #[serde(rename = "youtubeUrl")]
150 pub url: String,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, Default)]
154#[serde(rename_all = "camelCase")]
155pub struct BatchCreateSourcesRequest {
156 #[serde(rename = "userContents")]
157 pub user_contents: Vec<UserContent>,
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize, Default)]
161#[serde(rename_all = "camelCase")]
162pub struct BatchCreateSourcesResponse {
163 #[serde(default)]
164 pub sources: Vec<NotebookSource>,
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub error_count: Option<i32>,
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize, Default)]
170#[serde(rename_all = "camelCase")]
171pub struct BatchDeleteSourcesRequest {
172 pub names: Vec<String>,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize, Default)]
176#[serde(rename_all = "camelCase")]
177pub struct BatchDeleteSourcesResponse {
178 #[serde(flatten)]
179 pub extra: HashMap<String, Value>,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, Default)]
183#[serde(rename_all = "camelCase")]
184pub struct UploadSourceFileResponse {
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub source_id: Option<NotebookSourceId>,
187 #[serde(flatten)]
188 pub extra: HashMap<String, Value>,
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
197 fn test_user_content_web_constructor() {
198 let url = "https://example.com".to_string();
199 let source_name = Some("Test Web Source".to_string());
200
201 let content = UserContent::web(url.clone(), source_name.clone());
202
203 match content {
204 UserContent::Web { web_content } => {
205 assert_eq!(web_content.url, url);
206 assert_eq!(web_content.source_name, source_name);
207 }
208 _ => panic!("Expected UserContent::Web variant"),
209 }
210 }
211
212 #[test]
213 fn test_user_content_web_constructor_without_name() {
214 let url = "https://example.com".to_string();
215
216 let content = UserContent::web(url.clone(), None);
217
218 match content {
219 UserContent::Web { web_content } => {
220 assert_eq!(web_content.url, url);
221 assert_eq!(web_content.source_name, None);
222 }
223 _ => panic!("Expected UserContent::Web variant"),
224 }
225 }
226
227 #[test]
228 fn test_user_content_text_constructor() {
229 let text = "Sample text content".to_string();
230 let source_name = Some("Test Text".to_string());
231
232 let content = UserContent::text(text.clone(), source_name.clone());
233
234 match content {
235 UserContent::Text { text_content } => {
236 assert_eq!(text_content.content, text);
237 assert_eq!(text_content.source_name, source_name);
238 }
239 _ => panic!("Expected UserContent::Text variant"),
240 }
241 }
242
243 #[test]
244 fn test_user_content_google_drive_constructor() {
245 let document_id = "doc-12345".to_string();
246 let mime_type = "application/pdf".to_string();
247 let source_name = Some("Drive Document".to_string());
248
249 let content =
250 UserContent::google_drive(document_id.clone(), mime_type.clone(), source_name.clone());
251
252 match content {
253 UserContent::GoogleDrive {
254 google_drive_content,
255 } => {
256 assert_eq!(google_drive_content.document_id, document_id);
257 assert_eq!(google_drive_content.mime_type, mime_type);
258 assert_eq!(google_drive_content.source_name, source_name);
259 }
260 _ => panic!("Expected UserContent::GoogleDrive variant"),
261 }
262 }
263
264 #[test]
265 fn test_user_content_video_constructor() {
266 let url = "https://youtube.com/watch?v=abc123".to_string();
267
268 let content = UserContent::video(url.clone());
269
270 match content {
271 UserContent::Video { video_content } => {
272 assert_eq!(video_content.url, url);
273 }
274 _ => panic!("Expected UserContent::Video variant"),
275 }
276 }
277
278 #[test]
280 fn test_user_content_web_serialization() {
281 let content = UserContent::web(
282 "https://example.com".to_string(),
283 Some("Web Source".to_string()),
284 );
285
286 let json = serde_json::to_value(&content).unwrap();
287
288 assert_eq!(json["webContent"]["url"], "https://example.com");
289 assert_eq!(json["webContent"]["sourceName"], "Web Source");
290 }
291
292 #[test]
293 fn test_user_content_web_deserialization() {
294 let json = serde_json::json!({
295 "webContent": {
296 "url": "https://example.com",
297 "sourceName": "Web Source"
298 }
299 });
300
301 let content: UserContent = serde_json::from_value(json).unwrap();
302
303 match content {
304 UserContent::Web { web_content } => {
305 assert_eq!(web_content.url, "https://example.com");
306 assert_eq!(web_content.source_name, Some("Web Source".to_string()));
307 }
308 _ => panic!("Expected UserContent::Web variant"),
309 }
310 }
311
312 #[test]
313 fn test_user_content_text_serialization() {
314 let content = UserContent::text("Sample text".to_string(), Some("Text Source".to_string()));
315
316 let json = serde_json::to_value(&content).unwrap();
317
318 assert_eq!(json["textContent"]["content"], "Sample text");
319 assert_eq!(json["textContent"]["sourceName"], "Text Source");
320 }
321
322 #[test]
323 fn test_user_content_text_deserialization() {
324 let json = serde_json::json!({
325 "textContent": {
326 "content": "Sample text",
327 "sourceName": "Text Source"
328 }
329 });
330
331 let content: UserContent = serde_json::from_value(json).unwrap();
332
333 match content {
334 UserContent::Text { text_content } => {
335 assert_eq!(text_content.content, "Sample text");
336 assert_eq!(text_content.source_name, Some("Text Source".to_string()));
337 }
338 _ => panic!("Expected UserContent::Text variant"),
339 }
340 }
341
342 #[test]
343 fn test_user_content_google_drive_serialization() {
344 let content = UserContent::google_drive(
345 "doc-123".to_string(),
346 "application/pdf".to_string(),
347 Some("Drive Doc".to_string()),
348 );
349
350 let json = serde_json::to_value(&content).unwrap();
351
352 assert_eq!(json["googleDriveContent"]["documentId"], "doc-123");
353 assert_eq!(json["googleDriveContent"]["mimeType"], "application/pdf");
354 assert_eq!(json["googleDriveContent"]["sourceName"], "Drive Doc");
355 }
356
357 #[test]
358 fn test_user_content_google_drive_deserialization() {
359 let json = serde_json::json!({
360 "googleDriveContent": {
361 "documentId": "doc-123",
362 "mimeType": "application/pdf",
363 "sourceName": "Drive Doc"
364 }
365 });
366
367 let content: UserContent = serde_json::from_value(json).unwrap();
368
369 match content {
370 UserContent::GoogleDrive {
371 google_drive_content,
372 } => {
373 assert_eq!(google_drive_content.document_id, "doc-123");
374 assert_eq!(google_drive_content.mime_type, "application/pdf");
375 assert_eq!(
376 google_drive_content.source_name,
377 Some("Drive Doc".to_string())
378 );
379 }
380 _ => panic!("Expected UserContent::GoogleDrive variant"),
381 }
382 }
383
384 #[test]
385 fn test_user_content_video_serialization() {
386 let content = UserContent::video("https://youtube.com/watch?v=abc".to_string());
387
388 let json = serde_json::to_value(&content).unwrap();
389
390 assert_eq!(
391 json["videoContent"]["youtubeUrl"],
392 "https://youtube.com/watch?v=abc"
393 );
394 }
395
396 #[test]
397 fn test_user_content_video_deserialization() {
398 let json = serde_json::json!({
399 "videoContent": {
400 "youtubeUrl": "https://youtube.com/watch?v=abc"
401 }
402 });
403
404 let content: UserContent = serde_json::from_value(json).unwrap();
405
406 match content {
407 UserContent::Video { video_content } => {
408 assert_eq!(video_content.url, "https://youtube.com/watch?v=abc");
409 }
410 _ => panic!("Expected UserContent::Video variant"),
411 }
412 }
413
414 #[test]
416 fn test_web_content_omits_none_source_name() {
417 let content = WebContent {
418 url: "https://example.com".to_string(),
419 source_name: None,
420 };
421
422 let json = serde_json::to_value(&content).unwrap();
423 let obj = json.as_object().unwrap();
424
425 assert!(!obj.contains_key("sourceName"));
427 assert_eq!(obj.get("url").unwrap(), "https://example.com");
428 }
429
430 #[test]
431 fn test_web_content_includes_some_source_name() {
432 let content = WebContent {
433 url: "https://example.com".to_string(),
434 source_name: Some("My Source".to_string()),
435 };
436
437 let json = serde_json::to_value(&content).unwrap();
438 let obj = json.as_object().unwrap();
439
440 assert!(obj.contains_key("sourceName"));
442 assert_eq!(obj.get("sourceName").unwrap(), "My Source");
443 }
444
445 #[test]
446 fn test_text_content_omits_none_source_name() {
447 let content = TextContent {
448 content: "text".to_string(),
449 source_name: None,
450 };
451
452 let json = serde_json::to_value(&content).unwrap();
453 let obj = json.as_object().unwrap();
454
455 assert!(!obj.contains_key("sourceName"));
456 assert_eq!(obj.get("content").unwrap(), "text");
457 }
458
459 #[test]
460 fn test_google_drive_content_omits_none_source_name() {
461 let content = GoogleDriveContent {
462 document_id: "doc-123".to_string(),
463 mime_type: "application/pdf".to_string(),
464 source_name: None,
465 };
466
467 let json = serde_json::to_value(&content).unwrap();
468 let obj = json.as_object().unwrap();
469
470 assert!(!obj.contains_key("sourceName"));
471 assert_eq!(obj.get("documentId").unwrap(), "doc-123");
472 assert_eq!(obj.get("mimeType").unwrap(), "application/pdf");
473 }
474
475 #[test]
476 fn test_notebook_source_omits_none_fields() {
477 let source = NotebookSource {
478 name: "projects/123/notebooks/456/sources/789".to_string(),
479 metadata: None,
480 settings: None,
481 source_id: None,
482 title: None,
483 extra: HashMap::new(),
484 };
485
486 let json = serde_json::to_value(&source).unwrap();
487 let obj = json.as_object().unwrap();
488
489 assert!(obj.contains_key("name"));
491 assert!(!obj.contains_key("metadata"));
492 assert!(!obj.contains_key("settings"));
493 assert!(!obj.contains_key("sourceId"));
494 assert!(!obj.contains_key("title"));
495 }
496
497 #[test]
498 fn test_notebook_source_includes_some_fields() {
499 let source = NotebookSource {
500 name: "projects/123/notebooks/456/sources/789".to_string(),
501 metadata: Some(NotebookSourceMetadata {
502 word_count: Some(1000),
503 ..Default::default()
504 }),
505 settings: Some(NotebookSourceSettings {
506 status: Some("ACTIVE".to_string()),
507 ..Default::default()
508 }),
509 source_id: Some(NotebookSourceId {
510 id: Some("source-123".to_string()),
511 ..Default::default()
512 }),
513 title: Some("My Document".to_string()),
514 extra: HashMap::new(),
515 };
516
517 let json = serde_json::to_value(&source).unwrap();
518 let obj = json.as_object().unwrap();
519
520 assert!(obj.contains_key("name"));
521 assert!(obj.contains_key("metadata"));
522 assert!(obj.contains_key("settings"));
523 assert!(obj.contains_key("sourceId"));
524 assert!(obj.contains_key("title"));
525 }
526
527 #[test]
528 fn test_batch_create_sources_request_serialization() {
529 let request = BatchCreateSourcesRequest {
530 user_contents: vec![
531 UserContent::web("https://example.com".to_string(), None),
532 UserContent::text("Sample text".to_string(), Some("Text".to_string())),
533 ],
534 };
535
536 let json = serde_json::to_value(&request).unwrap();
537
538 assert!(json["userContents"].is_array());
539 assert_eq!(json["userContents"].as_array().unwrap().len(), 2);
540 }
541
542 #[test]
543 fn test_batch_create_sources_response_deserialization() {
544 let json = serde_json::json!({
545 "sources": [
546 {
547 "name": "projects/123/notebooks/456/sources/789"
548 }
549 ],
550 "errorCount": 0
551 });
552
553 let response: BatchCreateSourcesResponse = serde_json::from_value(json).unwrap();
554
555 assert_eq!(response.sources.len(), 1);
556 assert_eq!(response.error_count, Some(0));
557 }
558
559 #[test]
560 fn test_batch_create_sources_response_empty_sources() {
561 let json = serde_json::json!({
562 "sources": []
563 });
564
565 let response: BatchCreateSourcesResponse = serde_json::from_value(json).unwrap();
566
567 assert_eq!(response.sources.len(), 0);
568 assert_eq!(response.error_count, None);
569 }
570
571 #[test]
572 fn test_notebook_source_metadata_youtube() {
573 let metadata = NotebookSourceMetadata {
574 source_added_timestamp: Some("2024-01-01T00:00:00Z".to_string()),
575 word_count: Some(5000),
576 youtube_metadata: Some(NotebookSourceYoutubeMetadata {
577 channel_name: Some("Test Channel".to_string()),
578 video_id: Some("abc123".to_string()),
579 extra: HashMap::new(),
580 }),
581 extra: HashMap::new(),
582 };
583
584 let json = serde_json::to_value(&metadata).unwrap();
585
586 assert_eq!(json["sourceAddedTimestamp"], "2024-01-01T00:00:00Z");
587 assert_eq!(json["wordCount"], 5000);
588 assert_eq!(json["youtubeMetadata"]["channelName"], "Test Channel");
589 assert_eq!(json["youtubeMetadata"]["videoId"], "abc123");
590 }
591
592 #[test]
593 fn test_upload_source_file_response_omits_none_source_id() {
594 let response = UploadSourceFileResponse {
595 source_id: None,
596 extra: HashMap::new(),
597 };
598
599 let json = serde_json::to_value(&response).unwrap();
600 let obj = json.as_object().unwrap();
601
602 assert!(!obj.contains_key("sourceId"));
603 }
604
605 #[test]
606 fn test_camel_case_serialization() {
607 let metadata = NotebookSourceMetadata {
609 source_added_timestamp: Some("2024-01-01T00:00:00Z".to_string()),
610 word_count: Some(100),
611 youtube_metadata: None,
612 extra: HashMap::new(),
613 };
614
615 let json = serde_json::to_string(&metadata).unwrap();
616
617 assert!(json.contains("sourceAddedTimestamp"));
619 assert!(json.contains("wordCount"));
620 assert!(!json.contains("source_added_timestamp"));
622 assert!(!json.contains("word_count"));
623 }
624}