kintone/model/record.rs
1//! # Kintone Record Models
2//!
3//! This module provides the core data structures for working with records in Kintone applications.
4//! Records are the fundamental data containers that hold field values, similar to rows in a database.
5//!
6//! # Core Types
7//!
8//! - [`Record`] - A collection of field values representing a single record
9//! - [`FieldValue`] - Enum containing all possible field value types
10//! - [`FieldType`] - Enum identifying the type of a field
11//! - [`TableRow`] - Represents a row within a table field
12//! - [`RecordComment`] - Comments associated with records
13//!
14//! # Basic Usage
15//!
16//! Create and manipulate records with field values:
17//!
18//! ```rust
19//! use kintone::model::record::{Record, FieldValue};
20//! use bigdecimal::BigDecimal;
21//!
22//! // Create a record with initial fields
23//! let record = Record::from([
24//! ("title", FieldValue::SingleLineText("Project Alpha".to_string())),
25//! ("budget", FieldValue::Number(Some(50000.into()))),
26//! ("priority", FieldValue::RadioButton(Some("High".to_string()))),
27//! ("active", FieldValue::CheckBox(vec!["Yes".to_string()])),
28//! ]);
29//!
30//! // Read field value
31//! let Some(FieldValue::SingleLineText(title)) = record.get("title") else {
32//! panic!("Title is not set");
33//! };
34//! println!("Title: {}", title);
35//! ```
36//!
37//! ## Alternative Initialization Methods
38//!
39//! You can also create records using the `From` trait with an array of field tuples:
40//!
41//! ```rust
42//! use kintone::model::record::{Record, FieldValue};
43//! use bigdecimal::BigDecimal;
44//!
45//! // Create a record with initial fields using From trait
46//! let record = Record::from([
47//! ("name", FieldValue::SingleLineText("John Doe".to_string())),
48//! ("age", FieldValue::Number(Some(30.into()))),
49//! ("email", FieldValue::SingleLineText("john@example.com".to_string())),
50//! ]);
51//!
52//! // This is equivalent to creating an empty record and adding fields one by one
53//! assert_eq!(record.field_codes().count(), 3);
54//! ```
55//!
56//! # Working with Table Fields
57//!
58//! Table fields contain multiple rows of related data:
59//!
60//! ```rust
61//! use kintone::model::record::{Record, FieldValue, TableRow};
62//!
63//! // Create table rows
64//! let table_rows = vec![
65//! TableRow::from([
66//! ("item", FieldValue::SingleLineText("Item 1".to_string())),
67//! ("quantity", FieldValue::Number(Some(10.into()))),
68//! ]),
69//! TableRow::from([
70//! ("item", FieldValue::SingleLineText("Item 2".to_string())),
71//! ("quantity", FieldValue::Number(Some(5.into()))),
72//! ]),
73//! ];
74//!
75//! // Create record with subtable
76//! let record = Record::from([
77//! ("items", FieldValue::Subtable(table_rows)),
78//! ]);
79//! ```
80//!
81//! # Type-Safe Field Access
82//!
83//! The [`FieldValue`] enum provides type-safe access to field data while handling
84//! Kintone's dynamic field system. Each variant corresponds to a specific field type
85//! and ensures proper serialization/deserialization with the Kintone API.
86
87use std::{borrow::Borrow, collections::BTreeMap};
88
89use bigdecimal::BigDecimal;
90use chrono::{DateTime, FixedOffset, NaiveDate, NaiveTime};
91use enum_assoc::Assoc;
92use serde::{Deserialize, Serialize};
93
94use crate::{
95 internal::serde_helper::{stringified, stringified_or_empty},
96 model::{Entity, FileBody, Group, Organization, User},
97};
98
99/// Represents a record in a Kintone application.
100///
101/// A record is a collection of field values identified by field codes (names).
102/// Records are the primary data structure in Kintone applications, similar to
103/// rows in a database table. Each record can contain various types of fields
104/// such as text, numbers, dates, attachments, and more.
105///
106/// # Examples
107///
108/// ```rust
109/// use kintone::model::record::{Record, FieldValue};
110///
111/// // Create a record with initial fields
112/// let record = Record::from([
113/// ("name", FieldValue::SingleLineText("John Doe".to_owned())),
114/// ("age", FieldValue::Number(Some(30.into()))),
115/// ("email", FieldValue::Link("john@example.com".to_owned())),
116/// ]);
117///
118/// // Read field values
119/// if let Some(FieldValue::SingleLineText(name)) = record.get("name") {
120/// println!("Name: {}", name);
121/// }
122/// ```
123#[derive(Clone, Serialize, Deserialize)]
124pub struct Record {
125 #[serde(flatten)]
126 fields: BTreeMap<String, FieldValue>,
127}
128
129impl Record {
130 /// Creates a new empty record.
131 ///
132 /// # Examples
133 ///
134 /// ```rust
135 /// use kintone::model::record::Record;
136 ///
137 /// let record = Record::new();
138 /// assert_eq!(record.fields().len(), 0);
139 /// ```
140 pub fn new() -> Self {
141 Record {
142 fields: BTreeMap::new(),
143 }
144 }
145
146 /// Creates a copy of the record without built-in system fields.
147 ///
148 /// Built-in fields are system-managed fields like record ID, creator, creation time,
149 /// modifier, and modification time. This method is useful when you want to create
150 /// a new record based on an existing one, excluding the system-generated fields.
151 ///
152 /// # Examples
153 ///
154 /// ```rust
155 /// use kintone::model::record::{Record, FieldValue};
156 ///
157 /// let mut original = Record::new();
158 /// original.put_field("$id", FieldValue::__ID__(42));
159 /// original.put_field("name", FieldValue::SingleLineText("John".to_owned()));
160 ///
161 /// let clean_copy = original.clone_without_builtins();
162 ///
163 /// // clean_copy only contains user-defined fields, not system fields
164 /// assert_eq!(clean_copy.field_codes().collect::<Vec<_>>(), ["name"]);
165 /// ```
166 pub fn clone_without_builtins(&self) -> Self {
167 self.fields()
168 .filter_map(|(code, value)| {
169 if value.field_type().is_builtin() {
170 None
171 } else {
172 Some((code.to_owned(), value.clone()))
173 }
174 })
175 .collect()
176 }
177
178 /// Gets a reference to the field value for the specified field code.
179 ///
180 /// # Arguments
181 ///
182 /// * `field_code` - The field code (name) to look up
183 ///
184 /// # Returns
185 ///
186 /// `Some(&FieldValue)` if the field exists, `None` otherwise
187 ///
188 /// # Examples
189 ///
190 /// ```rust
191 /// use kintone::model::record::{Record, FieldValue};
192 ///
193 /// let mut record = Record::new();
194 /// record.put_field("name", FieldValue::SingleLineText("John".to_owned()));
195 ///
196 /// if let Some(FieldValue::SingleLineText(name)) = record.get("name") {
197 /// println!("Name: {}", name);
198 /// }
199 /// ```
200 pub fn get(&self, field_code: &str) -> Option<&FieldValue> {
201 self.fields.get(field_code)
202 }
203
204 /// Gets a mutable reference to the field value for the specified field code.
205 ///
206 /// # Arguments
207 ///
208 /// * `field_code` - The field code (name) to look up
209 ///
210 /// # Returns
211 ///
212 /// `Some(&mut FieldValue)` if the field exists, `None` otherwise
213 ///
214 /// # Examples
215 ///
216 /// ```rust
217 /// use kintone::model::record::{Record, FieldValue};
218 ///
219 /// let mut record = Record::new();
220 /// record.put_field("name", FieldValue::SingleLineText("John".to_owned()));
221 ///
222 /// if let Some(FieldValue::SingleLineText(name)) = record.get_mut("name") {
223 /// *name = "Jane".to_owned();
224 /// }
225 /// ```
226 pub fn get_mut(&mut self, field_code: &str) -> Option<&mut FieldValue> {
227 self.fields.get_mut(field_code)
228 }
229
230 /// Returns an iterator over all field codes and values in the record.
231 ///
232 /// The iterator yields tuples of `(&str, &FieldValue)` representing
233 /// the field code and its corresponding value.
234 ///
235 /// # Examples
236 ///
237 /// ```rust
238 /// use kintone::model::record::{Record, FieldValue};
239 ///
240 /// let record = Record::from([
241 /// ("name", FieldValue::SingleLineText("John".to_owned())),
242 /// ("age", FieldValue::Number(Some(30.into()))),
243 /// ]);
244 ///
245 /// for (field_code, field_value) in record.fields() {
246 /// println!("{}: {:?}", field_code, field_value);
247 /// }
248 /// ```
249 pub fn fields(&self) -> impl ExactSizeIterator<Item = (&'_ str, &'_ FieldValue)> + Clone {
250 self.fields.iter().map(|(k, v)| (k.borrow(), v))
251 }
252
253 /// Returns a mutable iterator over all field codes and values in the record.
254 ///
255 /// The iterator yields tuples of `(&str, &mut FieldValue)` representing
256 /// the field code and its corresponding mutable value reference.
257 ///
258 /// # Examples
259 ///
260 /// ```rust
261 /// use kintone::model::record::{Record, FieldValue};
262 ///
263 /// let mut record = Record::new();
264 /// record.put_field("name", FieldValue::SingleLineText("John".to_owned()));
265 ///
266 /// for (field_code, field_value) in record.fields_mut() {
267 /// if let FieldValue::SingleLineText(text) = field_value {
268 /// *text = text.to_uppercase();
269 /// }
270 /// }
271 /// ```
272 pub fn fields_mut(&mut self) -> impl ExactSizeIterator<Item = (&'_ str, &'_ mut FieldValue)> {
273 self.fields.iter_mut().map(|(k, v)| (k.borrow(), v))
274 }
275
276 /// Returns an iterator over all field codes in the record.
277 ///
278 /// # Examples
279 ///
280 /// ```rust
281 /// use kintone::model::record::{Record, FieldValue};
282 ///
283 /// let record = Record::from([
284 /// ("name", FieldValue::SingleLineText("John".to_owned())),
285 /// ("age", FieldValue::Number(Some(30.into()))),
286 /// ]);
287 ///
288 /// let field_codes: Vec<_> = record.field_codes().collect();
289 /// assert_eq!(field_codes.len(), 2);
290 /// ```
291 pub fn field_codes(&self) -> impl ExactSizeIterator<Item = &'_ str> + Clone {
292 self.fields.keys().map(|k| k.borrow())
293 }
294
295 /// Returns an iterator over all field values in the record.
296 ///
297 /// # Examples
298 ///
299 /// ```rust
300 /// use kintone::model::record::{Record, FieldValue};
301 ///
302 /// let record = Record::from([
303 /// ("name", FieldValue::SingleLineText("John".to_owned())),
304 /// ("age", FieldValue::Number(Some(30.into()))),
305 /// ]);
306 ///
307 /// let field_values: Vec<_> = record.field_values().collect();
308 /// assert_eq!(field_values.len(), 2);
309 /// ```
310 pub fn field_values(&self) -> impl ExactSizeIterator<Item = &'_ FieldValue> + Clone {
311 self.fields.values()
312 }
313
314 /// Inserts a field value into the record.
315 ///
316 /// If the field already exists, its value is replaced and the old value is returned.
317 ///
318 /// # Arguments
319 ///
320 /// * `field_code` - The field code (name) for the field
321 /// * `value` - The field value to insert
322 ///
323 /// # Returns
324 ///
325 /// The previous value if the field existed, `None` otherwise
326 ///
327 /// # Examples
328 ///
329 /// ```rust
330 /// use kintone::model::record::{Record, FieldValue};
331 ///
332 /// let mut record = Record::new();
333 /// let old_value = record.put_field("name", FieldValue::SingleLineText("John".to_owned()));
334 /// assert!(old_value.is_none());
335 ///
336 /// let replaced_value = record.put_field("name", FieldValue::SingleLineText("Jane".to_owned()));
337 /// assert!(replaced_value.is_some());
338 /// ```
339 pub fn put_field(
340 &mut self,
341 field_code: impl Into<String>,
342 value: FieldValue,
343 ) -> Option<FieldValue> {
344 self.fields.insert(field_code.into(), value)
345 }
346
347 /// Removes a field from the record.
348 ///
349 /// # Arguments
350 ///
351 /// * `field_code` - The field code to remove
352 ///
353 /// # Returns
354 ///
355 /// The removed field value if it existed, `None` otherwise
356 ///
357 /// # Examples
358 ///
359 /// ```rust
360 /// use kintone::model::record::{Record, FieldValue};
361 ///
362 /// let mut record = Record::new();
363 /// record.put_field("name", FieldValue::SingleLineText("John".to_owned()));
364 ///
365 /// let removed = record.remove_field("name");
366 /// assert!(removed.is_some());
367 ///
368 /// let not_found = record.remove_field("nonexistent");
369 /// assert!(not_found.is_none());
370 /// ```
371 pub fn remove_field(&mut self, field_code: &str) -> Option<FieldValue> {
372 self.fields.remove(field_code)
373 }
374
375 /// Gets the record ID if available.
376 ///
377 /// The record ID is a system-generated unique identifier for the record.
378 /// This field is only available for records that have been saved to Kintone.
379 ///
380 /// # Returns
381 ///
382 /// `Some(id)` if the record has an ID, `None` otherwise
383 ///
384 /// # Examples
385 ///
386 /// ```rust
387 /// use kintone::model::record::{Record, FieldValue};
388 ///
389 /// let mut record = Record::new();
390 /// assert!(record.id().is_none()); // New records don't have IDs
391 ///
392 /// record.put_field("$id", FieldValue::__ID__(42));
393 /// assert_eq!(record.id(), Some(42));
394 /// ```
395 pub fn id(&self) -> Option<u64> {
396 let Some(FieldValue::__ID__(value)) = self.get("$id") else {
397 return None;
398 };
399 Some(*value)
400 }
401
402 /// Gets the record revision number if available.
403 ///
404 /// The revision number is a system-managed version counter that increments
405 /// each time the record is updated. This is used for optimistic locking
406 /// to prevent concurrent modification conflicts.
407 ///
408 /// # Returns
409 ///
410 /// `Some(revision)` if the record has a revision number, `None` otherwise
411 ///
412 /// # Examples
413 ///
414 /// ```rust
415 /// use kintone::model::record::{Record, FieldValue};
416 ///
417 /// let mut record = Record::new();
418 /// assert!(record.revision().is_none()); // New records don't have revisions
419 ///
420 /// record.put_field("$revision", FieldValue::__REVISION__(3));
421 /// assert_eq!(record.revision(), Some(3));
422 /// ```
423 pub fn revision(&self) -> Option<u64> {
424 let Some(FieldValue::__REVISION__(value)) = self.get("$revision") else {
425 return None;
426 };
427 Some(*value)
428 }
429}
430
431impl std::fmt::Debug for Record {
432 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
433 let mut debug_struct = f.debug_struct("Record");
434
435 for (field_code, field_value) in self.fields() {
436 debug_struct.field(field_code, field_value);
437 }
438
439 debug_struct.finish()
440 }
441}
442
443impl Default for Record {
444 fn default() -> Self {
445 Self::new()
446 }
447}
448
449impl<const N: usize, S: Into<String>> From<[(S, FieldValue); N]> for Record {
450 fn from(fields: [(S, FieldValue); N]) -> Self {
451 Self {
452 fields: BTreeMap::from(fields.map(|(k, v)| (k.into(), v))),
453 }
454 }
455}
456
457impl FromIterator<(String, FieldValue)> for Record {
458 fn from_iter<T: IntoIterator<Item = (String, FieldValue)>>(iter: T) -> Self {
459 Self {
460 fields: BTreeMap::from_iter(iter),
461 }
462 }
463}
464
465/// Represents the type of a field in a Kintone application.
466///
467/// Each field in a Kintone app has a specific type that determines what kind of data
468/// it can store and how it behaves. Some field types are built-in system fields
469/// (like record ID, creation time) while others are user-defined fields.
470///
471/// The `is_builtin()` method can be used to distinguish between system-managed
472/// and user-defined fields.
473///
474/// # Examples
475///
476/// ```rust
477/// use kintone::model::record::FieldType;
478///
479/// assert!(!FieldType::SingleLineText.is_builtin());
480/// assert!(FieldType::CreatedTime.is_builtin());
481/// assert!(FieldType::Creator.is_builtin());
482/// ```
483#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Assoc)]
484#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
485#[func(pub const fn is_builtin(&self) -> bool)]
486#[non_exhaustive]
487pub enum FieldType {
488 /// Calculated field that computes values based on other fields
489 #[assoc(is_builtin = false)]
490 Calc,
491
492 /// System field for record categories (built-in)
493 #[assoc(is_builtin = true)]
494 Category,
495
496 /// Checkbox field for multiple selection options
497 #[assoc(is_builtin = false)]
498 CheckBox,
499
500 /// System field for record creation timestamp (built-in)
501 #[assoc(is_builtin = true)]
502 CreatedTime,
503
504 /// System field for record creator information (built-in)
505 #[assoc(is_builtin = true)]
506 Creator,
507
508 /// Date field for storing dates without time
509 #[assoc(is_builtin = false)]
510 Date,
511
512 /// Date and time field for storing timestamps
513 #[assoc(is_builtin = false)]
514 Datetime,
515
516 /// Dropdown field for single selection from predefined options
517 #[assoc(is_builtin = false)]
518 DropDown,
519
520 /// File attachment field for storing uploaded files
521 #[assoc(is_builtin = false)]
522 File,
523
524 /// Group field for displaying related information
525 #[assoc(is_builtin = false)]
526 Group,
527
528 /// Group selection field for choosing from predefined groups
529 #[assoc(is_builtin = false)]
530 GroupSelect,
531
532 /// Horizontal rule field for visual separation
533 #[assoc(is_builtin = false)]
534 Hr,
535
536 /// Label field for displaying text information
537 #[assoc(is_builtin = false)]
538 Label,
539
540 /// Link field for storing URLs
541 #[assoc(is_builtin = false)]
542 Link,
543
544 /// System field for record modifier information (built-in)
545 #[assoc(is_builtin = true)]
546 Modifier,
547
548 /// Multi-line text field for longer text content
549 #[assoc(is_builtin = false)]
550 MultiLineText,
551
552 /// Multi-select field for choosing multiple options
553 #[assoc(is_builtin = false)]
554 MultiSelect,
555
556 /// Number field for storing numeric values
557 #[assoc(is_builtin = false)]
558 Number,
559
560 /// Organization selection field for choosing from organizational units
561 #[assoc(is_builtin = false)]
562 OrganizationSelect,
563
564 /// Radio button field for single selection
565 #[assoc(is_builtin = false)]
566 RadioButton,
567
568 /// System field for unique record numbers (built-in)
569 #[assoc(is_builtin = true)]
570 RecordNumber,
571
572 /// Reference table field for linking to other app records
573 #[assoc(is_builtin = false)]
574 ReferenceTable,
575
576 /// Rich text field for formatted text content
577 #[assoc(is_builtin = false)]
578 RichText,
579
580 /// Single-line text field for short text content
581 #[assoc(is_builtin = false)]
582 SingleLineText,
583
584 /// Spacer field for layout purposes
585 #[assoc(is_builtin = false)]
586 Spacer,
587
588 /// System field for workflow status (built-in)
589 #[assoc(is_builtin = true)]
590 Status,
591
592 /// System field for workflow status assignee (built-in)
593 #[assoc(is_builtin = true)]
594 StatusAssignee,
595
596 /// Subtable field for tabular data
597 #[assoc(is_builtin = false)]
598 Subtable,
599
600 #[assoc(is_builtin = false)]
601 Time,
602
603 #[assoc(is_builtin = true)]
604 UpdatedTime,
605
606 #[assoc(is_builtin = false)]
607 UserSelect,
608
609 #[serde(rename = "__ID__")]
610 #[assoc(is_builtin = true)]
611 __ID__,
612
613 #[serde(rename = "__REVISION__")]
614 #[assoc(is_builtin = true)]
615 __REVISION__,
616}
617
618/// Represents the value of a field in a Kintone record.
619///
620/// Each variant corresponds to a specific field type and contains the appropriate value type.
621/// The enum is marked as `#[non_exhaustive]` to allow for future field types without breaking changes.
622///
623/// # Examples
624///
625/// ```rust
626/// use kintone::model::record::FieldValue;
627/// use chrono::{DateTime, FixedOffset, NaiveDate};
628///
629/// // Text field
630/// let text_value = FieldValue::SingleLineText("Hello, world!".to_string());
631///
632/// // Date field
633/// let date = NaiveDate::from_ymd_opt(2023, 12, 25).unwrap();
634/// let date_value = FieldValue::Date(Some(date));
635///
636/// // Number field
637/// let number_value = FieldValue::Number(Some(42.into()));
638/// ```
639#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Assoc)]
640#[serde(tag = "type", content = "value", rename_all = "SCREAMING_SNAKE_CASE")]
641#[func(pub const fn field_type(&self) -> FieldType)]
642#[non_exhaustive]
643pub enum FieldValue {
644 #[assoc(field_type = FieldType::Calc)]
645 Calc(String),
646
647 #[assoc(field_type = FieldType::Category)]
648 Category(Vec<String>),
649
650 #[assoc(field_type = FieldType::CheckBox)]
651 CheckBox(Vec<String>),
652
653 #[assoc(field_type = FieldType::CreatedTime)]
654 CreatedTime(DateTime<FixedOffset>),
655
656 #[assoc(field_type = FieldType::Creator)]
657 Creator(User),
658
659 #[assoc(field_type = FieldType::Date)]
660 Date(Option<NaiveDate>),
661
662 #[assoc(field_type = FieldType::Datetime)]
663 DateTime(Option<DateTime<FixedOffset>>),
664
665 #[assoc(field_type = FieldType::DropDown)]
666 DropDown(Option<String>),
667
668 #[assoc(field_type = FieldType::File)]
669 File(Vec<FileBody>),
670
671 #[assoc(field_type = FieldType::File)]
672 GroupSelect(Vec<Group>),
673
674 #[assoc(field_type = FieldType::Link)]
675 Link(String),
676
677 #[assoc(field_type = FieldType::Modifier)]
678 Modifier(User),
679
680 #[assoc(field_type = FieldType::MultiLineText)]
681 MultiLineText(String),
682
683 #[assoc(field_type = FieldType::MultiSelect)]
684 MultiSelect(Vec<String>),
685
686 #[assoc(field_type = FieldType::Number)]
687 Number(#[serde(with = "stringified_or_empty")] Option<BigDecimal>),
688
689 #[assoc(field_type = FieldType::OrganizationSelect)]
690 OrganizationSelect(Vec<Organization>),
691
692 #[assoc(field_type = FieldType::RadioButton)]
693 RadioButton(Option<String>),
694
695 #[assoc(field_type = FieldType::RecordNumber)]
696 RecordNumber(String),
697
698 #[assoc(field_type = FieldType::ReferenceTable)]
699 RichText(String),
700
701 #[assoc(field_type = FieldType::SingleLineText)]
702 SingleLineText(String),
703
704 #[assoc(field_type = FieldType::Status)]
705 Status(String),
706
707 #[assoc(field_type = FieldType::StatusAssignee)]
708 StatusAssignee(Vec<User>),
709
710 #[assoc(field_type = FieldType::Subtable)]
711 Subtable(Vec<TableRow>),
712
713 #[assoc(field_type = FieldType::Time)]
714 Time(Option<NaiveTime>),
715
716 #[assoc(field_type = FieldType::UpdatedTime)]
717 UpdatedTime(DateTime<FixedOffset>),
718
719 #[assoc(field_type = FieldType::UserSelect)]
720 UserSelect(Vec<User>),
721
722 #[serde(rename = "__ID__")]
723 #[assoc(field_type = FieldType::__ID__)]
724 __ID__(#[serde(with = "stringified")] u64),
725
726 #[serde(rename = "__REVISION__")]
727 #[assoc(field_type = FieldType::__REVISION__)]
728 __REVISION__(#[serde(with = "stringified")] u64),
729}
730
731/// Represents a single row in a subtable field.
732///
733/// A `TableRow` contains a collection of fields indexed by field code,
734/// similar to a record but used within subtable contexts.
735///
736/// # Examples
737///
738/// ```rust
739/// use kintone::model::record::{TableRow, FieldValue};
740///
741/// let row = TableRow::from([
742/// ("name", FieldValue::SingleLineText("John Doe".to_string())),
743/// ("age", FieldValue::Number(Some(25.into()))),
744/// ]);
745///
746/// if let Some(name_field) = row.get("name") {
747/// println!("Name: {:?}", name_field);
748/// }
749/// ```
750#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
751pub struct TableRow {
752 #[serde(flatten)]
753 fields: BTreeMap<String, FieldValue>,
754}
755
756impl TableRow {
757 /// Creates a new empty table row.
758 pub fn new() -> Self {
759 Self {
760 fields: BTreeMap::new(),
761 }
762 }
763
764 /// Gets a field value by field code.
765 pub fn get(&self, field_code: &str) -> Option<&FieldValue> {
766 self.fields.get(field_code)
767 }
768
769 /// Gets a mutable reference to a field value by field code.
770 pub fn get_mut(&mut self, field_code: &str) -> Option<&mut FieldValue> {
771 self.fields.get_mut(field_code)
772 }
773
774 /// Returns an iterator over all fields in the table row.
775 pub fn fields(&self) -> impl ExactSizeIterator<Item = (&'_ str, &'_ FieldValue)> + Clone {
776 self.fields.iter().map(|(k, v)| (k.borrow(), v))
777 }
778
779 /// Returns a mutable iterator over all fields in the table row.
780 pub fn fields_mut(&mut self) -> impl ExactSizeIterator<Item = (&'_ str, &'_ mut FieldValue)> {
781 self.fields.iter_mut().map(|(k, v)| (k.borrow(), v))
782 }
783
784 /// Returns an iterator over all field codes in the table row.
785 pub fn field_codes(&self) -> impl ExactSizeIterator<Item = &'_ str> + Clone {
786 self.fields.keys().map(|k| k.borrow())
787 }
788
789 /// Returns an iterator over all field values in the table row.
790 pub fn field_values(&self) -> impl ExactSizeIterator<Item = &'_ FieldValue> + Clone {
791 self.fields.values()
792 }
793
794 /// Adds or updates a field in the table row.
795 ///
796 /// Returns the previous value if the field already existed.
797 pub fn put_field(
798 &mut self,
799 field_code: impl Into<String>,
800 value: FieldValue,
801 ) -> Option<FieldValue> {
802 self.fields.insert(field_code.into(), value)
803 }
804
805 /// Removes a field from the table row.
806 pub fn remove_field(&mut self, field_code: &str) -> Option<FieldValue> {
807 self.fields.remove(field_code)
808 }
809}
810
811impl std::fmt::Debug for TableRow {
812 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
813 let mut debug_struct = f.debug_struct("TableRow");
814
815 for (field_code, field_value) in self.fields() {
816 debug_struct.field(field_code, field_value);
817 }
818
819 debug_struct.finish()
820 }
821}
822
823impl Default for TableRow {
824 fn default() -> Self {
825 Self::new()
826 }
827}
828
829impl<const N: usize, S: Into<String>> From<[(S, FieldValue); N]> for TableRow {
830 fn from(fields: [(S, FieldValue); N]) -> Self {
831 Self {
832 fields: BTreeMap::from(fields.map(|(k, v)| (k.into(), v))),
833 }
834 }
835}
836
837impl FromIterator<(String, FieldValue)> for TableRow {
838 fn from_iter<T: IntoIterator<Item = (String, FieldValue)>>(iter: T) -> Self {
839 Self {
840 fields: BTreeMap::from_iter(iter),
841 }
842 }
843}
844
845/// Represents a comment to be posted to a Kintone record.
846///
847/// This struct is used when creating new comments on records.
848/// Use `PostedRecordComment` for comments that have already been posted.
849///
850/// # Examples
851///
852/// ```rust
853/// use kintone::model::{Entity, EntityType};
854/// use kintone::model::record::RecordComment;
855///
856/// let comment = RecordComment {
857/// text: "Please review this record".to_string(),
858/// mentions: vec![
859/// Entity {
860/// entity_type: EntityType::USER,
861/// code: "user1".to_string(),
862/// }
863/// ],
864/// };
865/// ```
866#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
867#[serde(rename_all = "camelCase")]
868pub struct RecordComment {
869 /// The text content of the comment
870 pub text: String,
871 /// List of entities mentioned in the comment
872 pub mentions: Vec<Entity>,
873}
874
875impl From<PostedRecordComment> for RecordComment {
876 fn from(c: PostedRecordComment) -> Self {
877 RecordComment {
878 text: c.text,
879 mentions: c.mentions,
880 }
881 }
882}
883
884/// Represents a comment that has been posted to a Kintone record.
885///
886/// This struct contains all the metadata for an existing comment,
887/// including its ID, creation time, and author information.
888///
889/// # Examples
890///
891/// ```rust
892/// use kintone::model::record::{PostedRecordComment, RecordComment};
893/// use kintone::model::User;
894/// use chrono::{DateTime, FixedOffset};
895///
896/// // Convert a PostedRecordComment to RecordComment for updating
897/// let posted_comment = PostedRecordComment {
898/// id: 123,
899/// text: "Updated comment text".to_string(),
900/// created_at: DateTime::parse_from_rfc3339("2023-12-25T10:00:00+09:00").unwrap(),
901/// user: User {
902/// name: "John Doe".to_string(),
903/// code: "john.doe".to_string(),
904/// },
905/// mentions: vec![],
906/// };
907///
908/// let comment: RecordComment = posted_comment.into();
909/// ```
910#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
911#[serde(rename_all = "camelCase")]
912pub struct PostedRecordComment {
913 /// Unique identifier of the comment
914 pub id: u64,
915 /// The text content of the comment
916 pub text: String,
917 /// When the comment was created
918 pub created_at: DateTime<FixedOffset>,
919 /// User who created the comment
920 pub user: User,
921 /// List of entities mentioned in the comment
922 pub mentions: Vec<Entity>,
923}
924
925#[cfg(test)]
926mod tests {
927 use super::*;
928
929 const RECORD_JSON1: &str = include_str!("../testdata/record1.json");
930
931 fn assert_json_eq(json1: &str, json2: &str) {
932 let value1: serde_json::Value = serde_json::from_str(json1).unwrap();
933 let value2: serde_json::Value = serde_json::from_str(json2).unwrap();
934 assert_eq!(value1, value2);
935 }
936
937 #[test]
938 fn deserialize_and_serialize_record() {
939 let record: Record = serde_json::from_str(RECORD_JSON1).unwrap();
940 let serialized = serde_json::to_string_pretty(&record).unwrap();
941 assert_json_eq(RECORD_JSON1, &serialized);
942 }
943}