openlark_docs/common/
chain.rs1use openlark_core::config::Config;
42#[cfg(feature = "ccm-core")]
43use openlark_core::error::{business_error, CoreError};
44#[cfg(any(feature = "ccm-core", feature = "bitable"))]
45use openlark_core::SDKResult;
46use std::sync::Arc;
47
48#[derive(Debug, Clone)]
50pub struct DocsClient {
51 config: Arc<Config>,
52
53 #[cfg(feature = "ccm-core")]
54 pub ccm: CcmClient,
55
56 #[cfg(any(feature = "base", feature = "bitable"))]
57 pub base: BaseClient,
58
59 #[cfg(any(feature = "baike", feature = "lingo"))]
60 pub baike: BaikeClient,
61
62 #[cfg(feature = "minutes")]
63 pub minutes: MinutesClient,
64}
65
66impl DocsClient {
67 pub fn new(config: Config) -> Self {
68 let config = Arc::new(config);
69 Self {
70 config: config.clone(),
71 #[cfg(feature = "ccm-core")]
72 ccm: CcmClient::new(config.clone()),
73 #[cfg(any(feature = "base", feature = "bitable"))]
74 base: BaseClient::new(config.clone()),
75 #[cfg(any(feature = "baike", feature = "lingo"))]
76 baike: BaikeClient::new(config.clone()),
77 #[cfg(feature = "minutes")]
78 minutes: MinutesClient::new(config),
79 }
80 }
81
82 pub fn config(&self) -> &Config {
83 &self.config
84 }
85
86 #[cfg(feature = "ccm-core")]
88 pub async fn list_folder_children_all(
89 &self,
90 folder_token: &str,
91 doc_type: Option<&str>,
92 ) -> SDKResult<Vec<crate::ccm::explorer::v2::models::FileItem>> {
93 use crate::ccm::explorer::v2::{get_folder_children, GetFolderChildrenParams};
94
95 let mut items = Vec::new();
96 let mut page_token = None;
97
98 loop {
99 let response = get_folder_children(
100 self.config(),
101 folder_token,
102 Some(GetFolderChildrenParams {
103 page_size: Some(crate::common::constants::MAX_PAGE_SIZE),
104 page_token: page_token.clone(),
105 doc_type: doc_type.map(str::to_owned),
106 }),
107 )
108 .await?;
109
110 let Some(data) = response.data else {
111 break;
112 };
113
114 items.extend(data.items);
115
116 if !data.has_more {
117 break;
118 }
119
120 page_token = data.page_token;
121 }
122
123 Ok(items)
124 }
125
126 #[cfg(feature = "bitable")]
128 pub async fn search_bitable_records_all(
129 &self,
130 app_token: &str,
131 table_id: &str,
132 ) -> SDKResult<Vec<crate::base::bitable::v1::app::table::record::models::Record>> {
133 use crate::base::bitable::v1::app::table::record::search::SearchRecordRequest;
134
135 SearchRecordRequest::new(self.config().clone())
136 .app_token(app_token.to_string())
137 .table_id(table_id.to_string())
138 .automatic_fields(true)
139 .fetch_all()
140 .await
141 }
142
143 #[cfg(feature = "ccm-core")]
145 pub async fn read_multiple_ranges(
146 &self,
147 spreadsheet_token: &str,
148 ranges: Vec<String>,
149 ) -> SDKResult<crate::ccm::sheets_v2::v2::data_io::models::MultipleRangeData> {
150 use crate::ccm::sheets_v2::v2::data_io::{
151 read_multiple_ranges as read_multiple_ranges_api, ReadMultipleRangesParams,
152 };
153
154 let response = read_multiple_ranges_api(
155 self.config(),
156 spreadsheet_token,
157 ReadMultipleRangesParams {
158 ranges,
159 value_render_option: None,
160 date_render_option: None,
161 },
162 )
163 .await?;
164
165 response
166 .data
167 .ok_or_else(|| CoreError::api_data_error("读取多个范围"))
168 }
169
170 #[cfg(feature = "ccm-core")]
172 pub async fn write_multiple_ranges(
173 &self,
174 spreadsheet_token: &str,
175 data: Vec<crate::ccm::sheets_v2::v2::data_io::models::BatchWriteData>,
176 ) -> SDKResult<crate::ccm::sheets_v2::v2::data_io::models::BatchUpdateResult> {
177 use crate::ccm::sheets_v2::v2::data_io::{batch_write_ranges, BatchWriteRangesParams};
178
179 let response = batch_write_ranges(
180 self.config(),
181 spreadsheet_token,
182 BatchWriteRangesParams {
183 data,
184 include_style: None,
185 },
186 )
187 .await?;
188
189 response
190 .data
191 .ok_or_else(|| CoreError::api_data_error("批量写入多个范围"))
192 }
193
194 #[cfg(feature = "ccm-core")]
196 pub async fn find_sheet_by_title(
197 &self,
198 spreadsheet_token: &str,
199 title: &str,
200 ) -> SDKResult<crate::ccm::sheets_v2::v2::spreadsheet::models::SpreadsheetSheetInfo> {
201 let sheets = self.list_sheet_infos(spreadsheet_token).await?;
202
203 find_sheet_info(&sheets, title)
204 .ok_or_else(|| business_error(format!("未找到工作表: {title}")))
205 }
206
207 #[cfg(feature = "ccm-core")]
208 pub async fn list_sheet_infos(
209 &self,
210 spreadsheet_token: &str,
211 ) -> SDKResult<Vec<crate::ccm::sheets_v2::v2::spreadsheet::models::SpreadsheetSheetInfo>> {
212 use crate::ccm::sheets::v3::spreadsheet::sheet::query::query_sheets;
213 use crate::ccm::sheets_v2::v2::spreadsheet::models::SpreadsheetSheetInfo;
214
215 log::info!(
216 "[OPENLARK DEBUG] list_sheet_infos called with token: {}",
217 spreadsheet_token
218 );
219
220 let response = query_sheets(self.config(), spreadsheet_token).await?;
221
222 log::info!(
223 "[OPENLARK DEBUG] query_sheets response count: {}",
224 response.sheets.len()
225 );
226
227 let sheets: Vec<SpreadsheetSheetInfo> =
228 response.sheets.into_iter().map(map_v3_sheet_info).collect();
229
230 if sheets.is_empty() {
231 return Err(CoreError::api_data_error("获取工作表列表"));
232 }
233
234 Ok(sheets)
235 }
236}
237
238#[cfg(feature = "ccm-core")]
239fn map_v3_sheet_info(
240 sheet: crate::ccm::sheets::v3::spreadsheet::Sheet,
241) -> crate::ccm::sheets_v2::v2::spreadsheet::models::SpreadsheetSheetInfo {
242 crate::ccm::sheets_v2::v2::spreadsheet::models::SpreadsheetSheetInfo {
243 sheet_id: sheet.sheet_id,
244 title: sheet.title,
245 sheet_type: sheet.resource_type,
246 row_count: sheet.grid_properties.row_count,
247 column_count: sheet.grid_properties.column_count,
248 }
249}
250
251#[cfg(feature = "ccm-core")]
252fn find_sheet_info(
253 sheets: &[crate::ccm::sheets_v2::v2::spreadsheet::models::SpreadsheetSheetInfo],
254 title: &str,
255) -> Option<crate::ccm::sheets_v2::v2::spreadsheet::models::SpreadsheetSheetInfo> {
256 sheets.iter().find(|sheet| sheet.title == title).cloned()
257}
258
259#[cfg(feature = "ccm-core")]
261#[derive(Debug, Clone)]
262pub struct CcmClient {
263 config: Arc<Config>,
264}
265
266#[cfg(feature = "ccm-core")]
267impl CcmClient {
268 fn new(config: Arc<Config>) -> Self {
269 Self { config }
270 }
271
272 pub fn config(&self) -> &Config {
273 &self.config
274 }
275}
276
277#[cfg(any(feature = "base", feature = "bitable"))]
279#[derive(Debug, Clone)]
280pub struct BaseClient {
281 config: Arc<Config>,
282}
283
284#[cfg(any(feature = "base", feature = "bitable"))]
285impl BaseClient {
286 fn new(config: Arc<Config>) -> Self {
287 Self { config }
288 }
289
290 pub fn config(&self) -> &Config {
291 &self.config
292 }
293
294 #[cfg(feature = "bitable")]
295 pub fn bitable(&self) -> BitableClient {
296 BitableClient::new(self.config.clone())
297 }
298}
299
300#[cfg(feature = "bitable")]
302#[derive(Debug, Clone)]
303pub struct BitableClient {
304 config: Arc<Config>,
305}
306
307#[cfg(feature = "bitable")]
308impl BitableClient {
309 fn new(config: Arc<Config>) -> Self {
310 Self { config }
311 }
312
313 pub fn config(&self) -> &Config {
314 &self.config
315 }
316}
317
318#[cfg(any(feature = "baike", feature = "lingo"))]
320#[derive(Debug, Clone)]
321pub struct BaikeClient {
322 config: Arc<Config>,
323}
324
325#[cfg(any(feature = "baike", feature = "lingo"))]
326impl BaikeClient {
327 fn new(config: Arc<Config>) -> Self {
328 Self { config }
329 }
330
331 pub fn config(&self) -> &Config {
332 &self.config
333 }
334}
335
336#[cfg(feature = "minutes")]
338#[derive(Debug, Clone)]
339pub struct MinutesClient {
340 config: Arc<Config>,
341}
342
343#[cfg(feature = "minutes")]
344impl MinutesClient {
345 fn new(config: Arc<Config>) -> Self {
346 Self { config }
347 }
348
349 pub fn config(&self) -> &Config {
350 &self.config
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use serde_json;
357
358 #[test]
359 fn test_serialization_roundtrip() {
360 let json = r#"{"test": "value"}"#;
362 assert!(serde_json::from_str::<serde_json::Value>(json).is_ok());
363 }
364
365 #[test]
366 fn test_deserialization_from_json() {
367 let json = r#"{"field": "data"}"#;
369 let value: serde_json::Value = serde_json::from_str(json).unwrap();
370 assert_eq!(value["field"], "data");
371 }
372}