quillmark_core/document/
edit.rs1use unicode_normalization::UnicodeNormalization;
13
14use crate::document::meta::{validate_composable_kind, CardKindError};
15use crate::document::{Card, Document, Payload};
16use crate::value::QuillValue;
17use crate::version::QuillReference;
18
19pub const RESERVED_NAMES: &[&str] = &["BODY", "CARDS", "QUILL", "CARD"];
22
23#[inline]
24pub fn is_reserved_name(name: &str) -> bool {
25 RESERVED_NAMES.contains(&name)
26}
27
28pub fn is_valid_field_name(name: &str) -> bool {
30 let normalized: String = name.nfc().collect();
31 if normalized.is_empty() {
32 return false;
33 }
34 let mut chars = normalized.chars();
35 let first = chars.next().unwrap();
36 if !first.is_ascii_lowercase() && first != '_' {
37 return false;
38 }
39 for ch in chars {
40 if !ch.is_ascii_lowercase() && !ch.is_ascii_digit() && ch != '_' {
41 return false;
42 }
43 }
44 true
45}
46
47#[derive(Debug, Clone, PartialEq, thiserror::Error)]
49pub enum EditError {
50 #[error("reserved name '{0}' cannot be used as a field name")]
51 ReservedName(String),
52
53 #[error("invalid field name '{0}': must match [a-z_][a-z0-9_]*")]
54 InvalidFieldName(String),
55
56 #[error("invalid card kind '{0}': must match [a-z_][a-z0-9_]*")]
57 InvalidKindName(String),
58
59 #[error("card kind 'main' is reserved for the document root")]
60 ReservedKind,
61
62 #[error("index {index} is out of range (len = {len})")]
63 IndexOutOfRange { index: usize, len: usize },
64}
65
66impl Document {
67 pub fn set_quill_ref(&mut self, reference: QuillReference) {
68 self.main_mut().payload_mut().set_quill(reference);
69 }
70
71 pub fn card_mut(&mut self, index: usize) -> Option<&mut Card> {
72 self.cards_mut().get_mut(index)
73 }
74
75 pub fn push_card(&mut self, card: Card) {
76 self.cards_vec_mut().push(card);
77 }
78
79 pub fn insert_card(&mut self, index: usize, card: Card) -> Result<(), EditError> {
81 let len = self.cards().len();
82 if index > len {
83 return Err(EditError::IndexOutOfRange { index, len });
84 }
85 self.cards_vec_mut().insert(index, card);
86 Ok(())
87 }
88
89 pub fn remove_card(&mut self, index: usize) -> Option<Card> {
90 if index >= self.cards().len() {
91 return None;
92 }
93 Some(self.cards_vec_mut().remove(index))
94 }
95
96 pub fn set_card_kind(
106 &mut self,
107 index: usize,
108 new_kind: impl Into<String>,
109 ) -> Result<(), EditError> {
110 let new_kind = new_kind.into();
111 validate_composable_kind(&new_kind).map_err(|e| match e {
112 CardKindError::InvalidName => EditError::InvalidKindName(new_kind.clone()),
113 CardKindError::Reserved => EditError::ReservedKind,
114 })?;
115 let len = self.cards().len();
116 let card = self
117 .card_mut(index)
118 .ok_or(EditError::IndexOutOfRange { index, len })?;
119 card.payload_mut().set_kind(new_kind);
120 Ok(())
121 }
122
123 pub fn move_card(&mut self, from: usize, to: usize) -> Result<(), EditError> {
126 let len = self.cards().len();
127 if from >= len {
128 return Err(EditError::IndexOutOfRange { index: from, len });
129 }
130 if to >= len {
131 return Err(EditError::IndexOutOfRange { index: to, len });
132 }
133 if from == to {
134 return Ok(());
135 }
136 let card = self.cards_vec_mut().remove(from);
137 self.cards_vec_mut().insert(to, card);
138 Ok(())
139 }
140}
141
142impl Card {
143 pub fn new(kind: impl Into<String>) -> Result<Self, EditError> {
145 let kind = kind.into();
146 validate_composable_kind(&kind).map_err(|e| match e {
147 CardKindError::InvalidName => EditError::InvalidKindName(kind.clone()),
148 CardKindError::Reserved => EditError::ReservedKind,
149 })?;
150 let mut payload = Payload::new();
151 payload.set_kind(kind);
152 Ok(Card::from_parts(payload, String::new()))
153 }
154
155 pub fn set_field(&mut self, name: &str, value: QuillValue) -> Result<(), EditError> {
159 if is_reserved_name(name) {
160 return Err(EditError::ReservedName(name.to_string()));
161 }
162 if !is_valid_field_name(name) {
163 return Err(EditError::InvalidFieldName(name.to_string()));
164 }
165 self.payload_mut().insert(name.to_string(), value);
166 Ok(())
167 }
168
169 pub fn set_fill(&mut self, name: &str, value: QuillValue) -> Result<(), EditError> {
173 if is_reserved_name(name) {
174 return Err(EditError::ReservedName(name.to_string()));
175 }
176 if !is_valid_field_name(name) {
177 return Err(EditError::InvalidFieldName(name.to_string()));
178 }
179 self.payload_mut().insert_fill(name.to_string(), value);
180 Ok(())
181 }
182
183 pub fn remove_field(&mut self, name: &str) -> Result<Option<QuillValue>, EditError> {
186 if is_reserved_name(name) {
187 return Err(EditError::ReservedName(name.to_string()));
188 }
189 if !is_valid_field_name(name) {
190 return Err(EditError::InvalidFieldName(name.to_string()));
191 }
192 Ok(self.payload_mut().remove(name))
193 }
194
195 pub fn replace_body(&mut self, body: impl Into<String>) {
196 self.overwrite_body(body.into());
197 }
198}