Skip to main content

dingtalk_sdk/types/
enterprise.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// Request for getting a user by `userid`.
7#[derive(Debug, Clone, Serialize)]
8pub struct ContactGetUserRequest {
9    /// DingTalk user id.
10    pub userid: String,
11    /// Language code (for example `zh_CN`).
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub language: Option<String>,
14}
15
16impl ContactGetUserRequest {
17    /// Creates a request with required fields.
18    #[must_use]
19    pub fn new(userid: impl Into<String>) -> Self {
20        Self {
21            userid: userid.into(),
22            language: None,
23        }
24    }
25
26    /// Sets language preference.
27    #[must_use]
28    pub fn language(mut self, value: impl Into<String>) -> Self {
29        self.language = Some(value.into());
30        self
31    }
32}
33
34/// Request for getting a user by mobile number.
35#[derive(Debug, Clone, Serialize)]
36pub struct ContactGetUserByMobileRequest {
37    /// Mobile phone number.
38    pub mobile: String,
39}
40
41impl ContactGetUserByMobileRequest {
42    /// Creates a request with required fields.
43    #[must_use]
44    pub fn new(mobile: impl Into<String>) -> Self {
45        Self {
46            mobile: mobile.into(),
47        }
48    }
49}
50
51/// Request for getting a user by union id.
52#[derive(Debug, Clone, Serialize)]
53pub struct ContactGetUserByUnionIdRequest {
54    /// Union id.
55    pub unionid: String,
56}
57
58impl ContactGetUserByUnionIdRequest {
59    /// Creates a request with required fields.
60    #[must_use]
61    pub fn new(unionid: impl Into<String>) -> Self {
62        Self {
63            unionid: unionid.into(),
64        }
65    }
66}
67
68/// Request for listing users in a department.
69#[derive(Debug, Clone, Serialize)]
70pub struct ContactListUsersRequest {
71    /// Department id.
72    pub dept_id: i64,
73    /// Cursor for pagination.
74    pub cursor: i64,
75    /// Page size.
76    pub size: i64,
77    /// Language code (for example `zh_CN`).
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub language: Option<String>,
80    /// Optional ordering field.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub order_field: Option<String>,
83    /// Whether to include access-limited users.
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub contain_access_limit: Option<bool>,
86}
87
88impl ContactListUsersRequest {
89    /// Creates a request with required fields.
90    #[must_use]
91    pub fn new(dept_id: i64, cursor: i64, size: i64) -> Self {
92        Self {
93            dept_id,
94            cursor,
95            size,
96            language: None,
97            order_field: None,
98            contain_access_limit: None,
99        }
100    }
101
102    /// Sets language preference.
103    #[must_use]
104    pub fn language(mut self, value: impl Into<String>) -> Self {
105        self.language = Some(value.into());
106        self
107    }
108
109    /// Sets ordering field.
110    #[must_use]
111    pub fn order_field(mut self, value: impl Into<String>) -> Self {
112        self.order_field = Some(value.into());
113        self
114    }
115
116    /// Sets access-limit inclusion behavior.
117    #[must_use]
118    pub fn contain_access_limit(mut self, value: bool) -> Self {
119        self.contain_access_limit = Some(value);
120        self
121    }
122}
123
124/// Request for creating a user.
125#[derive(Debug, Clone, Serialize)]
126pub struct ContactCreateUserRequest {
127    /// Display name.
128    pub name: String,
129    /// Mobile phone number.
130    pub mobile: String,
131    /// Department id list encoded as DingTalk expects.
132    pub dept_id_list: String,
133    /// Optional user id.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub userid: Option<String>,
136    /// Additional pass-through fields supported by DingTalk.
137    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
138    pub extra: BTreeMap<String, Value>,
139}
140
141impl ContactCreateUserRequest {
142    /// Creates a request with required fields.
143    #[must_use]
144    pub fn new(
145        name: impl Into<String>,
146        mobile: impl Into<String>,
147        dept_id_list: impl Into<String>,
148    ) -> Self {
149        Self {
150            name: name.into(),
151            mobile: mobile.into(),
152            dept_id_list: dept_id_list.into(),
153            userid: None,
154            extra: BTreeMap::new(),
155        }
156    }
157
158    /// Sets explicit user id.
159    #[must_use]
160    pub fn userid(mut self, value: impl Into<String>) -> Self {
161        self.userid = Some(value.into());
162        self
163    }
164
165    /// Adds a custom extra field.
166    #[must_use]
167    pub fn insert_extra(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
168        self.extra.insert(key.into(), value.into());
169        self
170    }
171}
172
173/// Request for updating a user.
174#[derive(Debug, Clone, Serialize)]
175pub struct ContactUpdateUserRequest {
176    /// User id.
177    pub userid: String,
178    /// Additional pass-through fields supported by DingTalk.
179    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
180    pub extra: BTreeMap<String, Value>,
181}
182
183impl ContactUpdateUserRequest {
184    /// Creates a request with required fields.
185    #[must_use]
186    pub fn new(userid: impl Into<String>) -> Self {
187        Self {
188            userid: userid.into(),
189            extra: BTreeMap::new(),
190        }
191    }
192
193    /// Adds a custom extra field.
194    #[must_use]
195    pub fn insert_extra(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
196        self.extra.insert(key.into(), value.into());
197        self
198    }
199}
200
201/// Request for deleting a user.
202#[derive(Debug, Clone, Serialize)]
203pub struct ContactDeleteUserRequest {
204    /// User id.
205    pub userid: String,
206}
207
208impl ContactDeleteUserRequest {
209    /// Creates a request with required fields.
210    #[must_use]
211    pub fn new(userid: impl Into<String>) -> Self {
212        Self {
213            userid: userid.into(),
214        }
215    }
216}
217
218/// Request for getting a department.
219#[derive(Debug, Clone, Serialize)]
220pub struct ContactGetDepartmentRequest {
221    /// Department id.
222    pub dept_id: i64,
223    /// Language code (for example `zh_CN`).
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub language: Option<String>,
226}
227
228impl ContactGetDepartmentRequest {
229    /// Creates a request with required fields.
230    #[must_use]
231    pub fn new(dept_id: i64) -> Self {
232        Self {
233            dept_id,
234            language: None,
235        }
236    }
237
238    /// Sets language preference.
239    #[must_use]
240    pub fn language(mut self, value: impl Into<String>) -> Self {
241        self.language = Some(value.into());
242        self
243    }
244}
245
246/// Request for creating a department.
247#[derive(Debug, Clone, Serialize)]
248pub struct ContactCreateDepartmentRequest {
249    /// Department name.
250    pub name: String,
251    /// Parent department id.
252    pub parent_id: i64,
253    /// Additional pass-through fields supported by DingTalk.
254    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
255    pub extra: BTreeMap<String, Value>,
256}
257
258impl ContactCreateDepartmentRequest {
259    /// Creates a request with required fields.
260    #[must_use]
261    pub fn new(name: impl Into<String>, parent_id: i64) -> Self {
262        Self {
263            name: name.into(),
264            parent_id,
265            extra: BTreeMap::new(),
266        }
267    }
268
269    /// Adds a custom extra field.
270    #[must_use]
271    pub fn insert_extra(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
272        self.extra.insert(key.into(), value.into());
273        self
274    }
275}
276
277/// Request for updating a department.
278#[derive(Debug, Clone, Serialize)]
279pub struct ContactUpdateDepartmentRequest {
280    /// Department id.
281    pub dept_id: i64,
282    /// New department name.
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub name: Option<String>,
285    /// New parent department id.
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub parent_id: Option<i64>,
288    /// Additional pass-through fields supported by DingTalk.
289    #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
290    pub extra: BTreeMap<String, Value>,
291}
292
293impl ContactUpdateDepartmentRequest {
294    /// Creates a request with required fields.
295    #[must_use]
296    pub fn new(dept_id: i64) -> Self {
297        Self {
298            dept_id,
299            name: None,
300            parent_id: None,
301            extra: BTreeMap::new(),
302        }
303    }
304
305    /// Sets department name.
306    #[must_use]
307    pub fn name(mut self, value: impl Into<String>) -> Self {
308        self.name = Some(value.into());
309        self
310    }
311
312    /// Sets parent department id.
313    #[must_use]
314    pub fn parent_id(mut self, value: i64) -> Self {
315        self.parent_id = Some(value);
316        self
317    }
318
319    /// Adds a custom extra field.
320    #[must_use]
321    pub fn insert_extra(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
322        self.extra.insert(key.into(), value.into());
323        self
324    }
325}
326
327/// Request for deleting a department.
328#[derive(Debug, Clone, Serialize)]
329pub struct ContactDeleteDepartmentRequest {
330    /// Department id.
331    pub dept_id: i64,
332}
333
334impl ContactDeleteDepartmentRequest {
335    /// Creates a request with required fields.
336    #[must_use]
337    pub fn new(dept_id: i64) -> Self {
338        Self { dept_id }
339    }
340}
341
342/// Request for listing child departments.
343#[derive(Debug, Clone, Serialize)]
344pub struct ContactListSubDepartmentsRequest {
345    /// Department id.
346    pub dept_id: i64,
347    /// Language code (for example `zh_CN`).
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub language: Option<String>,
350}
351
352/// Request for listing child department ids.
353#[derive(Debug, Clone, Serialize)]
354pub struct ContactListSubDepartmentIdsRequest {
355    /// Department id.
356    pub dept_id: i64,
357    /// Language code (for example `zh_CN`).
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub language: Option<String>,
360}
361
362impl ContactListSubDepartmentIdsRequest {
363    /// Creates a request with required fields.
364    #[must_use]
365    pub fn new(dept_id: i64) -> Self {
366        Self {
367            dept_id,
368            language: None,
369        }
370    }
371
372    /// Sets language preference.
373    #[must_use]
374    pub fn language(mut self, value: impl Into<String>) -> Self {
375        self.language = Some(value.into());
376        self
377    }
378}
379
380impl ContactListSubDepartmentsRequest {
381    /// Creates a request with required fields.
382    #[must_use]
383    pub fn new(dept_id: i64) -> Self {
384        Self {
385            dept_id,
386            language: None,
387        }
388    }
389
390    /// Sets language preference.
391    #[must_use]
392    pub fn language(mut self, value: impl Into<String>) -> Self {
393        self.language = Some(value.into());
394        self
395    }
396}
397
398/// Form field item for approval instance creation.
399#[derive(Debug, Clone, Serialize)]
400pub struct ApprovalFormComponentValue {
401    /// Form field name.
402    pub name: String,
403    /// Form field value.
404    pub value: String,
405}
406
407impl ApprovalFormComponentValue {
408    /// Creates a form field item.
409    #[must_use]
410    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
411        Self {
412            name: name.into(),
413            value: value.into(),
414        }
415    }
416}
417
418/// Request for creating an approval process instance.
419#[derive(Debug, Clone, Serialize)]
420pub struct ApprovalCreateProcessInstanceRequest {
421    /// Process code.
422    pub process_code: String,
423    /// Originator user id.
424    pub originator_user_id: String,
425    /// Department id.
426    pub dept_id: i64,
427    /// Optional approvers list.
428    #[serde(skip_serializing_if = "Option::is_none")]
429    pub approvers: Option<String>,
430    /// Optional cc list.
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub cc_list: Option<String>,
433    /// Optional cc position.
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub cc_position: Option<String>,
436    /// Form fields.
437    pub form_component_values: Vec<ApprovalFormComponentValue>,
438}
439
440impl ApprovalCreateProcessInstanceRequest {
441    /// Creates a request with required fields.
442    #[must_use]
443    pub fn new(
444        process_code: impl Into<String>,
445        originator_user_id: impl Into<String>,
446        dept_id: i64,
447        form_component_values: Vec<ApprovalFormComponentValue>,
448    ) -> Self {
449        Self {
450            process_code: process_code.into(),
451            originator_user_id: originator_user_id.into(),
452            dept_id,
453            approvers: None,
454            cc_list: None,
455            cc_position: None,
456            form_component_values,
457        }
458    }
459
460    /// Sets approvers list.
461    #[must_use]
462    pub fn approvers(mut self, value: impl Into<String>) -> Self {
463        self.approvers = Some(value.into());
464        self
465    }
466
467    /// Sets cc list.
468    #[must_use]
469    pub fn cc_list(mut self, value: impl Into<String>) -> Self {
470        self.cc_list = Some(value.into());
471        self
472    }
473
474    /// Sets cc position.
475    #[must_use]
476    pub fn cc_position(mut self, value: impl Into<String>) -> Self {
477        self.cc_position = Some(value.into());
478        self
479    }
480}
481
482/// Request for listing approval process instance ids.
483#[derive(Debug, Clone, Serialize)]
484pub struct ApprovalListProcessInstanceIdsRequest {
485    /// Start timestamp in milliseconds.
486    pub start_time: i64,
487    /// End timestamp in milliseconds.
488    pub end_time: i64,
489    /// Cursor for pagination.
490    pub cursor: i64,
491    /// Page size.
492    pub size: i64,
493    /// Optional process code.
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub process_code: Option<String>,
496    /// Optional user id list.
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub userid_list: Option<String>,
499}
500
501/// Request for terminating an approval process instance.
502#[derive(Debug, Clone, Serialize)]
503pub struct ApprovalTerminateProcessInstanceRequest {
504    /// Process instance id.
505    pub process_instance_id: String,
506    /// Operator user id.
507    pub operating_userid: String,
508    /// Whether operator is system.
509    #[serde(skip_serializing_if = "Option::is_none")]
510    pub is_system: Option<bool>,
511    /// Optional termination remark.
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub remark: Option<String>,
514}
515
516impl ApprovalTerminateProcessInstanceRequest {
517    /// Creates a request with required fields.
518    #[must_use]
519    pub fn new(
520        process_instance_id: impl Into<String>,
521        operating_userid: impl Into<String>,
522    ) -> Self {
523        Self {
524            process_instance_id: process_instance_id.into(),
525            operating_userid: operating_userid.into(),
526            is_system: None,
527            remark: None,
528        }
529    }
530
531    /// Marks the operation as system-initiated.
532    #[must_use]
533    pub fn is_system(mut self, value: bool) -> Self {
534        self.is_system = Some(value);
535        self
536    }
537
538    /// Sets termination remark.
539    #[must_use]
540    pub fn remark(mut self, value: impl Into<String>) -> Self {
541        self.remark = Some(value.into());
542        self
543    }
544}
545
546impl ApprovalListProcessInstanceIdsRequest {
547    /// Creates a request with required fields.
548    #[must_use]
549    pub fn new(start_time: i64, end_time: i64, cursor: i64, size: i64) -> Self {
550        Self {
551            start_time,
552            end_time,
553            cursor,
554            size,
555            process_code: None,
556            userid_list: None,
557        }
558    }
559
560    /// Sets process code filter.
561    #[must_use]
562    pub fn process_code(mut self, value: impl Into<String>) -> Self {
563        self.process_code = Some(value.into());
564        self
565    }
566
567    /// Sets user id list filter.
568    #[must_use]
569    pub fn userid_list(mut self, value: impl Into<String>) -> Self {
570        self.userid_list = Some(value.into());
571        self
572    }
573}
574
575#[derive(Debug, Clone, Deserialize)]
576#[non_exhaustive]
577/// Response payload for process-instance id listing.
578pub struct ApprovalListProcessInstanceIdsResult {
579    /// Process instance ids.
580    pub list: Vec<String>,
581    /// Cursor for next page.
582    pub next_cursor: Option<i64>,
583}
584
585#[derive(Debug, Clone, Serialize, Deserialize)]
586#[non_exhaustive]
587/// Typed user object from contact APIs.
588pub struct ContactUser {
589    /// DingTalk user id.
590    #[serde(default)]
591    pub userid: Option<String>,
592    /// DingTalk union id.
593    #[serde(default)]
594    pub unionid: Option<String>,
595    /// Display name.
596    #[serde(default)]
597    pub name: Option<String>,
598    /// Mobile number.
599    #[serde(default)]
600    pub mobile: Option<String>,
601    /// Additional response fields not modeled explicitly.
602    #[serde(flatten, default)]
603    pub extra: BTreeMap<String, Value>,
604}
605
606#[derive(Debug, Clone, Serialize, Deserialize)]
607#[non_exhaustive]
608/// Response payload for contact user listing.
609pub struct ContactListUsersResult {
610    /// Whether there are more records.
611    #[serde(default)]
612    pub has_more: Option<bool>,
613    /// Cursor for the next page.
614    #[serde(default)]
615    pub next_cursor: Option<i64>,
616    /// User records in this page.
617    #[serde(default)]
618    pub list: Vec<ContactUser>,
619    /// Additional response fields not modeled explicitly.
620    #[serde(flatten, default)]
621    pub extra: BTreeMap<String, Value>,
622}
623
624#[derive(Debug, Clone, Serialize, Deserialize)]
625#[non_exhaustive]
626/// Response payload for contact user creation.
627pub struct ContactCreateUserResult {
628    /// Created user id.
629    #[serde(default)]
630    pub userid: Option<String>,
631    /// Related union id when provided by DingTalk.
632    #[serde(default)]
633    pub unionid: Option<String>,
634    /// Additional response fields not modeled explicitly.
635    #[serde(flatten, default)]
636    pub extra: BTreeMap<String, Value>,
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize)]
640#[non_exhaustive]
641/// Typed department object from contact APIs.
642pub struct ContactDepartment {
643    /// Department id.
644    #[serde(default, alias = "id")]
645    pub dept_id: Option<i64>,
646    /// Department name.
647    #[serde(default)]
648    pub name: Option<String>,
649    /// Parent department id.
650    #[serde(default)]
651    pub parent_id: Option<i64>,
652    /// Additional response fields not modeled explicitly.
653    #[serde(flatten, default)]
654    pub extra: BTreeMap<String, Value>,
655}
656
657#[derive(Debug, Clone, Serialize, Deserialize)]
658#[non_exhaustive]
659/// Response payload for listing child departments.
660pub struct ContactListSubDepartmentsResult {
661    /// Child department records.
662    #[serde(default, alias = "dept_list", alias = "department", alias = "list")]
663    pub departments: Vec<ContactDepartment>,
664    /// Additional response fields not modeled explicitly.
665    #[serde(flatten, default)]
666    pub extra: BTreeMap<String, Value>,
667}
668
669#[derive(Debug, Clone, Serialize, Deserialize)]
670#[non_exhaustive]
671/// Response payload for listing child department ids.
672pub struct ContactListSubDepartmentIdsResult {
673    /// Child department id list.
674    #[serde(default, alias = "list", alias = "department_ids")]
675    pub dept_id_list: Vec<i64>,
676    /// Additional response fields not modeled explicitly.
677    #[serde(flatten, default)]
678    pub extra: BTreeMap<String, Value>,
679}
680
681#[derive(Debug, Clone, Serialize, Deserialize)]
682#[non_exhaustive]
683/// Response payload for creating a department.
684pub struct ContactCreateDepartmentResult {
685    /// Created department id.
686    #[serde(default, alias = "id")]
687    pub dept_id: Option<i64>,
688    /// Additional response fields not modeled explicitly.
689    #[serde(flatten, default)]
690    pub extra: BTreeMap<String, Value>,
691}
692
693#[derive(Debug, Clone, Serialize, Deserialize)]
694#[non_exhaustive]
695/// Typed approval process instance payload.
696pub struct ApprovalProcessInstance {
697    /// Approval process instance id.
698    #[serde(default)]
699    pub process_instance_id: Option<String>,
700    /// Additional response fields not modeled explicitly.
701    #[serde(flatten, default)]
702    pub extra: BTreeMap<String, Value>,
703}
704
705#[cfg(test)]
706mod tests {
707    use super::{
708        ApprovalProcessInstance, ApprovalTerminateProcessInstanceRequest, ContactListUsersResult,
709    };
710
711    #[test]
712    fn approval_terminate_request_serializes_optional_remark() {
713        let request = ApprovalTerminateProcessInstanceRequest::new("PROC-1", "user-1")
714            .is_system(true)
715            .remark("cancelled by sdk test");
716
717        let value = serde_json::to_value(request).expect("request should serialize");
718        assert_eq!(
719            value.get("remark").and_then(serde_json::Value::as_str),
720            Some("cancelled by sdk test")
721        );
722    }
723
724    #[test]
725    fn contact_list_users_result_parses_known_and_extra_fields() {
726        let raw = r#"{
727            "has_more": true,
728            "next_cursor": 30,
729            "list": [{"userid":"u-1","name":"Alice"}],
730            "unknown_flag": 1
731        }"#;
732        let parsed: ContactListUsersResult =
733            serde_json::from_str(raw).expect("response should deserialize");
734
735        assert_eq!(parsed.has_more, Some(true));
736        assert_eq!(parsed.next_cursor, Some(30));
737        assert_eq!(parsed.list.len(), 1);
738        assert_eq!(parsed.list[0].userid.as_deref(), Some("u-1"));
739        assert_eq!(
740            parsed
741                .extra
742                .get("unknown_flag")
743                .and_then(serde_json::Value::as_i64),
744            Some(1)
745        );
746    }
747
748    #[test]
749    fn approval_process_instance_parses_known_and_extra_fields() {
750        let raw = r#"{"process_instance_id":"PROC-1","biz_id":"BIZ-1"}"#;
751        let parsed: ApprovalProcessInstance =
752            serde_json::from_str(raw).expect("response should deserialize");
753
754        assert_eq!(parsed.process_instance_id.as_deref(), Some("PROC-1"));
755        assert_eq!(
756            parsed
757                .extra
758                .get("biz_id")
759                .and_then(serde_json::Value::as_str),
760            Some("BIZ-1")
761        );
762    }
763}