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_alphabetic() && first != '_' {
return false;
}
for ch in chars {
if !ch.is_ascii_alphanumeric() && ch != '_' {
return false;
}
}
true
}
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
pub enum EditError {
#[error("invalid field name '{0}': must match [A-Za-z_][A-Za-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 },
#[error("value nests deeper than the maximum of {max} levels")]
ValueTooDeep { max: usize },
}
impl EditError {
pub fn variant_name(&self) -> &'static str {
match self {
EditError::InvalidFieldName(_) => "InvalidFieldName",
EditError::InvalidKindName(_) => "InvalidKindName",
EditError::ReservedKind => "ReservedKind",
EditError::IndexOutOfRange { .. } => "IndexOutOfRange",
EditError::ValueTooDeep { .. } => "ValueTooDeep",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FieldViolation {
InvalidName,
TooDeep,
}
fn check_field(name: &str, value: &serde_json::Value) -> Result<(), EditError> {
validate_field(name, value).map_err(|v| match v {
FieldViolation::InvalidName => EditError::InvalidFieldName(name.to_string()),
FieldViolation::TooDeep => EditError::ValueTooDeep {
max: crate::document::limits::MAX_YAML_DEPTH,
},
})
}
fn check_meta_depth(map: &serde_json::Map<String, serde_json::Value>) -> Result<(), EditError> {
let as_value = serde_json::Value::Object(map.clone());
if crate::value::json_depth_exceeds(&as_value, crate::document::limits::MAX_YAML_DEPTH) {
return Err(EditError::ValueTooDeep {
max: crate::document::limits::MAX_YAML_DEPTH,
});
}
Ok(())
}
pub fn validate_field(key: &str, value: &serde_json::Value) -> Result<(), FieldViolation> {
if !is_valid_field_name(key) {
return Err(FieldViolation::InvalidName);
}
if crate::value::json_depth_exceeds(value, crate::document::limits::MAX_YAML_DEPTH) {
return Err(FieldViolation::TooDeep);
}
Ok(())
}
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) -> Result<(), EditError> {
Self::check_composable_kind(&card)?;
self.cards_vec_mut().push(card);
Ok(())
}
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::check_composable_kind(&card)?;
self.cards_vec_mut().insert(index, card);
Ok(())
}
fn check_composable_kind(card: &Card) -> Result<(), EditError> {
let kind = card.kind().unwrap_or("");
validate_composable_kind(kind).map_err(|e| match e {
CardKindError::InvalidName => EditError::InvalidKindName(kind.to_string()),
CardKindError::Reserved => EditError::ReservedKind,
})
}
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> {
check_field(name, value.as_json())?;
self.payload_mut().insert(name.to_string(), value);
Ok(())
}
pub fn set_fill(&mut self, name: &str, value: QuillValue) -> Result<(), EditError> {
check_field(name, value.as_json())?;
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>,
) -> Result<(), EditError> {
check_meta_depth(&value)?;
self.payload_mut().set_ext(value);
Ok(())
}
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,
) -> Result<(), EditError> {
let mut map = self.payload_mut().ext().cloned().unwrap_or_default();
map.insert(namespace.into(), value);
check_meta_depth(&map)?;
self.payload_mut().take_ext();
self.payload_mut().set_ext(map);
Ok(())
}
pub fn remove_ext_namespace(&mut self, namespace: &str) -> Option<serde_json::Value> {
let mut map = self.payload_mut().take_ext()?;
let removed = map.remove(namespace);
if !map.is_empty() {
self.payload_mut().set_ext(map);
}
removed
}
pub fn seed(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {
self.payload().seed()
}
pub fn set_seed_namespace(
&mut self,
card_kind: impl Into<String>,
value: serde_json::Value,
) -> Result<(), EditError> {
let card_kind = card_kind.into();
validate_composable_kind(&card_kind).map_err(|e| match e {
CardKindError::InvalidName => EditError::InvalidKindName(card_kind.clone()),
CardKindError::Reserved => EditError::ReservedKind,
})?;
let mut map = self.payload_mut().seed().cloned().unwrap_or_default();
map.insert(card_kind, value);
check_meta_depth(&map)?;
self.payload_mut().take_seed();
self.payload_mut().set_seed(map);
Ok(())
}
pub fn remove_seed_namespace(&mut self, card_kind: &str) -> Option<serde_json::Value> {
let mut map = self.payload_mut().take_seed()?;
let removed = map.remove(card_kind);
if !map.is_empty() {
self.payload_mut().set_seed(map);
}
removed
}
pub fn replace_body(&mut self, body: impl Into<String>) {
self.overwrite_body(body.into());
}
}