Skip to main content

polyspec_crudui_generator/
instance.rs

1use crate::binding::bind_ordered;
2use crate::history::{
3    can_redo, can_undo, empty_history, record_change, redo_change, undo_change, History,
4    HistoryResult,
5};
6use crate::template::member_ordered_template;
7use crate::template::{data_only, repeats};
8use crate::util::{segments, value_at};
9use crate::view::{
10    initial_view, rekey_row_view, remove_row_view, set_all_expanded_view, toggle_row_view,
11    ViewState,
12};
13use crate::{BindOptions, FieldTemplate, FormError, FormResult, FormTemplate};
14use serde::{Deserialize, Serialize};
15use serde_json::{json, Map, Value};
16
17/// Format a nonnegative sequence containing at most 13 decimal digits.
18pub fn sequence_row_key(sequence: &str) -> FormResult<String> {
19    if sequence.is_empty() || sequence.len() > 13 || !sequence.bytes().all(|c| c.is_ascii_digit()) {
20        return Err(FormError::input(
21            "A sequence must contain 1–13 decimal digits",
22        ));
23    }
24    Ok(format!("__{sequence:0>13}__"))
25}
26
27/// Generate a random 13-character hexadecimal row key using the OS random source.
28pub fn create_row_key() -> FormResult<String> {
29    let mut bytes = [0u8; 7];
30    getrandom::fill(&mut bytes)
31        .map_err(|e| FormError::input(format!("Unable to generate a row key: {e}")))?;
32    let digits = bytes.iter().map(|b| format!("{b:02x}")).collect::<String>();
33    Ok(format!("__{}__", &digits[..13]))
34}
35
36fn check_key(key: &str) -> FormResult<()> {
37    if key.is_empty()
38        || !key
39            .bytes()
40            .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
41        || key.bytes().all(|b| b.is_ascii_digit())
42        || ["__proto__", "prototype", "constructor"].contains(&key)
43    {
44        return Err(FormError::input(format!(
45            "Invalid row key: {key}; use sequenceRowKey for numeric ids"
46        )));
47    }
48    Ok(())
49}
50
51fn checked_segments(path: &str) -> FormResult<Vec<String>> {
52    let parts = segments(path);
53    if parts.is_empty()
54        || parts
55            .iter()
56            .any(|s| ["__proto__", "prototype", "constructor"].contains(&s.as_str()))
57    {
58        return Err(FormError::input(format!("Invalid form path: {path}")));
59    }
60    Ok(parts)
61}
62
63/// The row path `{collection}.{key}` of a row.
64fn row_path(collection: &[String], key: &str) -> String {
65    collection
66        .iter()
67        .map(String::as_str)
68        .chain([key])
69        .collect::<Vec<_>>()
70        .join(".")
71}
72
73fn put_at(data: &Value, path: &[String], value: Value) -> Value {
74    let mut object = data.as_object().cloned().unwrap_or_default();
75    let head = &path[0];
76    let next = if path.len() == 1 {
77        value
78    } else {
79        put_at(object.get(head).unwrap_or(&Value::Null), &path[1..], value)
80    };
81    object.insert(head.clone(), next);
82    object.into()
83}
84
85fn fresh_key(used: &Map<String, Value>) -> FormResult<String> {
86    for _ in 0..100 {
87        let key = create_row_key()?;
88        if !used.contains_key(&key) {
89            return Ok(key);
90        }
91    }
92    Err(FormError::input("Unable to generate an unused row key"))
93}
94
95/// The key of the one initial row that missing repeated data creates (CUEC-002).
96const INITIAL_ROW_KEY: &str = "__0000000000000__";
97
98fn normalize_row(
99    field: &FieldTemplate,
100    value: Option<&Value>,
101    path: &str,
102    fresh: bool,
103) -> FormResult<Value> {
104    if field.spec.get("type").is_some_and(|t| t == "group") {
105        normalize_fields(&field.children, value, path, fresh)
106    } else {
107        Ok(value
108            .or_else(|| field.spec.get("default"))
109            .cloned()
110            .unwrap_or(json!("")))
111    }
112}
113
114/// Normalize record data; `path` is the full data path, empty at the root.
115/// `fresh` is set for a row that an operation creates: its missing repeated data gets a fresh key, where the record
116/// normalization gives the initial row `__0000000000000__`, as the TypeScript and Python references do.
117fn normalize_fields(
118    fields: &[FieldTemplate],
119    value: Option<&Value>,
120    path: &str,
121    fresh: bool,
122) -> FormResult<Value> {
123    let mut data = match value {
124        None => Map::new(),
125        Some(Value::Object(map)) => map.clone(),
126        _ if path.is_empty() => return Err(FormError::input("Form data must be an object")),
127        _ => {
128            return Err(FormError::input(format!(
129                "Group data must be an object: {path}"
130            )))
131        }
132    };
133    for field in fields {
134        let raw = data.get(&field.name);
135        let field_path = if path.is_empty() {
136            field.name.clone()
137        } else {
138            format!("{path}.{}", field.name)
139        };
140        if repeats(field) {
141            let mut rows = Map::new();
142            match raw {
143                // A data-only collection has no row without data.
144                None if data_only(field) => {}
145                None => {
146                    let key = if fresh {
147                        fresh_key(&rows)?
148                    } else {
149                        INITIAL_ROW_KEY.to_owned()
150                    };
151                    let row = normalize_row(field, None, &format!("{field_path}.{key}"), fresh)?;
152                    rows.insert(key, row);
153                }
154                Some(Value::Object(map)) => {
155                    for (key, value) in map {
156                        check_key(key)?;
157                        let row = normalize_row(
158                            field,
159                            Some(value),
160                            &format!("{field_path}.{key}"),
161                            fresh,
162                        )?;
163                        rows.insert(key.clone(), row);
164                    }
165                }
166                _ => {
167                    return Err(FormError::input(format!(
168                        "Repeated data must be a keyed object: {field_path}"
169                    )))
170                }
171            }
172            data.insert(field.name.clone(), rows.into());
173        } else if field.spec.get("type").is_some_and(|t| t == "group") {
174            let group = normalize_fields(&field.children, raw, &field_path, fresh)?;
175            data.insert(field.name.clone(), group);
176        } else if raw.is_none() {
177            if let Some(default) = field.spec.get("default") {
178                data.insert(field.name.clone(), default.clone());
179            }
180        }
181    }
182    Ok(data.into())
183}
184
185fn copy_row_value(field: &FieldTemplate, value: &Value) -> FormResult<Value> {
186    if field.spec.get("type").is_none_or(|t| t != "group") {
187        return Ok(value.clone());
188    }
189    let mut row = value.as_object().cloned().unwrap_or_default();
190    for child in &field.children {
191        let Some(raw) = row.get(&child.name).filter(|v| v.is_object()) else {
192            continue;
193        };
194        if repeats(child) {
195            let mut used = raw.as_object().unwrap().clone();
196            let mut copied = Map::new();
197            for value in raw.as_object().unwrap().values() {
198                let key = fresh_key(&used)?;
199                used.insert(key.clone(), Value::Null);
200                copied.insert(key, copy_row_value(child, value)?);
201            }
202            row.insert(child.name.clone(), copied.into());
203        } else if child.spec.get("type").is_some_and(|t| t == "group") {
204            let copied = copy_row_value(child, raw)?;
205            row.insert(child.name.clone(), copied);
206        }
207    }
208    Ok(row.into())
209}
210
211/// Values, identity and insertion position for an added or copied row.
212#[derive(Debug, Default, Clone, Serialize, Deserialize)]
213#[serde(rename_all = "camelCase")]
214pub struct AddRowOptions {
215    /// Supplied row key; omitted to generate a new key.
216    pub key: Option<String>,
217    /// Existing row after which insertion occurs; omitted to append.
218    pub after_key: Option<String>,
219    /// Initial value; omitted to apply field defaults.
220    #[serde(
221        default,
222        deserialize_with = "present_value",
223        skip_serializing_if = "Option::is_none"
224    )]
225    pub value: Option<Value>,
226}
227
228fn present_value<'de, D: serde::Deserializer<'de>>(
229    deserializer: D,
230) -> Result<Option<Value>, D::Error> {
231    Value::deserialize(deserializer).map(Some)
232}
233
234/// An independent editable form instance using a compiled structure.
235#[derive(Clone)]
236pub struct Form {
237    template: FormTemplate,
238    data: Value,
239    fields: Vec<Value>,
240    options: BindOptions,
241    revision: u64,
242    view: ViewState,
243    history: History<Value>,
244}
245
246impl Form {
247    /// Create an instance, prepare default data and evaluate fields.
248    pub fn new(template: FormTemplate, data: &Value, options: BindOptions) -> FormResult<Self> {
249        // The template specification is kept in member order; the data keeps its own order.
250        let template = member_ordered_template(&template);
251        let data = normalize_fields(&template.fields, Some(data), "", false)?;
252        let fields = bind_ordered(&template, &data, &options)?;
253        Ok(Self {
254            template,
255            data,
256            fields,
257            options,
258            revision: 0,
259            view: initial_view(),
260            history: empty_history(),
261        })
262    }
263
264    /// Content and interface language of the instance, defaulting to Korean.
265    pub(crate) fn language(&self) -> &str {
266        self.options.language.as_str().unwrap_or("ko")
267    }
268
269    /// Return the shared structure without instance values.
270    pub fn template(&self) -> &FormTemplate {
271        &self.template
272    }
273
274    /// Return the current field models.
275    pub fn fields(&self) -> &[Value] {
276        &self.fields
277    }
278
279    /// Number of successful instance updates.
280    pub fn revision(&self) -> u64 {
281        self.revision
282    }
283
284    /// Return detached submission data.
285    pub fn get_data(&self) -> Value {
286        self.data.clone()
287    }
288
289    /// Replace record data, restart the history and reevaluate the form atomically.
290    pub fn set_data(&mut self, data: &Value) -> FormResult<()> {
291        self.commit_view(
292            normalize_fields(&self.template.fields, Some(data), "", false)?,
293            initial_view(),
294            empty_history(),
295        )
296    }
297
298    /// Return a detached value relative to the data root.
299    pub fn get_value(&self, path: &str) -> FormResult<Value> {
300        checked_segments(path)?;
301        Ok(value_at(&self.data, path).cloned().unwrap_or(Value::Null))
302    }
303
304    /// Update one data path and reevaluate the form atomically.
305    pub fn set_value(&mut self, path: &str, value: Value) -> FormResult<()> {
306        let segments = checked_segments(path)?;
307        let data = put_at(&self.data, &segments, value);
308        self.commit(
309            normalize_fields(&self.template.fields, Some(&data), "", false)?,
310            Some(&segments.join(".")),
311        )
312    }
313
314    /// Restore the record before the last data change; the reverted record can be redone (CURT-011).
315    pub fn undo(&mut self) -> FormResult<()> {
316        let result = undo_change(&self.history, self.data.clone())?;
317        self.restore(result)
318    }
319
320    /// Reapply the most recently undone data change (CURT-012).
321    pub fn redo(&mut self) -> FormResult<()> {
322        let result = redo_change(&self.history, self.data.clone())?;
323        self.restore(result)
324    }
325
326    /// Whether `undo` can restore an earlier record (CURT-061).
327    pub fn can_undo(&self) -> bool {
328        can_undo(&self.history)
329    }
330
331    /// Whether `redo` can reapply a reverted change (CURT-061).
332    pub fn can_redo(&self) -> bool {
333        can_redo(&self.history)
334    }
335
336    /// Replace the record and the history of an undo or a redo and keep the view; the revision advances.
337    fn restore(&mut self, result: HistoryResult<Value>) -> FormResult<()> {
338        let fields = bind_ordered(
339            &self.template,
340            &result.value,
341            &self.bind_options(&self.view),
342        )?;
343        self.history = result.history;
344        self.data = result.value;
345        self.fields = fields;
346        self.revision += 1;
347        Ok(())
348    }
349
350    /// The options of the form with the collapsed rows of a view.
351    fn bind_options(&self, view: &ViewState) -> BindOptions {
352        let mut options = self.options.clone();
353        options.collapsed = view
354            .collapsed
355            .iter()
356            .cloned()
357            .map(Value::String)
358            .collect::<Vec<_>>()
359            .into();
360        options
361    }
362
363    /// Replace the record and keep the view; `path` is the path of a value edit, if the change is one.
364    /// A failure leaves the record, the view and the history unchanged.
365    fn commit(&mut self, data: Value, path: Option<&str>) -> FormResult<()> {
366        let view = self.view.clone();
367        let history = self.recorded(path);
368        self.commit_view(data, view, history)
369    }
370
371    /// The history after a change of the record: the record before the change is kept (CURT-063).
372    fn recorded(&self, path: Option<&str>) -> History<Value> {
373        record_change(&self.history, self.data.clone(), path)
374    }
375
376    /// Replace the record, the view and the history together.
377    fn commit_view(
378        &mut self,
379        data: Value,
380        view: ViewState,
381        history: History<Value>,
382    ) -> FormResult<()> {
383        let fields = bind_ordered(&self.template, &data, &self.bind_options(&view))?;
384        self.data = data;
385        self.fields = fields;
386        self.view = view;
387        self.history = history;
388        self.revision += 1;
389        Ok(())
390    }
391
392    /// Expand a collapsed row or collapse an expanded one; the record and the revision do not change.
393    pub fn toggle_row(&mut self, path: &str, key: &str) -> FormResult<()> {
394        let (_, rows) = self.collection(path)?;
395        if !rows.contains_key(key) {
396            return Err(FormError::input(format!("Unknown row: {key}")));
397        }
398        let segments = checked_segments(path)?;
399        self.refresh_view(toggle_row_view(&self.view, &row_path(&segments, key)))
400    }
401
402    /// Expand every row, or collapse every collapsible row; the record and the revision do not change.
403    pub fn set_all_expanded(&mut self, expanded: bool) -> FormResult<()> {
404        self.refresh_view(set_all_expanded_view(&self.fields, expanded))
405    }
406
407    fn refresh_view(&mut self, view: ViewState) -> FormResult<()> {
408        self.fields = bind_ordered(&self.template, &self.data, &self.bind_options(&view))?;
409        self.view = view;
410        Ok(())
411    }
412
413    /// A collection whose rows the form may add, copy, remove, move or rekey.
414    fn editable_collection(&self, path: &str) -> FormResult<(&FieldTemplate, &Map<String, Value>)> {
415        let found = self.collection(path)?;
416        if data_only(found.0) {
417            return Err(FormError::input(format!(
418                "Rows of {path} come only from data"
419            )));
420        }
421        Ok(found)
422    }
423
424    fn collection(&self, path: &str) -> FormResult<(&FieldTemplate, &Map<String, Value>)> {
425        let parts = checked_segments(path)?;
426        let mut fields = &self.template.fields;
427        let mut selected = None;
428        let mut i = 0;
429        while i < parts.len() {
430            let field = fields
431                .iter()
432                .find(|f| f.name == parts[i])
433                .ok_or_else(|| FormError::input(format!("Unknown collection: {path}")))?;
434            if i + 1 == parts.len() {
435                selected = Some(field);
436                break;
437            }
438            if repeats(field) {
439                i += 1;
440            }
441            fields = &field.children;
442            i += 1;
443        }
444        let field = selected
445            .filter(|f| repeats(f))
446            .ok_or_else(|| FormError::input(format!("Not a keyed collection: {path}")))?;
447        let rows = value_at(&self.data, path)
448            .and_then(Value::as_object)
449            .ok_or_else(|| FormError::input(format!("Not a keyed collection: {path}")))?;
450        Ok((field, rows))
451    }
452
453    /// Insert a row with supplied data or field defaults.
454    pub fn add_row(&mut self, path: &str, options: AddRowOptions) -> FormResult<String> {
455        let (field, rows) = self.editable_collection(path)?;
456        if field
457            .spec
458            .get("multiple")
459            .and_then(|v| v.get("max"))
460            .and_then(Value::as_f64)
461            .is_some_and(|max| rows.len() as f64 >= max)
462        {
463            return Err(FormError::input(format!(
464                "Maximum row count reached: {path}"
465            )));
466        }
467        let key = match options.key {
468            Some(key) => key,
469            None => fresh_key(rows)?,
470        };
471        check_key(&key)?;
472        if rows.contains_key(&key) {
473            return Err(FormError::input(format!("Row key already exists: {key}")));
474        }
475        let mut entries = rows
476            .iter()
477            .map(|(k, v)| (k.clone(), v.clone()))
478            .collect::<Vec<_>>();
479        let at = match &options.after_key {
480            None => entries.len(),
481            Some(after) => {
482                entries
483                    .iter()
484                    .position(|(k, _)| k == after)
485                    .ok_or_else(|| FormError::input(format!("Unknown row: {after}")))?
486                    + 1
487            }
488        };
489        let row_path = format!("{}.{key}", checked_segments(path)?.join("."));
490        entries.insert(
491            at,
492            (
493                key.clone(),
494                normalize_row(field, options.value.as_ref(), &row_path, true)?,
495            ),
496        );
497        self.commit(
498            put_at(
499                &self.data,
500                &checked_segments(path)?,
501                Value::Object(entries.into_iter().collect()),
502            ),
503            None,
504        )?;
505        Ok(key)
506    }
507
508    /// Copy current values and generate new identities for nested repeated rows.
509    pub fn copy_row(
510        &mut self,
511        path: &str,
512        key: &str,
513        mut options: AddRowOptions,
514    ) -> FormResult<String> {
515        let (field, rows) = self.editable_collection(path)?;
516        let value = rows
517            .get(key)
518            .ok_or_else(|| FormError::input(format!("Unknown row: {key}")))?;
519        options.value = Some(copy_row_value(field, value)?);
520        if options.after_key.is_none() {
521            options.after_key = Some(key.into());
522        }
523        self.add_row(path, options)
524    }
525
526    /// Remove a row while enforcing the minimum count.
527    pub fn remove_row(&mut self, path: &str, key: &str) -> FormResult<()> {
528        let (field, rows) = self.editable_collection(path)?;
529        if !rows.contains_key(key) {
530            return Err(FormError::input(format!("Unknown row: {key}")));
531        }
532        if field
533            .spec
534            .get("multiple")
535            .and_then(|v| v.get("min"))
536            .and_then(Value::as_f64)
537            .is_some_and(|min| rows.len() as f64 <= min)
538        {
539            return Err(FormError::input(format!(
540                "Minimum row count reached: {path}"
541            )));
542        }
543        let mut rows = rows.clone();
544        rows.shift_remove(key);
545        let segments = checked_segments(path)?;
546        let view = remove_row_view(&self.view, &row_path(&segments, key));
547        let history = self.recorded(None);
548        self.commit_view(put_at(&self.data, &segments, rows.into()), view, history)
549    }
550
551    /// Move a row without changing its identity or values; `index` is the position after the move, and a position
552    /// outside the rows fails.
553    pub fn move_row(&mut self, path: &str, key: &str, index: i64) -> FormResult<()> {
554        let (_, rows) = self.editable_collection(path)?;
555        let mut entries = rows
556            .iter()
557            .map(|(k, v)| (k.clone(), v.clone()))
558            .collect::<Vec<_>>();
559        let from = entries
560            .iter()
561            .position(|(k, _)| k == key)
562            .ok_or_else(|| FormError::input(format!("Unknown row: {key}")))?;
563        if index < 0 || index >= entries.len() as i64 {
564            return Err(FormError::input(format!("Invalid row position: {index}")));
565        }
566        let to = index as usize;
567        if from == to {
568            return Ok(());
569        }
570        let row = entries.remove(from);
571        entries.insert(to, row);
572        self.commit(
573            put_at(
574                &self.data,
575                &checked_segments(path)?,
576                Value::Object(entries.into_iter().collect()),
577            ),
578            None,
579        )
580    }
581
582    /// Apply a saved sequence key to one row and update its descendant paths.
583    pub fn rekey_row(&mut self, path: &str, old_key: &str, new_key: &str) -> FormResult<()> {
584        let (_, rows) = self.editable_collection(path)?;
585        check_key(new_key)?;
586        if !rows.contains_key(old_key) {
587            return Err(FormError::input(format!("Unknown row: {old_key}")));
588        }
589        if old_key == new_key {
590            return Ok(());
591        }
592        if rows.contains_key(new_key) {
593            return Err(FormError::input(format!(
594                "Row key already exists: {new_key}"
595            )));
596        }
597        let rows = rows
598            .iter()
599            .map(|(k, v)| {
600                (
601                    if k == old_key {
602                        new_key.to_owned()
603                    } else {
604                        k.clone()
605                    },
606                    v.clone(),
607                )
608            })
609            .collect::<Map<_, _>>();
610        let segments = checked_segments(path)?;
611        let view = rekey_row_view(
612            &self.view,
613            &row_path(&segments, old_key),
614            &row_path(&segments, new_key),
615        );
616        let history = self.recorded(None);
617        self.commit_view(put_at(&self.data, &segments, rows.into()), view, history)
618    }
619}