//! Comments on tickets and patch proposals: the rules a comment lives by,
//! and the store that keeps its rows.
//!
//! The rules sit here rather than in the handlers because everything that
//! speaks about a comment has to agree on them: what a comment body must be,
//! and who may change one.
pub mod store;
pub use store::CommentAuthor;
/// How long a comment's content may be, which is the width of the column it
/// is kept in: a longer one is refused here rather than by SQLite in words of
/// its own.
pub const MAX_CONTENT_LEN: usize = 65_536;
/// The content a comment may be saved with: the text with the space around it
/// taken off, or the reason it cannot be one.
///
/// The content is the one thing a comment cannot do without - it is what the
/// thread is for - so it is asked for in words of ours rather than left to a
/// `NOT NULL` to say no to.
pub fn checked_content(content: &str) -> Result<&str, String> {
let content = content.trim();
if content.is_empty() {
return Err("a comment needs content".to_string());
}
if content.chars().count() > MAX_CONTENT_LEN {
return Err(format!(
"a comment may be at most {MAX_CONTENT_LEN} characters long"
));
}
Ok(content)
}
/// Whether `viewer` may edit or delete a comment written by `author` in a
/// repository owned by `owner`. If the comment was created by a remote actor
/// (author is None), only the repository owner may change it.
///
/// The person who wrote the comment, and the person whose repository it is,
/// and nobody else.
pub fn may_change(author: Option<i64>, owner: i64, viewer: i64) -> bool {
author.is_some_and(|a| viewer == a) || viewer == owner
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_comment_content_is_the_text_with_the_space_around_it_gone() {
assert_eq!(checked_content(" Great point \n"), Ok("Great point"));
}
#[test]
fn a_comment_content_of_nothing_is_refused_in_words_of_ours() {
assert_eq!(
checked_content(""),
Err("a comment needs content".to_string())
);
assert_eq!(
checked_content(" "),
Err("a comment needs content".to_string())
);
assert_eq!(
checked_content("\n\t"),
Err("a comment needs content".to_string())
);
}
#[test]
fn a_comment_content_longer_than_the_column_holds_says_how_long_is_too_long() {
let long = "x".repeat(MAX_CONTENT_LEN + 1);
assert_eq!(
checked_content(&long).unwrap_err(),
"a comment may be at most 65536 characters long"
);
// The width is counted in characters rather than bytes, so a comment
// of accents fills the column the same way the column counts it.
assert!(checked_content(&"é".repeat(MAX_CONTENT_LEN)).is_ok());
}
#[test]
fn a_comment_is_changed_by_the_one_who_wrote_it_or_the_owner_of_the_repository() {
assert!(may_change(Some(3), 7, 3), "the author may change it");
assert!(may_change(Some(3), 7, 7), "the owner may change it");
assert!(!may_change(Some(3), 7, 9), "somebody else may not");
// Remote-created comments (author is None) can only be changed by the
// owner
assert!(
may_change(None, 7, 7),
"the owner may change remote-created comments"
);
assert!(
!may_change(None, 7, 3),
"non-owners may not change remote-created comments"
);
}
}