Skip to main content

molten_core/
document.rs

1//! This module defines the `Document` struct, which represents a concrete
2//! instance of a form.
3//!
4//! A `Document` holds the actual data values for fields defined in a
5//! `FormDefinition` and tracks its current phase within an associated
6//! `WorkflowDefinition`. It serves as the primary data entity managed
7//! by the Molten system.
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11use std::collections::HashMap;
12use validator::Validate;
13
14/// Represents a single instance of a Form.
15///
16/// While `FormDefinition` defines the structure, `Document` holds the actual data.
17/// The `data` field is a dynamic map where keys correspond to `FieldDefinition.id`.
18#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
19pub struct Document {
20    /// Unique identifier for this specific document (usually a UUID).
21    #[validate(length(min = 1, max = 64))]
22    pub id: String,
23
24    /// Links this document to a specific Form Definition.
25    #[validate(length(min = 1, max = 64))]
26    pub form_id: String,
27
28    /// Links this document to a specific Workflow Definition
29    #[validate(length(min = 1, max = 64))]
30    pub workflow_id: String,
31
32    /// Links this document to a specific Phase
33    pub current_phase: String,
34
35    /// The generic data payload.
36    /// Key: Field ID (e.g., "incident_date")
37    /// Value: The user input (String, Number, Boolean, etc.)
38    ///
39    /// Note: We do NOT validate the *content* of these values here.
40    /// That requires the `FormDefinition` and happens in the `molten-document` crate.
41    pub data: HashMap<String, Value>,
42
43    /// Metadata: When this document was created.
44    pub created_at: DateTime<Utc>,
45
46    /// Metadata: When this document was last modified.
47    pub updated_at: DateTime<Utc>,
48}
49
50impl Document {
51    /// Creates a new, empty document for a specific form.
52    pub fn new(id: &str, form_id: &str, workflow_id: &str) -> Self {
53        let now = Utc::now();
54        Self {
55            id: id.to_string(),
56            form_id: form_id.to_string(),
57            workflow_id: workflow_id.to_string(),
58            current_phase: "".to_string(),
59            data: HashMap::new(),
60            created_at: now,
61            updated_at: now,
62        }
63    }
64
65    /// Helper to get a value for a specific field ID.
66    pub fn get_value(&self, field_id: &str) -> Option<&Value> {
67        self.data.get(field_id)
68    }
69
70    /// Helper to set a value.
71    pub fn set_value(&mut self, field_id: &str, value: Value) {
72        self.data.insert(field_id.to_string(), value);
73        self.updated_at = Utc::now();
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use serde_json::json;
81
82    #[test]
83    fn test_document_creation() {
84        let mut doc = Document::new("doc_123", "incident_report", "flow_123");
85
86        // Simulate user input
87        doc.set_value("title", json!("Server Crash"));
88        doc.set_value("severity", json!(5));
89
90        assert_eq!(doc.id, "doc_123");
91        assert_eq!(doc.data.get("title"), Some(&json!("Server Crash")));
92    }
93
94    #[test]
95    fn test_serialization() {
96        let mut doc = Document::new("doc_1", "form_1", "flow_1");
97        doc.set_value("active", json!(true));
98
99        let json_output = serde_json::to_string(&doc).unwrap();
100        assert!(json_output.contains("doc_1"));
101        assert!(json_output.contains("active"));
102        assert!(json_output.contains("true"));
103    }
104}