1use indexmap::IndexMap;
33
34use crate::document::edit::resolve_field_write;
35use crate::document::{Card, Document, EditError};
36use crate::quill::{CardSchema, FieldSchema, QuillConfig};
37use crate::value::QuillValue;
38use crate::Delta;
39
40pub struct TypedWriter<'a> {
44 config: &'a QuillConfig,
45 doc: &'a mut Document,
46}
47
48impl<'a> TypedWriter<'a> {
49 pub fn new(config: &'a QuillConfig, doc: &'a mut Document) -> Self {
51 Self { config, doc }
52 }
53
54 pub fn set(&mut self, name: &str, value: impl Into<QuillValue>) -> Result<(), EditError> {
61 let config = self.config;
62 match config.main.fields.get(name) {
63 Some(schema) => self.doc.main_mut().commit_field(name, value, schema),
64 None => Err(EditError::UnknownField(name.to_string())),
65 }
66 }
67
68 pub fn set_all<K, V, I>(&mut self, fields: I) -> Result<(), Vec<(String, EditError)>>
76 where
77 K: Into<String>,
78 V: Into<QuillValue>,
79 I: IntoIterator<Item = (K, V)>,
80 {
81 let schema = Some(&self.config.main.fields);
82 set_all_impl(self.doc.main_mut(), schema, fields)
83 }
84
85 pub fn set_body(&mut self, markdown: &str) -> Result<(), EditError> {
90 self.doc.main_mut().revise_body(markdown).map(|_| ())
91 }
92
93 pub fn revise_field(&mut self, name: &str, text: &str) -> Result<Delta, EditError> {
106 match self.config.main.fields.get(name) {
107 Some(schema) => self.doc.main_mut().revise_field_checked(name, text, schema),
108 None => Err(EditError::UnknownField(name.to_string())),
109 }
110 }
111
112 pub fn add_card<K, V, I>(
128 &mut self,
129 kind: &str,
130 fields: I,
131 body: Option<&str>,
132 at: Option<usize>,
133 ) -> Result<(), Vec<(String, EditError)>>
134 where
135 K: Into<String>,
136 V: Into<QuillValue>,
137 I: IntoIterator<Item = (K, V)>,
138 {
139 let mut card = Card::new(kind).map_err(|e| vec![("$kind".to_string(), e)])?;
140 let schema = self.config.card_kind(kind).map(|s| &s.fields);
141 set_all_impl(&mut card, schema, fields)?;
142 if let Some(md) = body {
143 card.revise_body(md)
144 .map_err(|e| vec![("$body".to_string(), e)])?;
145 }
146 match at {
147 Some(index) => self.doc.insert_card(index, card),
148 None => self.doc.push_card(card),
149 }
150 .map_err(|e| vec![("$kind".to_string(), e)])?;
151 Ok(())
152 }
153
154 pub fn remove_card(&mut self, index: usize) -> Option<Card> {
158 self.doc.remove_card(index)
159 }
160
161 pub fn card(&mut self, index: usize) -> Result<CardWriter<'_>, EditError> {
168 let config = self.config;
169 let len = self.doc.cards().len();
170 let card = self
171 .doc
172 .card_mut(index)
173 .ok_or(EditError::IndexOutOfRange { index, len })?;
174 let schema = card.kind().and_then(|k| config.card_kind(k));
175 Ok(CardWriter { schema, card })
176 }
177}
178
179pub struct CardWriter<'a> {
182 schema: Option<&'a CardSchema>,
183 card: &'a mut Card,
184}
185
186impl CardWriter<'_> {
187 pub fn kind(&self) -> Option<&str> {
189 self.card.kind()
190 }
191
192 pub fn set(&mut self, name: &str, value: impl Into<QuillValue>) -> Result<(), EditError> {
197 match self.schema.and_then(|s| s.fields.get(name)) {
198 Some(schema) => self.card.commit_field(name, value, schema),
199 None => Err(EditError::UnknownField(name.to_string())),
200 }
201 }
202
203 pub fn set_body(&mut self, markdown: &str) -> Result<(), EditError> {
206 self.card.revise_body(markdown).map(|_| ())
207 }
208
209 pub fn revise_field(&mut self, name: &str, text: &str) -> Result<Delta, EditError> {
216 match self.schema.and_then(|s| s.fields.get(name)) {
217 Some(schema) => self.card.revise_field_checked(name, text, schema),
218 None => Err(EditError::UnknownField(name.to_string())),
219 }
220 }
221
222 pub fn set_all<K, V, I>(&mut self, fields: I) -> Result<(), Vec<(String, EditError)>>
226 where
227 K: Into<String>,
228 V: Into<QuillValue>,
229 I: IntoIterator<Item = (K, V)>,
230 {
231 set_all_impl(self.card, self.schema.map(|s| &s.fields), fields)
232 }
233}
234
235fn set_all_impl<K, V, I>(
242 card: &mut Card,
243 fields_schema: Option<&IndexMap<String, FieldSchema>>,
244 fields: I,
245) -> Result<(), Vec<(String, EditError)>>
246where
247 K: Into<String>,
248 V: Into<QuillValue>,
249 I: IntoIterator<Item = (K, V)>,
250{
251 let fields: Vec<(String, QuillValue)> = fields
252 .into_iter()
253 .map(|(k, v)| (k.into(), v.into()))
254 .collect();
255
256 let mut resolved: Vec<(String, QuillValue)> = Vec::with_capacity(fields.len());
257 let mut errors: Vec<(String, EditError)> = Vec::new();
258 for (name, value) in fields {
259 match fields_schema.and_then(|m| m.get(&name)) {
260 Some(schema) => match resolve_field_write(&name, value, schema) {
261 Ok(stored) => resolved.push((name, stored)),
262 Err(e) => errors.push((name, e)),
263 },
264 None => errors.push((name.clone(), EditError::UnknownField(name))),
265 }
266 }
267 if !errors.is_empty() {
268 return Err(errors);
269 }
270 for (name, stored) in resolved {
272 card.payload_mut().insert_unchecked(name, stored);
273 }
274 Ok(())
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use crate::document::{Card, Document};
281 use crate::version::QuillReference;
282 use std::str::FromStr;
283
284 const QUILL_YAML: &str = "\
285quill:
286 name: memo
287 backend: typst
288 version: 1.0.0
289 description: Editor test quill
290main:
291 fields:
292 subject:
293 type: richtext
294 inline: true
295 qty:
296 type: integer
297card_kinds:
298 note:
299 fields:
300 body:
301 type: richtext
302";
303
304 fn config() -> QuillConfig {
305 QuillConfig::from_yaml(QUILL_YAML).expect("valid quill")
306 }
307
308 fn blank_doc() -> Document {
309 Document::new(QuillReference::from_str("memo@1.0.0").unwrap())
310 }
311
312 #[test]
313 fn set_resolves_schema_field_as_typed_commit() {
314 let config = config();
315 let mut doc = blank_doc();
316 let mut ed = TypedWriter::new(&config, &mut doc);
317
318 ed.set("qty", "3").unwrap();
320 ed.set("subject", "Hello").unwrap();
321 assert_eq!(
322 doc.main().payload().get("qty").unwrap().as_json(),
323 &serde_json::json!(3)
324 );
325 assert_eq!(doc.main().field_markdown("subject").unwrap().unwrap(), "Hello");
326 }
327
328 #[test]
329 fn set_rejects_unknown_field() {
330 let config = config();
331 let mut doc = blank_doc();
332 let mut ed = TypedWriter::new(&config, &mut doc);
333 let err = ed.set("notafield", "x").unwrap_err();
336 assert_eq!(err.variant_name(), "UnknownField");
337 assert!(doc.main().payload().get("notafield").is_none());
338 }
339
340 #[test]
341 fn set_all_is_all_or_nothing() {
342 let config = config();
343 let mut doc = blank_doc();
344 let mut ed = TypedWriter::new(&config, &mut doc);
345 let errs = ed
347 .set_all([("qty", "5"), ("subject", "bad\n\nblock")])
348 .unwrap_err();
349 assert_eq!(errs.len(), 1);
350 assert_eq!(errs[0].0, "subject");
351 assert!(doc.main().payload().get("qty").is_none());
352
353 let mut ed = TypedWriter::new(&config, &mut doc);
355 ed.set_all([("qty", "5"), ("subject", "ok")]).unwrap();
356 assert_eq!(
357 doc.main().payload().get("qty").unwrap().as_json(),
358 &serde_json::json!(5)
359 );
360 }
361
362 #[test]
363 fn set_all_rejects_unknown_field() {
364 let config = config();
365 let mut doc = blank_doc();
366 let mut ed = TypedWriter::new(&config, &mut doc);
367 let errs = ed.set_all([("qty", "3"), ("titel", "oops")]).unwrap_err();
371 assert_eq!(errs.len(), 1);
372 assert_eq!(errs[0].0, "titel");
373 assert_eq!(errs[0].1.variant_name(), "UnknownField");
374 assert!(doc.main().payload().get("qty").is_none());
375 }
376
377 #[test]
378 fn set_body_revises_main_body() {
379 let config = config();
380 let mut doc = blank_doc();
381 let mut ed = TypedWriter::new(&config, &mut doc);
382 ed.set_body("**hi**").unwrap();
383 assert_eq!(doc.main().body_markdown(), "**hi**");
384 }
385
386 #[test]
387 fn add_card_fuses_new_commit_push() {
388 let config = config();
389 let mut doc = blank_doc();
390 let mut ed = TypedWriter::new(&config, &mut doc);
391 ed.add_card("note", [("body", "**hi**")], Some("card body"), None)
392 .unwrap();
393 assert_eq!(doc.cards().len(), 1);
394 assert_eq!(doc.cards()[0].kind(), Some("note"));
395 assert_eq!(doc.cards()[0].field_markdown("body").unwrap().unwrap(), "**hi**");
396 assert_eq!(doc.cards()[0].body_markdown(), "card body");
397 }
398
399 #[test]
400 fn add_card_at_inserts_and_remove_card_returns() {
401 let config = config();
402 let mut doc = blank_doc();
403 {
404 let mut ed = TypedWriter::new(&config, &mut doc);
405 ed.add_card("note", [("body", "a")], None, None).unwrap();
406 ed.add_card("note", [("body", "c")], None, None).unwrap();
407 ed.add_card("note", [("body", "b")], None, Some(1)).unwrap();
409 }
410 let bodies: Vec<String> = doc
411 .cards()
412 .iter()
413 .map(|c| c.field_markdown("body").unwrap().unwrap())
414 .collect();
415 assert_eq!(bodies, ["a", "b", "c"]);
416
417 {
419 let mut ed = TypedWriter::new(&config, &mut doc);
420 let errs = ed
421 .add_card("note", [("body", "x")], None, Some(9))
422 .unwrap_err();
423 assert_eq!(errs[0].0, "$kind");
424 }
425 assert_eq!(doc.cards().len(), 3);
426
427 {
429 let mut ed = TypedWriter::new(&config, &mut doc);
430 let removed = ed.remove_card(1).unwrap();
431 assert_eq!(removed.field_markdown("body").unwrap().unwrap(), "b");
432 assert!(ed.remove_card(5).is_none());
433 }
434 assert_eq!(doc.cards().len(), 2);
435 }
436
437 #[test]
438 fn add_card_is_transactional_on_bad_field() {
439 let config = config();
440 let mut doc = blank_doc();
441 let mut ed = TypedWriter::new(&config, &mut doc);
442 let errs = ed
444 .add_card("note", [("stray", "x")], None, None)
445 .unwrap_err();
446 assert_eq!(errs[0].0, "stray");
447 assert_eq!(errs[0].1.variant_name(), "UnknownField");
448 assert_eq!(doc.cards().len(), 0);
449 }
450
451 #[test]
452 fn add_card_reports_invalid_kind() {
453 let config = config();
454 let mut doc = blank_doc();
455 let mut ed = TypedWriter::new(&config, &mut doc);
456 let errs = ed
458 .add_card("$reserved", [] as [(&str, &str); 0], None, None)
459 .unwrap_err();
460 assert_eq!(errs[0].0, "$kind");
461 assert_eq!(doc.cards().len(), 0);
462 }
463
464 #[test]
465 fn card_writer_resolves_card_kind_schema() {
466 let config = config();
467 let mut doc = blank_doc();
468 doc.push_card(Card::new("note").unwrap()).unwrap();
469
470 let mut ed = TypedWriter::new(&config, &mut doc);
471 let mut card_ed = ed.card(0).unwrap();
472 card_ed.set("body", "**hi**").unwrap();
473 let err = card_ed.set("stray", "v").unwrap_err();
475 assert_eq!(err.variant_name(), "UnknownField");
476
477 assert_eq!(doc.cards()[0].field_markdown("body").unwrap().unwrap(), "**hi**");
478
479 let mut ed = TypedWriter::new(&config, &mut doc);
481 assert!(matches!(
482 ed.card(9),
483 Err(EditError::IndexOutOfRange { .. })
484 ));
485 }
486
487 #[test]
488 fn revise_field_is_typed_and_rejects_unknown_and_non_inline() {
489 let config = config();
490 let mut doc = blank_doc();
491 let mut ed = TypedWriter::new(&config, &mut doc);
492 let _delta = ed.revise_field("subject", "Hello").unwrap();
494 assert_eq!(doc.main().field_markdown("subject").unwrap().unwrap(), "Hello");
495
496 let mut ed = TypedWriter::new(&config, &mut doc);
498 assert_eq!(
499 ed.revise_field("nope", "x").unwrap_err().variant_name(),
500 "UnknownField"
501 );
502 let err = ed.revise_field("subject", "a\n\nb").unwrap_err();
504 assert_eq!(err.variant_name(), "FieldRichtextNotInline");
505 assert_eq!(doc.main().field_markdown("subject").unwrap().unwrap(), "Hello");
506 }
507
508 #[test]
509 fn card_writer_revise_field_resolves_card_schema() {
510 let config = config();
511 let mut doc = blank_doc();
512 doc.push_card(Card::new("note").unwrap()).unwrap();
513
514 let mut ed = TypedWriter::new(&config, &mut doc);
515 ed.card(0).unwrap().revise_field("body", "**hi**").unwrap();
516 assert_eq!(doc.cards()[0].field_markdown("body").unwrap().unwrap(), "**hi**");
517
518 let mut ed = TypedWriter::new(&config, &mut doc);
519 assert_eq!(
520 ed.card(0)
521 .unwrap()
522 .revise_field("stray", "x")
523 .unwrap_err()
524 .variant_name(),
525 "UnknownField"
526 );
527 }
528}