metabase_api_rs/core/models/
field.rs

1//! Field model representing database fields in Metabase
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use super::database::{FieldId, TableId};
8
9/// Represents a database field with full metadata
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Field {
12    /// Field ID
13    pub id: FieldId,
14
15    /// Table ID this field belongs to
16    pub table_id: TableId,
17
18    /// Field name in the database
19    pub name: String,
20
21    /// Display name for UI
22    pub display_name: String,
23
24    /// Database type (e.g., "VARCHAR(255)", "INTEGER")
25    pub database_type: String,
26
27    /// Metabase base type (e.g., "type/Text", "type/Integer")
28    pub base_type: String,
29
30    /// Semantic type (e.g., "type/Email", "type/Category")
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub semantic_type: Option<String>,
33
34    /// Field description
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub description: Option<String>,
37
38    /// Whether this field is active
39    #[serde(default = "default_true")]
40    pub active: bool,
41
42    /// Position in the table
43    pub position: i32,
44
45    /// Whether this is a primary key
46    #[serde(default)]
47    pub is_pk: bool,
48
49    /// Foreign key target field ID
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub fk_target_field_id: Option<FieldId>,
52
53    /// Visibility type
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub visibility_type: Option<String>,
56
57    /// Field fingerprint (contains statistics about the field)
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub fingerprint: Option<Value>,
60
61    /// Whether field values should be cached
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub has_field_values: Option<String>,
64
65    /// Settings for this field
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub settings: Option<Value>,
68
69    /// When the field was created
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub created_at: Option<DateTime<Utc>>,
72
73    /// When the field was last updated
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub updated_at: Option<DateTime<Utc>>,
76}
77
78/// Field values for a specific field
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct FieldValues {
81    /// Field ID
82    pub field_id: FieldId,
83
84    /// List of distinct values
85    pub values: Vec<Value>,
86
87    /// Whether there are more values than returned
88    #[serde(default)]
89    pub has_more_values: bool,
90}
91
92/// Request to update a field
93#[derive(Debug, Clone, Default, Serialize)]
94pub struct UpdateFieldRequest {
95    /// New display name
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub display_name: Option<String>,
98
99    /// New description
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub description: Option<String>,
102
103    /// New semantic type
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub semantic_type: Option<String>,
106
107    /// New visibility type
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub visibility_type: Option<String>,
110
111    /// New settings
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub settings: Option<Value>,
114
115    /// Whether field values should be cached
116    #[serde(skip_serializing_if = "Option::is_none")]
117    pub has_field_values: Option<String>,
118}
119
120fn default_true() -> bool {
121    true
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use serde_json::json;
128
129    #[test]
130    fn test_field_serialization() {
131        let field = Field {
132            id: FieldId(1),
133            table_id: TableId(10),
134            name: "email".to_string(),
135            display_name: "Email Address".to_string(),
136            database_type: "VARCHAR(255)".to_string(),
137            base_type: "type/Text".to_string(),
138            semantic_type: Some("type/Email".to_string()),
139            description: Some("User email address".to_string()),
140            active: true,
141            position: 3,
142            is_pk: false,
143            fk_target_field_id: None,
144            visibility_type: Some("normal".to_string()),
145            fingerprint: None,
146            has_field_values: Some("list".to_string()),
147            settings: None,
148            created_at: None,
149            updated_at: None,
150        };
151
152        let json = serde_json::to_value(&field).unwrap();
153        assert_eq!(json["name"], "email");
154        assert_eq!(json["base_type"], "type/Text");
155        assert_eq!(json["semantic_type"], "type/Email");
156    }
157
158    #[test]
159    fn test_field_values() {
160        let values = FieldValues {
161            field_id: FieldId(5),
162            values: vec![json!("option1"), json!("option2"), json!("option3")],
163            has_more_values: false,
164        };
165
166        assert_eq!(values.values.len(), 3);
167        assert!(!values.has_more_values);
168    }
169
170    #[test]
171    fn test_update_field_request() {
172        let request = UpdateFieldRequest {
173            display_name: Some("Updated Name".to_string()),
174            semantic_type: Some("type/Category".to_string()),
175            ..Default::default()
176        };
177
178        let json = serde_json::to_value(&request).unwrap();
179        assert_eq!(json["display_name"], "Updated Name");
180        assert_eq!(json["semantic_type"], "type/Category");
181        assert_eq!(json.get("description"), None);
182    }
183}