Skip to main content

xfa_json/
types.rs

1//! Core JSON types for the XFA JSON-first API.
2
3use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5
6/// A JSON-friendly representation of an XFA form's data.
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct FormData {
9    /// Field values keyed by SOM-style dotted path.
10    pub fields: IndexMap<String, FieldValue>,
11}
12
13/// A typed form field value with automatic coercion from XFA string values.
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
15#[serde(untagged)]
16pub enum FieldValue {
17    /// Numeric value (coerced from string when parseable as f64).
18    Number(f64),
19    /// Boolean value (coerced from "true"/"false"/"1"/"0").
20    Boolean(bool),
21    /// Text string value.
22    Text(String),
23    /// Null value (from empty or missing fields).
24    Null,
25    /// Repeating section: array of sub-objects.
26    Array(Vec<IndexMap<String, FieldValue>>),
27}
28
29/// Schema metadata for a form's fields.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct FormSchema {
32    /// Field schemas keyed by SOM-style dotted path.
33    pub fields: IndexMap<String, FieldSchema>,
34}
35
36/// The semantic type of a form field.
37#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "lowercase")]
39pub enum FieldType {
40    /// Free-text input.
41    Text,
42    /// Numeric input.
43    Numeric,
44    /// Boolean (checkbox/toggle).
45    Boolean,
46    /// Static content (Draw elements).
47    Static,
48}
49
50/// Schema for a single form field.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct FieldSchema {
53    /// Full SOM-style path to the field.
54    pub som_path: String,
55    /// Semantic field type.
56    pub field_type: FieldType,
57    /// Whether this field is required (occur.min > 0).
58    pub required: bool,
59    /// Whether the parent section can repeat.
60    pub repeatable: bool,
61    /// Maximum occurrences (None = unlimited).
62    pub max_occurrences: Option<u32>,
63    /// FormCalc calculate script, if any.
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub calculate: Option<String>,
66    /// FormCalc validate script, if any.
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub validate: Option<String>,
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn field_value_json_roundtrip() {
77        let val = FieldValue::Number(42.5);
78        let json = serde_json::to_string(&val).unwrap();
79        assert_eq!(json, "42.5");
80
81        let val = FieldValue::Boolean(true);
82        let json = serde_json::to_string(&val).unwrap();
83        assert_eq!(json, "true");
84
85        let val = FieldValue::Text("hello".to_string());
86        let json = serde_json::to_string(&val).unwrap();
87        assert_eq!(json, "\"hello\"");
88
89        let val = FieldValue::Null;
90        let json = serde_json::to_string(&val).unwrap();
91        assert_eq!(json, "null");
92    }
93
94    #[test]
95    fn form_data_json_roundtrip() {
96        let mut fields = IndexMap::new();
97        fields.insert("name".to_string(), FieldValue::Text("Acme".to_string()));
98        fields.insert("amount".to_string(), FieldValue::Number(100.0));
99        let data = FormData { fields };
100
101        let json = serde_json::to_string_pretty(&data).unwrap();
102        let parsed: FormData = serde_json::from_str(&json).unwrap();
103        assert_eq!(parsed.fields.len(), 2);
104    }
105
106    #[test]
107    fn field_schema_omits_none_scripts() {
108        let schema = FieldSchema {
109            som_path: "form1.Name".to_string(),
110            field_type: FieldType::Text,
111            required: true,
112            repeatable: false,
113            max_occurrences: Some(1),
114            calculate: None,
115            validate: None,
116        };
117        let json = serde_json::to_string(&schema).unwrap();
118        assert!(!json.contains("calculate"));
119        assert!(!json.contains("validate"));
120    }
121}