use auths_core::witness::SignedReceipt;
pub use auths_core::witness::Receipt;
pub const WITNESS_RECEIPT_KEY: &str = "Auths-Witness-Receipt";
const FOLD_WIDTH: usize = 76;
pub fn append_trailer(message: &str, key: &str, value: &str) -> String {
let full_line = format!("{}: {}", key, value);
let folded = fold_line(&full_line);
let trimmed = message.trim_end();
if trimmed.is_empty() {
return folded;
}
if has_trailer_block(trimmed) {
format!("{}\n{}", trimmed, folded)
} else {
format!("{}\n\n{}", trimmed, folded)
}
}
fn fold_line(line: &str) -> String {
if line.len() <= FOLD_WIDTH {
return line.to_string();
}
let mut result = String::with_capacity(line.len() + line.len() / FOLD_WIDTH);
let mut remaining = line;
while remaining.len() > FOLD_WIDTH {
let break_at = remaining[..FOLD_WIDTH]
.rfind(' ')
.filter(|&pos| pos > 0)
.unwrap_or(FOLD_WIDTH);
result.push_str(&remaining[..break_at]);
result.push('\n');
remaining = &remaining[break_at..];
if !remaining.starts_with(' ') {
result.push(' ');
}
}
result.push_str(remaining);
result
}
pub fn parse_trailers(message: &str) -> Vec<(String, String)> {
let trailer_block = extract_trailer_block(message);
if trailer_block.is_empty() {
return vec![];
}
let unfolded = unfold_lines(trailer_block);
let mut trailers = Vec::new();
for line in unfolded.lines() {
if let Some((key, value)) = parse_trailer_line(line) {
trailers.push((key, value));
}
}
trailers
}
pub fn extract_witness_receipts(message: &str) -> Vec<SignedReceipt> {
parse_trailers(message)
.into_iter()
.filter(|(key, _)| key == WITNESS_RECEIPT_KEY)
.filter_map(|(_, value)| SignedReceipt::from_trailer_value(&value).ok())
.collect()
}
fn has_trailer_block(message: &str) -> bool {
let last_paragraph = match message.rfind("\n\n") {
Some(pos) => &message[pos + 2..],
None => message,
};
last_paragraph
.lines()
.any(|line| parse_trailer_line(line).is_some())
}
fn extract_trailer_block(message: &str) -> &str {
let trimmed = message.trim_end();
match trimmed.rfind("\n\n") {
Some(pos) => &trimmed[pos + 2..],
None => {
if trimmed.lines().all(|l| {
l.starts_with(' ') || l.starts_with('\t') || parse_trailer_line(l).is_some()
}) {
trimmed
} else {
""
}
}
}
}
fn unfold_lines(text: &str) -> String {
let mut result = String::with_capacity(text.len());
for line in text.lines() {
if line.starts_with(' ') || line.starts_with('\t') {
result.push_str(line);
} else {
if !result.is_empty() {
result.push('\n');
}
result.push_str(line);
}
}
result
}
fn parse_trailer_line(line: &str) -> Option<(String, String)> {
let colon_pos = line.find(':')?;
let key = line[..colon_pos].trim();
if key.is_empty() || key.contains(' ') {
return None;
}
if !key
.chars()
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
{
return None;
}
let value = line[colon_pos + 1..].trim().to_string();
Some((key.to_string(), value))
}
#[cfg(test)]
mod tests {
use super::*;
use auths_core::witness::ReceiptTag;
use auths_keri::{KeriSequence, Prefix, Said, VersionString};
fn sample_signed_receipt() -> SignedReceipt {
let receipt = Receipt {
v: VersionString::placeholder(),
t: ReceiptTag,
d: Said::new_unchecked("EEvent456".into()),
i: Prefix::new_unchecked("did:key:z6MkWitness".into()),
s: KeriSequence::new(5),
};
SignedReceipt {
receipt,
signature: vec![0xab; 64],
}
}
#[test]
fn append_trailer_to_empty_message() {
let result = append_trailer("", "Signed-off-by", "Alice");
assert_eq!(result, "Signed-off-by: Alice");
}
#[test]
fn append_trailer_to_message_without_trailers() {
let msg = "fix: resolve null pointer\n\nLong description here.";
let result = append_trailer(msg, "Signed-off-by", "Alice");
assert!(result.contains("\n\nSigned-off-by: Alice"));
}
#[test]
fn append_trailer_to_existing_trailer_block() {
let msg = "fix: bug\n\nSigned-off-by: Bob";
let result = append_trailer(msg, "Reviewed-by", "Alice");
assert!(result.ends_with("Reviewed-by: Alice"));
assert!(!result.contains("\n\n\n"));
}
#[test]
fn fold_short_line_unchanged() {
let line = "Key: short value";
assert_eq!(fold_line(line), line);
}
#[test]
fn fold_long_line() {
let long_value = "x".repeat(100);
let line = format!("Key: {}", long_value);
let folded = fold_line(&line);
for (i, segment) in folded.lines().enumerate() {
if i > 0 {
assert!(
segment.starts_with(' '),
"continuation must start with space"
);
}
}
}
#[test]
fn parse_trailers_basic() {
let msg = "fix: bug\n\nSigned-off-by: Alice\nReviewed-by: Bob";
let trailers = parse_trailers(msg);
assert_eq!(trailers.len(), 2);
assert_eq!(
trailers[0],
("Signed-off-by".to_string(), "Alice".to_string())
);
assert_eq!(trailers[1], ("Reviewed-by".to_string(), "Bob".to_string()));
}
#[test]
fn parse_trailers_with_folding() {
let msg = "fix: bug\n\nKey: start\n of-value";
let trailers = parse_trailers(msg);
assert_eq!(trailers.len(), 1);
assert!(trailers[0].1.contains("start"));
assert!(trailers[0].1.contains("of-value"));
}
#[test]
fn parse_trailers_empty_message() {
let trailers = parse_trailers("");
assert!(trailers.is_empty());
}
#[test]
fn parse_trailers_no_trailer_block() {
let msg = "just a commit message\n\nwith a body paragraph";
let trailers = parse_trailers(msg);
assert!(trailers.is_empty());
}
#[test]
fn extract_witness_receipts_roundtrip() {
let signed = sample_signed_receipt();
let trailer_value = signed.to_trailer_value().unwrap();
let msg = append_trailer(
"feat: add agent signing",
WITNESS_RECEIPT_KEY,
&trailer_value,
);
let receipts = extract_witness_receipts(&msg);
assert_eq!(receipts.len(), 1);
assert_eq!(receipts[0], signed);
}
#[test]
fn extract_multiple_witness_receipts() {
let r1 = sample_signed_receipt();
let mut r2 = sample_signed_receipt();
r2.receipt.i = Prefix::new_unchecked("did:key:z6MkWitness2".into());
r2.receipt.d = Said::new_unchecked("EEvent789".into());
let mut msg = "feat: signed commit".to_string();
msg = append_trailer(&msg, WITNESS_RECEIPT_KEY, &r1.to_trailer_value().unwrap());
msg = append_trailer(&msg, WITNESS_RECEIPT_KEY, &r2.to_trailer_value().unwrap());
let receipts = extract_witness_receipts(&msg);
assert_eq!(receipts.len(), 2);
}
#[test]
fn extract_witness_receipts_ignores_other_trailers() {
let signed = sample_signed_receipt();
let mut msg = "feat: stuff".to_string();
msg = append_trailer(&msg, "Signed-off-by", "Alice");
msg = append_trailer(
&msg,
WITNESS_RECEIPT_KEY,
&signed.to_trailer_value().unwrap(),
);
msg = append_trailer(&msg, "Co-authored-by", "Bob");
let receipts = extract_witness_receipts(&msg);
assert_eq!(receipts.len(), 1);
}
#[test]
fn unfold_lines_basic() {
let text = "Key: start\n of-value\n more";
let result = unfold_lines(text);
assert_eq!(result, "Key: start of-value more");
}
#[test]
fn parse_trailer_line_valid() {
assert_eq!(
parse_trailer_line("Signed-off-by: Alice"),
Some(("Signed-off-by".into(), "Alice".into()))
);
}
#[test]
fn parse_trailer_line_invalid() {
assert!(parse_trailer_line("no colon here").is_none());
assert!(parse_trailer_line("has space key: value").is_none());
assert!(parse_trailer_line(": no key").is_none());
}
}