use unicode_normalization::UnicodeNormalization;
use crate::document::meta::{validate_composable_kind, CardKindError};
use crate::document::{Card, Document, Payload};
use crate::value::QuillValue;
use crate::version::QuillReference;
pub fn is_valid_field_name(name: &str) -> bool {
let normalized: String = name.nfc().collect();
if normalized.is_empty() {
return false;
}
let mut chars = normalized.chars();
let first = chars.next().unwrap();
if !first.is_ascii_lowercase() && first != '_' {
return false;
}
for ch in chars {
if !ch.is_ascii_lowercase() && !ch.is_ascii_digit() && ch != '_' {
return false;
}
}
true
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum EditError {
#[error("invalid field name '{0}': must match [a-z_][a-z0-9_]*")]
InvalidFieldName(String),
#[error("invalid card kind '{0}': must match [a-z_][a-z0-9_]*")]
InvalidKindName(String),
#[error("card kind 'main' is reserved for the document root")]
ReservedKind,
#[error("index {index} is out of range (len = {len})")]
IndexOutOfRange { index: usize, len: usize },
}
impl Document {
pub fn set_quill_ref(&mut self, reference: QuillReference) {
self.main_mut().payload_mut().set_quill(reference);
}
pub fn card_mut(&mut self, index: usize) -> Option<&mut Card> {
self.cards_mut().get_mut(index)
}
pub fn push_card(&mut self, card: Card) {
self.cards_vec_mut().push(card);
}
pub fn insert_card(&mut self, index: usize, card: Card) -> Result<(), EditError> {
let len = self.cards().len();
if index > len {
return Err(EditError::IndexOutOfRange { index, len });
}
self.cards_vec_mut().insert(index, card);
Ok(())
}
pub fn remove_card(&mut self, index: usize) -> Option<Card> {
if index >= self.cards().len() {
return None;
}
Some(self.cards_vec_mut().remove(index))
}
pub fn set_card_kind(
&mut self,
index: usize,
new_kind: impl Into<String>,
) -> Result<(), EditError> {
let new_kind = new_kind.into();
validate_composable_kind(&new_kind).map_err(|e| match e {
CardKindError::InvalidName => EditError::InvalidKindName(new_kind.clone()),
CardKindError::Reserved => EditError::ReservedKind,
})?;
let len = self.cards().len();
let card = self
.card_mut(index)
.ok_or(EditError::IndexOutOfRange { index, len })?;
card.payload_mut().set_kind(new_kind);
Ok(())
}
pub fn move_card(&mut self, from: usize, to: usize) -> Result<(), EditError> {
let len = self.cards().len();
if from >= len {
return Err(EditError::IndexOutOfRange { index: from, len });
}
if to >= len {
return Err(EditError::IndexOutOfRange { index: to, len });
}
if from == to {
return Ok(());
}
let card = self.cards_vec_mut().remove(from);
self.cards_vec_mut().insert(to, card);
Ok(())
}
}
impl Card {
pub fn new(kind: impl Into<String>) -> Result<Self, EditError> {
let kind = kind.into();
validate_composable_kind(&kind).map_err(|e| match e {
CardKindError::InvalidName => EditError::InvalidKindName(kind.clone()),
CardKindError::Reserved => EditError::ReservedKind,
})?;
let mut payload = Payload::new();
payload.set_kind(kind);
Ok(Card::from_parts(payload, String::new()))
}
pub fn set_field(&mut self, name: &str, value: QuillValue) -> Result<(), EditError> {
if !is_valid_field_name(name) {
return Err(EditError::InvalidFieldName(name.to_string()));
}
self.payload_mut().insert(name.to_string(), value);
Ok(())
}
pub fn set_fill(&mut self, name: &str, value: QuillValue) -> Result<(), EditError> {
if !is_valid_field_name(name) {
return Err(EditError::InvalidFieldName(name.to_string()));
}
self.payload_mut().insert_fill(name.to_string(), value);
Ok(())
}
pub fn remove_field(&mut self, name: &str) -> Result<Option<QuillValue>, EditError> {
if !is_valid_field_name(name) {
return Err(EditError::InvalidFieldName(name.to_string()));
}
Ok(self.payload_mut().remove(name))
}
pub fn set_ext(&mut self, value: serde_json::Map<String, serde_json::Value>) {
self.payload_mut().set_ext(value);
}
pub fn remove_ext(&mut self) -> Option<serde_json::Map<String, serde_json::Value>> {
self.payload_mut().take_ext()
}
pub fn set_ext_namespace(&mut self, namespace: impl Into<String>, value: serde_json::Value) {
let mut map = self.payload_mut().take_ext().unwrap_or_default();
map.insert(namespace.into(), value);
self.payload_mut().set_ext(map);
}
pub fn replace_body(&mut self, body: impl Into<String>) {
self.overwrite_body(body.into());
}
}