Skip to main content

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}