open_lark/service/cloud_docs/sheets/v2/data_operation/
merge_cells.rs

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/// 合并单元格请求
15#[derive(Serialize, Debug, Default)]
16pub struct MergeCellsRequest {
17    #[serde(skip)]
18    api_request: ApiRequest,
19    #[serde(skip)]
20    spreadsheet_token: String,
21    /// 查询范围,包含 sheetId 与单元格范围两部分,目前支持四种索引方式,详见 在线表格开发指南
22    range: String,
23    /// 可选三个类型,"MERGE_ALL" 将所选区域直接合并、"MERGE_ROWS"
24    /// 将所选区域按行合并、"MERGE_COLUMNS" 将所选区域按列合并响应
25    #[serde(rename = "mergeType")]
26    merge_type: String,
27}
28
29impl MergeCellsRequest {
30    pub fn builder() -> MergeCellsRequestBuilder {
31        MergeCellsRequestBuilder::default()
32    }
33}
34
35#[derive(Default)]
36pub struct MergeCellsRequestBuilder {
37    request: MergeCellsRequest,
38}
39
40impl MergeCellsRequestBuilder {
41    pub fn spreadsheet_token(mut self, spreadsheet_token: impl ToString) -> Self {
42        self.request.spreadsheet_token = spreadsheet_token.to_string();
43        self
44    }
45
46    /// 查询范围,包含 sheetId 与单元格范围两部分,目前支持四种索引方式,详见 在线表格开发指南
47    pub fn range(mut self, range: impl ToString) -> Self {
48        self.request.range = range.to_string();
49        self
50    }
51
52    /// 可选三个类型,"MERGE_ALL" 将所选区域直接合并、"MERGE_ROWS"
53    /// 将所选区域按行合并、"MERGE_COLUMNS" 将所选区域按列合并响应
54    pub fn merge_type(mut self, merge_type: impl ToString) -> Self {
55        self.request.merge_type = merge_type.to_string();
56        self
57    }
58
59    pub fn build(mut self) -> MergeCellsRequest {
60        self.request.api_request.body = serde_json::to_vec(&self.request).unwrap();
61        self.request
62    }
63}
64
65#[derive(Deserialize, Debug)]
66pub struct MergeCellsResponse {
67    /// spreadsheet 的 token
68    #[serde(rename = "spreadsheetToken")]
69    pub spread_sheet_token: String,
70}
71
72impl ApiResponseTrait for MergeCellsResponse {
73    fn data_format() -> ResponseFormat {
74        ResponseFormat::Data
75    }
76}
77
78impl SpreadsheetService {
79    /// 合并单元格
80    pub async fn merge_cells(
81        &self,
82        request: MergeCellsRequest,
83        option: Option<req_option::RequestOption>,
84    ) -> SDKResult<BaseResponse<MergeCellsResponse>> {
85        let mut api_req = request.api_request;
86        api_req.api_path =
87            SHEETS_V2_SPREADSHEET_MERGE_CELLS.replace("{}", &request.spreadsheet_token);
88        api_req.http_method = reqwest::Method::POST;
89        api_req.supported_access_token_types = vec![AccessTokenType::Tenant, AccessTokenType::App];
90
91        let api_resp = crate::core::http::Transport::request(api_req, &self.config, option).await?;
92
93        Ok(api_resp)
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use crate::{
100        core::{config::Config, constants::AppType},
101        service::cloud_docs::sheets::v2::{
102            data_operation::{MergeCellsRequest, MergeCellsResponse},
103            SpreadsheetService,
104        },
105    };
106
107    fn create_service() -> SpreadsheetService {
108        let config = Config::builder()
109            .app_id("test_app_id")
110            .app_secret("test_app_secret")
111            .app_type(AppType::SelfBuild)
112            .build();
113        SpreadsheetService { config }
114    }
115
116    #[test]
117    fn test_merge_cells_builder_default() {
118        let request = MergeCellsRequest::builder().build();
119
120        assert_eq!(request.spreadsheet_token, "");
121        assert_eq!(request.range, "");
122        assert_eq!(request.merge_type, "");
123    }
124
125    #[test]
126    fn test_merge_cells_builder_basic() {
127        let request = MergeCellsRequest::builder()
128            .spreadsheet_token("test_token")
129            .range("Sheet1!A1:B2")
130            .merge_type("MERGE_ALL")
131            .build();
132
133        assert_eq!(request.spreadsheet_token, "test_token");
134        assert_eq!(request.range, "Sheet1!A1:B2");
135        assert_eq!(request.merge_type, "MERGE_ALL");
136    }
137
138    #[test]
139    fn test_merge_cells_builder_all_options() {
140        let request = MergeCellsRequest::builder()
141            .spreadsheet_token("spreadsheet_abc123")
142            .range("Data!C3:F6")
143            .merge_type("MERGE_ROWS")
144            .build();
145
146        assert_eq!(request.spreadsheet_token, "spreadsheet_abc123");
147        assert_eq!(request.range, "Data!C3:F6");
148        assert_eq!(request.merge_type, "MERGE_ROWS");
149    }
150
151    #[test]
152    fn test_merge_cells_builder_chaining() {
153        let request = MergeCellsRequest::builder()
154            .spreadsheet_token("chain_test")
155            .range("Summary!A1:D1")
156            .merge_type("MERGE_COLUMNS")
157            .build();
158
159        assert_eq!(request.spreadsheet_token, "chain_test");
160        assert_eq!(request.range, "Summary!A1:D1");
161        assert_eq!(request.merge_type, "MERGE_COLUMNS");
162    }
163
164    #[test]
165    fn test_merge_cells_merge_all_type() {
166        let request = MergeCellsRequest::builder()
167            .spreadsheet_token("merge_all_test")
168            .range("Sheet1!A1:C3")
169            .merge_type("MERGE_ALL")
170            .build();
171
172        assert_eq!(request.spreadsheet_token, "merge_all_test");
173        assert_eq!(request.range, "Sheet1!A1:C3");
174        assert_eq!(request.merge_type, "MERGE_ALL");
175    }
176
177    #[test]
178    fn test_merge_cells_merge_rows_type() {
179        let request = MergeCellsRequest::builder()
180            .spreadsheet_token("merge_rows_test")
181            .range("Report!B2:E5")
182            .merge_type("MERGE_ROWS")
183            .build();
184
185        assert_eq!(request.spreadsheet_token, "merge_rows_test");
186        assert_eq!(request.range, "Report!B2:E5");
187        assert_eq!(request.merge_type, "MERGE_ROWS");
188    }
189
190    #[test]
191    fn test_merge_cells_merge_columns_type() {
192        let request = MergeCellsRequest::builder()
193            .spreadsheet_token("merge_columns_test")
194            .range("Table!A1:A10")
195            .merge_type("MERGE_COLUMNS")
196            .build();
197
198        assert_eq!(request.spreadsheet_token, "merge_columns_test");
199        assert_eq!(request.range, "Table!A1:A10");
200        assert_eq!(request.merge_type, "MERGE_COLUMNS");
201    }
202
203    #[test]
204    fn test_merge_cells_with_unicode_ranges() {
205        let request = MergeCellsRequest::builder()
206            .spreadsheet_token("unicode_test")
207            .range("数据表!A1:D4")
208            .merge_type("MERGE_ALL")
209            .build();
210
211        assert_eq!(request.spreadsheet_token, "unicode_test");
212        assert_eq!(request.range, "数据表!A1:D4");
213        assert_eq!(request.merge_type, "MERGE_ALL");
214    }
215
216    #[test]
217    fn test_merge_cells_with_special_characters() {
218        let request = MergeCellsRequest::builder()
219            .spreadsheet_token("special_chars_test")
220            .range("'Sheet With Spaces'!A1:B5")
221            .merge_type("MERGE_ROWS")
222            .build();
223
224        assert_eq!(request.spreadsheet_token, "special_chars_test");
225        assert_eq!(request.range, "'Sheet With Spaces'!A1:B5");
226        assert_eq!(request.merge_type, "MERGE_ROWS");
227    }
228
229    #[test]
230    fn test_merge_cells_single_cell_range() {
231        let request = MergeCellsRequest::builder()
232            .spreadsheet_token("single_cell_test")
233            .range("Sheet1!A1:A1")
234            .merge_type("MERGE_ALL")
235            .build();
236
237        assert_eq!(request.spreadsheet_token, "single_cell_test");
238        assert_eq!(request.range, "Sheet1!A1:A1");
239        assert_eq!(request.merge_type, "MERGE_ALL");
240    }
241
242    #[test]
243    fn test_merge_cells_large_range() {
244        let request = MergeCellsRequest::builder()
245            .spreadsheet_token("large_range_test")
246            .range("Data!A1:Z100")
247            .merge_type("MERGE_ALL")
248            .build();
249
250        assert_eq!(request.spreadsheet_token, "large_range_test");
251        assert_eq!(request.range, "Data!A1:Z100");
252        assert_eq!(request.merge_type, "MERGE_ALL");
253    }
254
255    #[test]
256    fn test_merge_cells_row_range() {
257        let request = MergeCellsRequest::builder()
258            .spreadsheet_token("row_range_test")
259            .range("Sheet1!A1:Z1")
260            .merge_type("MERGE_ROWS")
261            .build();
262
263        assert_eq!(request.spreadsheet_token, "row_range_test");
264        assert_eq!(request.range, "Sheet1!A1:Z1");
265        assert_eq!(request.merge_type, "MERGE_ROWS");
266    }
267
268    #[test]
269    fn test_merge_cells_column_range() {
270        let request = MergeCellsRequest::builder()
271            .spreadsheet_token("column_range_test")
272            .range("Sheet1!A1:A50")
273            .merge_type("MERGE_COLUMNS")
274            .build();
275
276        assert_eq!(request.spreadsheet_token, "column_range_test");
277        assert_eq!(request.range, "Sheet1!A1:A50");
278        assert_eq!(request.merge_type, "MERGE_COLUMNS");
279    }
280
281    #[test]
282    fn test_merge_cells_different_sheets() {
283        let sheets_and_ranges = [
284            ("Sheet1", "A1:B2"),
285            ("Summary", "C1:F1"),
286            ("Data", "A5:D10"),
287            ("第一页", "B3:E8"),
288        ];
289
290        for (sheet, range) in sheets_and_ranges.iter() {
291            let full_range = format!("{}!{}", sheet, range);
292            let request = MergeCellsRequest::builder()
293                .spreadsheet_token("multi_sheet_test")
294                .range(&full_range)
295                .merge_type("MERGE_ALL")
296                .build();
297
298            assert_eq!(request.range, full_range);
299            assert_eq!(request.merge_type, "MERGE_ALL");
300        }
301    }
302
303    #[test]
304    fn test_merge_cells_serialization() {
305        let request = MergeCellsRequest::builder()
306            .spreadsheet_token("serialization_test")
307            .range("Sheet1!A1:C3")
308            .merge_type("MERGE_ALL")
309            .build();
310
311        let serialized = serde_json::to_string(&request);
312        assert!(serialized.is_ok());
313
314        let json_value: serde_json::Value = serde_json::from_str(&serialized.unwrap()).unwrap();
315        assert_eq!(json_value["range"], "Sheet1!A1:C3");
316        assert_eq!(json_value["mergeType"], "MERGE_ALL");
317    }
318
319    #[test]
320    fn test_merge_cells_response_deserialization() {
321        let response_json = serde_json::json!({
322            "spreadsheetToken": "test_token_123"
323        });
324
325        let response: MergeCellsResponse = serde_json::from_value(response_json).unwrap();
326
327        assert_eq!(response.spread_sheet_token, "test_token_123");
328    }
329
330    #[test]
331    fn test_merge_cells_complex_range_references() {
332        let complex_ranges = vec![
333            "Sheet1!A1:D5",
334            "'Data Sheet'!B2:F10",
335            "工作表!C3:G7",
336            "Sheet-Name_123!A10:E15",
337            "'Sheet (1)'!D1:H4",
338        ];
339
340        for range in complex_ranges {
341            let request = MergeCellsRequest::builder()
342                .spreadsheet_token("complex_ref_test")
343                .range(range)
344                .merge_type("MERGE_ALL")
345                .build();
346
347            assert_eq!(request.range, range);
348        }
349    }
350
351    #[test]
352    fn test_merge_cells_all_merge_types() {
353        let merge_types = vec!["MERGE_ALL", "MERGE_ROWS", "MERGE_COLUMNS"];
354
355        for merge_type in merge_types {
356            let request = MergeCellsRequest::builder()
357                .spreadsheet_token("all_types_test")
358                .range("Sheet1!A1:C3")
359                .merge_type(merge_type)
360                .build();
361
362            assert_eq!(request.merge_type, merge_type);
363        }
364    }
365
366    #[test]
367    fn test_merge_cells_service_creation() {
368        let service = create_service();
369        assert_eq!(service.config.app_id, "test_app_id");
370        assert_eq!(service.config.app_secret, "test_app_secret");
371        assert!(matches!(service.config.app_type, AppType::SelfBuild));
372    }
373
374    #[test]
375    fn test_merge_cells_builder_overwrites() {
376        let request = MergeCellsRequest::builder()
377            .spreadsheet_token("original_token")
378            .spreadsheet_token("final_token") // Should overwrite
379            .range("Sheet1!A1:B2")
380            .range("Sheet2!C3:D4") // Should overwrite
381            .merge_type("MERGE_ROWS")
382            .merge_type("MERGE_ALL") // Should overwrite
383            .build();
384
385        assert_eq!(request.spreadsheet_token, "final_token");
386        assert_eq!(request.range, "Sheet2!C3:D4");
387        assert_eq!(request.merge_type, "MERGE_ALL");
388    }
389
390    #[test]
391    fn test_merge_cells_case_sensitive_merge_types() {
392        // Test that merge types are case sensitive (as they should be)
393        let merge_types = vec![
394            "MERGE_ALL",
395            "merge_all", // lowercase
396            "Merge_All", // mixed case
397            "MERGE_ROWS",
398            "merge_rows",
399            "MERGE_COLUMNS",
400            "merge_columns",
401        ];
402
403        for merge_type in merge_types {
404            let request = MergeCellsRequest::builder()
405                .spreadsheet_token("case_test")
406                .range("Sheet1!A1:B2")
407                .merge_type(merge_type)
408                .build();
409
410            assert_eq!(request.merge_type, merge_type);
411        }
412    }
413
414    #[test]
415    fn test_merge_cells_very_long_token() {
416        let very_long_token = "a".repeat(1000);
417        let request = MergeCellsRequest::builder()
418            .spreadsheet_token(&very_long_token)
419            .range("Sheet1!A1:B2")
420            .merge_type("MERGE_ALL")
421            .build();
422
423        assert_eq!(request.spreadsheet_token, very_long_token);
424        assert_eq!(request.range, "Sheet1!A1:B2");
425        assert_eq!(request.merge_type, "MERGE_ALL");
426    }
427
428    #[test]
429    fn test_merge_cells_response_struct_debug() {
430        let response = MergeCellsResponse {
431            spread_sheet_token: "debug_test".to_string(),
432        };
433
434        let debug_str = format!("{:?}", response);
435        assert!(debug_str.contains("debug_test"));
436        assert!(debug_str.contains("MergeCellsResponse"));
437    }
438
439    #[test]
440    fn test_merge_cells_request_struct_debug() {
441        let request = MergeCellsRequest::builder()
442            .spreadsheet_token("debug_token")
443            .range("Sheet1!A1:B2")
444            .merge_type("MERGE_ALL")
445            .build();
446
447        let debug_str = format!("{:?}", request);
448        assert!(debug_str.contains("debug_token"));
449        assert!(debug_str.contains("Sheet1!A1:B2"));
450        assert!(debug_str.contains("MERGE_ALL"));
451    }
452
453    #[test]
454    fn test_merge_cells_empty_strings() {
455        // Test behavior with empty strings (should be allowed for flexibility)
456        let request = MergeCellsRequest::builder()
457            .spreadsheet_token("")
458            .range("")
459            .merge_type("")
460            .build();
461
462        assert_eq!(request.spreadsheet_token, "");
463        assert_eq!(request.range, "");
464        assert_eq!(request.merge_type, "");
465    }
466}