metabase_api_rs/core/models/
collection.rs

1use super::common::CollectionId;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5/// Represents a Metabase Collection
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Collection {
8    pub id: Option<CollectionId>,
9    pub name: String,
10    #[serde(skip_serializing_if = "Option::is_none")]
11    pub description: Option<String>,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub color: Option<String>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub parent_id: Option<i32>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub personal_owner_id: Option<i64>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub namespace: Option<String>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub slug: Option<String>,
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub archived: Option<bool>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub can_write: Option<bool>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub created_at: Option<DateTime<Utc>>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub updated_at: Option<DateTime<Utc>>,
30    // Additional fields from API specification
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub authority_level: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub collection_position: Option<i32>,
35}
36
37impl Collection {
38    /// Create a new Collection with minimal required fields
39    pub fn new(id: Option<CollectionId>, name: String) -> Self {
40        Self {
41            id,
42            name,
43            description: None,
44            color: None,
45            parent_id: None,
46            personal_owner_id: None,
47            namespace: None,
48            slug: None,
49            archived: Some(false),
50            can_write: None,
51            created_at: None,
52            updated_at: None,
53            authority_level: None,
54            collection_position: None,
55        }
56    }
57
58    // Getters
59    pub fn id(&self) -> Option<CollectionId> {
60        self.id
61    }
62
63    pub fn name(&self) -> &str {
64        &self.name
65    }
66
67    pub fn description(&self) -> Option<&str> {
68        self.description.as_deref()
69    }
70
71    pub fn color(&self) -> Option<&str> {
72        self.color.as_deref()
73    }
74
75    pub fn parent_id(&self) -> Option<i32> {
76        self.parent_id
77    }
78
79    pub fn personal_owner_id(&self) -> Option<i64> {
80        self.personal_owner_id
81    }
82
83    pub fn namespace(&self) -> Option<&str> {
84        self.namespace.as_deref()
85    }
86
87    pub fn slug(&self) -> Option<&str> {
88        self.slug.as_deref()
89    }
90
91    pub fn archived(&self) -> Option<bool> {
92        self.archived
93    }
94
95    pub fn can_write(&self) -> Option<bool> {
96        self.can_write
97    }
98
99    pub fn authority_level(&self) -> Option<&str> {
100        self.authority_level.as_deref()
101    }
102
103    pub fn collection_position(&self) -> Option<i32> {
104        self.collection_position
105    }
106
107    /// Check if this is a personal collection
108    pub fn is_personal(&self) -> bool {
109        self.personal_owner_id.is_some()
110    }
111
112    /// Check if this is the root collection
113    pub fn is_root(&self) -> bool {
114        self.parent_id.is_none() && !self.is_personal()
115    }
116}
117
118/// Builder for creating Collection instances
119pub struct CollectionBuilder {
120    id: Option<CollectionId>,
121    name: String,
122    description: Option<String>,
123    color: Option<String>,
124    parent_id: Option<i32>,
125    personal_owner_id: Option<i64>,
126    namespace: Option<String>,
127    slug: Option<String>,
128    archived: Option<bool>,
129    can_write: Option<bool>,
130    created_at: Option<DateTime<Utc>>,
131    updated_at: Option<DateTime<Utc>>,
132    authority_level: Option<String>,
133    collection_position: Option<i32>,
134}
135
136impl CollectionBuilder {
137    /// Create a new CollectionBuilder with required fields
138    pub fn new(id: Option<CollectionId>, name: String) -> Self {
139        Self {
140            id,
141            name,
142            description: None,
143            color: None,
144            parent_id: None,
145            personal_owner_id: None,
146            namespace: None,
147            slug: None,
148            archived: Some(false),
149            can_write: None,
150            created_at: None,
151            updated_at: None,
152            authority_level: None,
153            collection_position: None,
154        }
155    }
156
157    /// Create a new CollectionBuilder for creating a new collection (ID will be assigned by server)
158    pub fn new_collection(name: impl Into<String>) -> Self {
159        Self::new(None, name.into())
160    }
161
162    pub fn description<S: Into<String>>(mut self, desc: S) -> Self {
163        self.description = Some(desc.into());
164        self
165    }
166
167    pub fn color<S: Into<String>>(mut self, color: S) -> Self {
168        self.color = Some(color.into());
169        self
170    }
171
172    pub fn parent_id(mut self, id: i32) -> Self {
173        self.parent_id = Some(id);
174        self
175    }
176
177    pub fn personal_owner_id(mut self, id: i64) -> Self {
178        self.personal_owner_id = Some(id);
179        self
180    }
181
182    pub fn namespace<S: Into<String>>(mut self, ns: S) -> Self {
183        self.namespace = Some(ns.into());
184        self
185    }
186
187    pub fn slug<S: Into<String>>(mut self, slug: S) -> Self {
188        self.slug = Some(slug.into());
189        self
190    }
191
192    pub fn archived(mut self, archived: bool) -> Self {
193        self.archived = Some(archived);
194        self
195    }
196
197    pub fn can_write(mut self, can_write: bool) -> Self {
198        self.can_write = Some(can_write);
199        self
200    }
201
202    pub fn created_at(mut self, dt: DateTime<Utc>) -> Self {
203        self.created_at = Some(dt);
204        self
205    }
206
207    pub fn updated_at(mut self, dt: DateTime<Utc>) -> Self {
208        self.updated_at = Some(dt);
209        self
210    }
211
212    pub fn authority_level<S: Into<String>>(mut self, level: S) -> Self {
213        self.authority_level = Some(level.into());
214        self
215    }
216
217    pub fn collection_position(mut self, position: i32) -> Self {
218        self.collection_position = Some(position);
219        self
220    }
221
222    /// Build the Collection instance
223    pub fn build(self) -> Collection {
224        Collection {
225            id: self.id,
226            name: self.name,
227            description: self.description,
228            color: self.color,
229            parent_id: self.parent_id,
230            personal_owner_id: self.personal_owner_id,
231            namespace: self.namespace,
232            slug: self.slug,
233            archived: self.archived,
234            can_write: self.can_write,
235            created_at: self.created_at,
236            updated_at: self.updated_at,
237            authority_level: self.authority_level,
238            collection_position: self.collection_position,
239        }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_collection_creation() {
249        let collection = Collection::new(Some(CollectionId(1)), "Test Collection".to_string());
250
251        assert_eq!(collection.id(), Some(CollectionId(1)));
252        assert_eq!(collection.name(), "Test Collection");
253        assert!(collection.description().is_none());
254        assert!(collection.parent_id().is_none());
255        assert!(!collection.is_personal());
256    }
257
258    #[test]
259    fn test_collection_hierarchy() {
260        let _parent = Collection::new(Some(CollectionId(1)), "Parent Collection".to_string());
261
262        let child = CollectionBuilder::new(Some(CollectionId(2)), "Child Collection".to_string())
263            .parent_id(1)
264            .description("A child collection")
265            .build();
266
267        assert_eq!(child.parent_id(), Some(1));
268        assert_eq!(child.description(), Some("A child collection"));
269    }
270
271    #[test]
272    fn test_personal_collection() {
273        let personal = CollectionBuilder::new(
274            Some(CollectionId(100)),
275            "My Personal Collection".to_string(),
276        )
277        .personal_owner_id(42)
278        .build();
279
280        assert_eq!(personal.personal_owner_id(), Some(42));
281        assert!(personal.is_personal());
282    }
283
284    #[test]
285    fn test_collection_with_authority_level() {
286        let collection =
287            CollectionBuilder::new(Some(CollectionId(10)), "Admin Collection".to_string())
288                .authority_level("admin")
289                .collection_position(1)
290                .build();
291
292        assert_eq!(collection.authority_level(), Some("admin"));
293        assert_eq!(collection.collection_position(), Some(1));
294    }
295}