cob: improve Comment generics

`Infallible` is a more appropriate type in comparsion to `()` for
comments that do not require a `CodeLocation`.

That is because if the type for `location` is `Option<()>`, this means
that `Some(())` can be constructed, however, we never intend to
construct that variant. On the other hand, `Infallible` can never be
constructed, and so, only `None` can be used.

This requires updates to some functions to use the generic `T` and
also handle the different kinds of `Comment`s -- `Infallible` and
`CodeLocation`.

X-Co-Authored-By: Fintan Halpenny <fintan.halpenny@gmail.com>
This commit is contained in:
Sebastian Martinez 2023-12-13 14:09:10 +01:00
parent 48843434c7
commit 1f36879aa2
No known key found for this signature in database
5 changed files with 43 additions and 13 deletions

View File

@ -5,9 +5,9 @@ use crate::terminal as term;
use crate::terminal::format::Author;
/// Return a comment header as a [`term::Element`].
pub fn header(
pub fn header<T>(
id: &CommentId,
comment: &Comment,
comment: &Comment<T>,
profile: &Profile,
) -> term::hstack::HStack<'static> {
let author = comment.author();
@ -27,7 +27,7 @@ pub fn header(
}
/// Return a full comment widget as a [`term::Element`].
pub fn widget<'a>(id: &CommentId, comment: &Comment, profile: &Profile) -> term::VStack<'a> {
pub fn widget<'a, T>(id: &CommentId, comment: &Comment<T>, profile: &Profile) -> term::VStack<'a> {
term::vstack::bordered(header(id, comment, profile))
.child(term::textarea(comment.body()).wrap(60))
}

View File

@ -103,7 +103,7 @@ pub(crate) fn issue(id: IssueId, issue: Issue, aliases: &impl AliasStore) -> Val
"title": issue.title(),
"state": issue.state(),
"assignees": issue.assignees().collect::<Vec<_>>(),
"discussion": issue.comments().map(|(id, c)| comment(id, c, aliases)).collect::<Vec<_>>(),
"discussion": issue.comments().map(|(id, c)| issue_comment(id, c, aliases)).collect::<Vec<_>>(),
"labels": issue.labels().collect::<Vec<_>>(),
})
}
@ -132,7 +132,7 @@ pub(crate) fn patch(
"base": rev.base(),
"oid": rev.head(),
"refs": get_refs(repo, patch.author().id(), &rev.head()).unwrap_or_default(),
"discussions": rev.discussion().comments().map(|(id, c)| comment(id, c, aliases)).collect::<Vec<_>>(),
"discussions": rev.discussion().comments().map(|(id, c)| patch_comment(id, c, aliases)).collect::<Vec<_>>(),
"timestamp": rev.timestamp().as_secs(),
"reviews": rev.reviews().map(|(nid, r)| review(nid, r, aliases)).collect::<Vec<_>>(),
})
@ -172,8 +172,8 @@ fn review(nid: &NodeId, review: &Review, aliases: &impl AliasStore) -> Value {
})
}
/// Returns JSON for a `Comment`.
fn comment(id: &CommentId, comment: &Comment, aliases: &impl AliasStore) -> Value {
/// Returns JSON for a Issue `Comment`.
fn issue_comment(id: &CommentId, comment: &Comment, aliases: &impl AliasStore) -> Value {
json!({
"id": *id,
"author": author(&Author::from(comment.author()), aliases.alias(&comment.author())),
@ -194,6 +194,33 @@ fn comment(id: &CommentId, comment: &Comment, aliases: &impl AliasStore) -> Valu
})
}
/// Returns JSON for a Patch `Comment`.
fn patch_comment(
id: &CommentId,
comment: &Comment<CodeLocation>,
aliases: &impl AliasStore,
) -> Value {
json!({
"id": *id,
"author": author(&Author::from(comment.author()), aliases.alias(&comment.author())),
"body": comment.body(),
"edits": comment.edits().map(|edit| {
json!({
"author": author(&Author::from(edit.author), aliases.alias(&edit.author)),
"body": edit.body,
"timestamp": edit.timestamp.as_secs(),
"embeds": edit.embeds,
})
}).collect::<Vec<_>>(),
"embeds": comment.embeds().to_vec(),
"reactions": comment.reactions().collect::<Vec<_>>(),
"timestamp": comment.timestamp().as_secs(),
"replyTo": comment.reply_to(),
"location": comment.location(),
"resolved": comment.resolved(),
})
}
/// Returns JSON for a `Review`.
fn review_comment(
id: &CommentId,

View File

@ -3041,6 +3041,7 @@ mod routes {
"reactions": [["z6Mkk7oqY4pPxhMmGEotDYsFo97vhCj85BLY1H256HrJmjN8","🚀"]],
"timestamp": TIMESTAMP,
"replyTo": null,
"location": null,
"resolved": false,
},
{
@ -3063,6 +3064,7 @@ mod routes {
"reactions": [],
"timestamp": TIMESTAMP,
"replyTo": comment_id,
"location": null,
"resolved": false,
},
],

View File

@ -1012,7 +1012,7 @@ impl Patch {
body,
reply_to,
embeds,
..
location,
} => {
if let Some(revision) = lookup::revision_mut(self, &revision)? {
thread::comment(
@ -1022,7 +1022,7 @@ impl Patch {
timestamp,
body,
reply_to,
None,
location,
embeds,
)?;
}
@ -1255,7 +1255,7 @@ pub struct Revision {
/// Reference to the Git object containing the code (revision head).
pub(super) oid: git::Oid,
/// Discussion around this revision.
pub(super) discussion: Thread<Comment>,
pub(super) discussion: Thread<Comment<CodeLocation>>,
/// Reviews of this revision's changes (one per actor).
pub(super) reviews: BTreeMap<ActorId, Option<Review>>,
/// When this revision was created.
@ -1313,7 +1313,7 @@ impl Revision {
}
/// Discussion around this revision.
pub fn discussion(&self) -> &Thread<Comment> {
pub fn discussion(&self) -> &Thread<Comment<CodeLocation>> {
&self.discussion
}

View File

@ -1,5 +1,6 @@
use std::cmp::Ordering;
use std::collections::{BTreeMap, BTreeSet};
use std::convert::Infallible;
use std::str::FromStr;
use once_cell::sync::Lazy;
@ -67,7 +68,7 @@ pub struct Edit {
/// A comment on a discussion thread.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Comment<T = ()> {
pub struct Comment<T = Infallible> {
/// Comment author.
author: ActorId,
/// The comment body.
@ -265,7 +266,7 @@ impl From<Action> for nonempty::NonEmpty<Action> {
/// A discussion thread.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Thread<T = Comment<()>> {
pub struct Thread<T = Comment> {
/// The comments under the thread.
pub(crate) comments: BTreeMap<CommentId, Option<T>>,
/// Comment timeline.