1use serde::{Deserialize, Serialize};
2
3use crate::{
4 core::{
5 api_req::ApiRequest,
6 api_resp::{ApiResponseTrait, BaseResponse, ResponseFormat},
7 constants::AccessTokenType,
8 endpoints::cloud_docs::*,
9 req_option, SDKResult,
10 },
11 service::cloud_docs::sheets::v2::SpreadsheetService,
12};
13
14#[derive(Serialize, Debug, Default)]
16pub struct WriteImageRequest {
17 #[serde(skip)]
18 api_request: ApiRequest,
19 #[serde(skip)]
20 spreadsheet_token: String,
21 range: String,
24 image: Vec<u8>,
27 name: String,
29}
30
31impl WriteImageRequest {
32 pub fn builder() -> WriteImageRequestBuilder {
33 WriteImageRequestBuilder::default()
34 }
35}
36
37#[derive(Default)]
38pub struct WriteImageRequestBuilder {
39 request: WriteImageRequest,
40}
41
42impl WriteImageRequestBuilder {
43 pub fn spreadsheet_token(mut self, spreadsheet_token: impl ToString) -> Self {
44 self.request.spreadsheet_token = spreadsheet_token.to_string();
45 self
46 }
47
48 pub fn range(mut self, range: impl ToString) -> Self {
49 self.request.range = range.to_string();
50 self
51 }
52
53 pub fn image(mut self, image: Vec<u8>) -> Self {
54 self.request.image = image;
55 self
56 }
57
58 pub fn name(mut self, name: impl ToString) -> Self {
59 self.request.name = name.to_string();
60 self
61 }
62
63 pub fn build(mut self) -> WriteImageRequest {
64 self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
65 self.request
66 }
67}
68
69#[derive(Debug, Deserialize)]
71pub struct WriteImageResponse {
72 #[serde(rename = "spreadsheetToken")]
74 pub spread_sheet_token: String,
75 pub revision: i32,
77 #[serde(rename = "updateRange")]
79 pub update_range: String,
80}
81
82impl ApiResponseTrait for WriteImageResponse {
83 fn data_format() -> ResponseFormat {
84 ResponseFormat::Data
85 }
86}
87
88impl SpreadsheetService {
89 pub async fn write_image(
91 &self,
92 request: WriteImageRequest,
93 option: Option<req_option::RequestOption>,
94 ) -> SDKResult<BaseResponse<WriteImageResponse>> {
95 let mut api_req = request.api_request;
96 api_req.api_path =
97 SHEETS_V2_SPREADSHEET_VALUES_IMAGE.replace("{}", &request.spreadsheet_token);
98 api_req.http_method = reqwest::Method::POST;
99 api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::App];
100
101 let api_resp = crate::core::http::Transport::request(api_req, &self.config, option).await?;
102
103 Ok(api_resp)
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use crate::{
110 core::{config::Config, constants::AppType},
111 service::cloud_docs::sheets::v2::{
112 data_operation::{WriteImageRequest, WriteImageResponse},
113 SpreadsheetService,
114 },
115 };
116
117 fn create_service() -> SpreadsheetService {
118 let config = Config::builder()
119 .app_id("test_app_id")
120 .app_secret("test_app_secret")
121 .app_type(AppType::SelfBuild)
122 .build();
123 SpreadsheetService { config }
124 }
125
126 fn create_test_image_data() -> Vec<u8> {
127 vec![
129 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00,
135 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00,
140 0x01, 0x0D, 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82, ]
146 }
147
148 fn create_jpeg_header() -> Vec<u8> {
149 vec![0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46]
151 }
152
153 #[test]
154 fn test_write_image_builder_default() {
155 let request = WriteImageRequest::builder().build();
156
157 assert_eq!(request.spreadsheet_token, "");
158 assert_eq!(request.range, "");
159 assert_eq!(request.image, Vec::<u8>::new());
160 assert_eq!(request.name, "");
161 }
162
163 #[test]
164 fn test_write_image_builder_basic() {
165 let image_data = create_test_image_data();
166 let request = WriteImageRequest::builder()
167 .spreadsheet_token("test_token")
168 .range("Sheet1!A1:A1")
169 .image(image_data.clone())
170 .name("test_image.png")
171 .build();
172
173 assert_eq!(request.spreadsheet_token, "test_token");
174 assert_eq!(request.range, "Sheet1!A1:A1");
175 assert_eq!(request.image, image_data);
176 assert_eq!(request.name, "test_image.png");
177 }
178
179 #[test]
180 fn test_write_image_builder_all_options() {
181 let image_data = create_test_image_data();
182 let request = WriteImageRequest::builder()
183 .spreadsheet_token("spreadsheet_abc123")
184 .range("Photos!B2:B2")
185 .image(image_data.clone())
186 .name("company_logo.png")
187 .build();
188
189 assert_eq!(request.spreadsheet_token, "spreadsheet_abc123");
190 assert_eq!(request.range, "Photos!B2:B2");
191 assert_eq!(request.image, image_data);
192 assert_eq!(request.name, "company_logo.png");
193 }
194
195 #[test]
196 fn test_write_image_builder_chaining() {
197 let image_data = create_test_image_data();
198 let request = WriteImageRequest::builder()
199 .spreadsheet_token("chain_test")
200 .range("Gallery!C3:C3")
201 .name("chained_image.png")
202 .image(image_data.clone())
203 .build();
204
205 assert_eq!(request.spreadsheet_token, "chain_test");
206 assert_eq!(request.range, "Gallery!C3:C3");
207 assert_eq!(request.image, image_data);
208 assert_eq!(request.name, "chained_image.png");
209 }
210
211 #[test]
212 fn test_write_image_with_different_formats() {
213 let png_data = create_test_image_data();
214 let jpeg_data = create_jpeg_header();
215
216 let png_request = WriteImageRequest::builder()
217 .spreadsheet_token("format_test")
218 .range("Images!A1:A1")
219 .image(png_data.clone())
220 .name("image.png")
221 .build();
222
223 let jpeg_request = WriteImageRequest::builder()
224 .spreadsheet_token("format_test")
225 .range("Images!B1:B1")
226 .image(jpeg_data.clone())
227 .name("image.jpg")
228 .build();
229
230 assert_eq!(png_request.image, png_data);
231 assert_eq!(jpeg_request.image, jpeg_data);
232 assert_eq!(png_request.name, "image.png");
233 assert_eq!(jpeg_request.name, "image.jpg");
234 }
235
236 #[test]
237 fn test_write_image_with_unicode_names() {
238 let image_data = create_test_image_data();
239 let request = WriteImageRequest::builder()
240 .spreadsheet_token("unicode_test")
241 .range("图片!A1:A1")
242 .image(image_data.clone())
243 .name("公司标志.png")
244 .build();
245
246 assert_eq!(request.spreadsheet_token, "unicode_test");
247 assert_eq!(request.range, "图片!A1:A1");
248 assert_eq!(request.image, image_data);
249 assert_eq!(request.name, "公司标志.png");
250 }
251
252 #[test]
253 fn test_write_image_with_special_characters() {
254 let image_data = create_test_image_data();
255 let request = WriteImageRequest::builder()
256 .spreadsheet_token("special_chars_test")
257 .range("'Sheet With Spaces'!A1:A1")
258 .image(image_data.clone())
259 .name("image@2x!#$%^&*().png")
260 .build();
261
262 assert_eq!(request.spreadsheet_token, "special_chars_test");
263 assert_eq!(request.range, "'Sheet With Spaces'!A1:A1");
264 assert_eq!(request.image, image_data);
265 assert_eq!(request.name, "image@2x!#$%^&*().png");
266 }
267
268 #[test]
269 fn test_write_image_empty_image() {
270 let request = WriteImageRequest::builder()
271 .spreadsheet_token("empty_test")
272 .range("Sheet1!A1:A1")
273 .image(vec![])
274 .name("empty.png")
275 .build();
276
277 assert_eq!(request.spreadsheet_token, "empty_test");
278 assert_eq!(request.range, "Sheet1!A1:A1");
279 assert_eq!(request.image, Vec::<u8>::new());
280 assert_eq!(request.name, "empty.png");
281 }
282
283 #[test]
284 fn test_write_image_large_data() {
285 let large_image_data = vec![0u8; 1024 * 1024]; let request = WriteImageRequest::builder()
287 .spreadsheet_token("large_image_test")
288 .range("BigImages!A1:A1")
289 .image(large_image_data.clone())
290 .name("large_image.png")
291 .build();
292
293 assert_eq!(request.spreadsheet_token, "large_image_test");
294 assert_eq!(request.range, "BigImages!A1:A1");
295 assert_eq!(request.image.len(), 1024 * 1024);
296 assert_eq!(request.name, "large_image.png");
297 }
298
299 #[test]
300 fn test_write_image_different_ranges() {
301 let image_data = create_test_image_data();
302
303 let ranges = [
304 "Sheet1!A1:A1",
305 "Sheet2!B5:B5",
306 "DataSheet!Z100:Z100",
307 "第一页!C10:C10",
308 ];
309
310 for (i, range) in ranges.iter().enumerate() {
311 let request = WriteImageRequest::builder()
312 .spreadsheet_token("range_test")
313 .range(range)
314 .image(image_data.clone())
315 .name(format!("image_{}.png", i))
316 .build();
317
318 assert_eq!(request.range, *range);
319 assert_eq!(request.name, format!("image_{}.png", i));
320 }
321 }
322
323 #[test]
324 fn test_write_image_serialization() {
325 let image_data = create_test_image_data();
326 let request = WriteImageRequest::builder()
327 .spreadsheet_token("serialization_test")
328 .range("Sheet1!A1:A1")
329 .image(image_data.clone())
330 .name("test_image.png")
331 .build();
332
333 let serialized = serde_json::to_string(&request);
334 assert!(serialized.is_ok());
335
336 let json_value: serde_json::Value = serde_json::from_str(&serialized.unwrap()).unwrap();
337 assert_eq!(json_value["range"], "Sheet1!A1:A1");
338 assert_eq!(json_value["name"], "test_image.png");
339 assert!(json_value["image"].is_array());
341 }
342
343 #[test]
344 fn test_write_image_response_deserialization() {
345 let response_json = serde_json::json!({
346 "spreadsheetToken": "test_token_123",
347 "revision": 456,
348 "updateRange": "Sheet1!A1:A1"
349 });
350
351 let response: WriteImageResponse = serde_json::from_value(response_json).unwrap();
352
353 assert_eq!(response.spread_sheet_token, "test_token_123");
354 assert_eq!(response.revision, 456);
355 assert_eq!(response.update_range, "Sheet1!A1:A1");
356 }
357
358 #[test]
359 fn test_write_image_various_extensions() {
360 let image_data = create_test_image_data();
361
362 let extensions = vec!["png", "jpg", "jpeg", "gif", "bmp", "tiff", "heic", "webp"];
363
364 for ext in extensions {
365 let request = WriteImageRequest::builder()
366 .spreadsheet_token("extension_test")
367 .range("Images!A1:A1")
368 .image(image_data.clone())
369 .name(format!("test_image.{}", ext))
370 .build();
371
372 assert_eq!(request.name, format!("test_image.{}", ext));
373 assert_eq!(request.image, image_data);
374 }
375 }
376
377 #[test]
378 fn test_write_image_long_filename() {
379 let image_data = create_test_image_data();
380 let long_name = "a".repeat(255) + ".png";
381
382 let request = WriteImageRequest::builder()
383 .spreadsheet_token("long_name_test")
384 .range("Sheet1!A1:A1")
385 .image(image_data.clone())
386 .name(&long_name)
387 .build();
388
389 assert_eq!(request.name, long_name);
390 assert_eq!(request.image, image_data);
391 }
392
393 #[test]
394 fn test_write_image_binary_data_integrity() {
395 let original_data = vec![0x89, 0x50, 0x4E, 0x47, 0xFF, 0x00, 0xAA, 0xBB];
396
397 let request = WriteImageRequest::builder()
398 .spreadsheet_token("binary_test")
399 .range("Sheet1!A1:A1")
400 .image(original_data.clone())
401 .name("binary_test.png")
402 .build();
403
404 assert_eq!(request.image, original_data);
405 for (i, &byte) in original_data.iter().enumerate() {
407 assert_eq!(request.image[i], byte);
408 }
409 }
410
411 #[test]
412 fn test_write_image_service_creation() {
413 let service = create_service();
414 assert_eq!(service.config.app_id, "test_app_id");
415 assert_eq!(service.config.app_secret, "test_app_secret");
416 assert!(matches!(service.config.app_type, AppType::SelfBuild));
417 }
418
419 #[test]
420 fn test_write_image_complex_range_references() {
421 let image_data = create_test_image_data();
422
423 let complex_ranges = vec![
424 "Sheet1!A1:A1",
425 "'Sheet with spaces'!B2:B2",
426 "工作表1!C3:C3",
427 "Sheet-Name_123!D4:D4",
428 ];
429
430 for range in complex_ranges {
431 let request = WriteImageRequest::builder()
432 .spreadsheet_token("complex_range_test")
433 .range(range)
434 .image(image_data.clone())
435 .name("test.png")
436 .build();
437
438 assert_eq!(request.range, range);
439 }
440 }
441
442 #[test]
443 fn test_write_image_different_image_sizes() {
444 let spreadsheet_token = "size_test";
445
446 let sizes = vec![1, 10, 100, 1000, 10000];
448
449 for size in sizes {
450 let image_data = vec![0xFFu8; size];
451 let request = WriteImageRequest::builder()
452 .spreadsheet_token(spreadsheet_token)
453 .range("Sheet1!A1:A1")
454 .image(image_data.clone())
455 .name(format!("image_{}bytes.png", size))
456 .build();
457
458 assert_eq!(request.image.len(), size);
459 assert_eq!(request.name, format!("image_{}bytes.png", size));
460 }
461 }
462
463 #[test]
464 fn test_write_image_metadata_only() {
465 let request = WriteImageRequest::builder()
467 .spreadsheet_token("metadata_test")
468 .range("Sheet1!A1:A1")
469 .name("placeholder.png")
470 .build();
471
472 assert_eq!(request.spreadsheet_token, "metadata_test");
473 assert_eq!(request.range, "Sheet1!A1:A1");
474 assert_eq!(request.name, "placeholder.png");
475 assert_eq!(request.image, Vec::<u8>::new());
476 }
477
478 #[test]
479 fn test_write_image_very_long_token() {
480 let very_long_token = "a".repeat(1000);
481 let image_data = create_test_image_data();
482
483 let request = WriteImageRequest::builder()
484 .spreadsheet_token(&very_long_token)
485 .range("Sheet1!A1:A1")
486 .image(image_data.clone())
487 .name("test.png")
488 .build();
489
490 assert_eq!(request.spreadsheet_token, very_long_token);
491 assert_eq!(request.image, image_data);
492 }
493
494 #[test]
495 fn test_write_image_response_struct_debug() {
496 let response = WriteImageResponse {
497 spread_sheet_token: "debug_test".to_string(),
498 revision: 123,
499 update_range: "Sheet1!A1:A1".to_string(),
500 };
501
502 let debug_str = format!("{:?}", response);
503 assert!(debug_str.contains("debug_test"));
504 assert!(debug_str.contains("123"));
505 assert!(debug_str.contains("Sheet1!A1:A1"));
506 }
507
508 #[test]
509 fn test_write_image_builder_overwrites() {
510 let image_data1 = vec![1, 2, 3];
511 let image_data2 = vec![4, 5, 6];
512
513 let request = WriteImageRequest::builder()
514 .spreadsheet_token("original_token")
515 .spreadsheet_token("final_token") .range("Sheet1!A1:A1")
517 .range("Sheet2!B2:B2") .image(image_data1)
519 .image(image_data2.clone()) .name("original.png")
521 .name("final.png") .build();
523
524 assert_eq!(request.spreadsheet_token, "final_token");
525 assert_eq!(request.range, "Sheet2!B2:B2");
526 assert_eq!(request.image, image_data2);
527 assert_eq!(request.name, "final.png");
528 }
529}