1pub const VERSION: &str = env!("CARGO_PKG_VERSION");
3
4pub mod error;
5pub mod types;
6
7pub use error::{Error, Result};
8pub use types::*;
9
10fn sanitize_author_field(s: &str) -> String {
15 s.chars()
16 .filter(|c| !matches!(c, '\0' | '\n' | '\r' | '<' | '>'))
17 .collect()
18}
19
20pub fn resolve_author(name: &str, email: &str, agent: &str) -> (String, String) {
25 let safe_agent = sanitize_author_field(agent);
26 let sanitized_name = sanitize_author_field(name);
27 let effective_name = if sanitized_name.is_empty() {
28 safe_agent.clone()
29 } else {
30 sanitized_name
31 };
32 let sanitized_email = sanitize_author_field(email);
33 let effective_email = if sanitized_email.is_empty() {
34 format!("{}@dkod.dev", safe_agent)
35 } else {
36 sanitized_email
37 };
38 (effective_name, effective_email)
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn resolve_author_uses_supplied_values() {
47 let (name, email) = resolve_author("Alice", "alice@example.com", "agent-1");
48 assert_eq!(name, "Alice");
49 assert_eq!(email, "alice@example.com");
50 }
51
52 #[test]
53 fn resolve_author_falls_back_to_agent() {
54 let (name, email) = resolve_author("", "", "agent-1");
55 assert_eq!(name, "agent-1");
56 assert_eq!(email, "agent-1@dkod.dev");
57 }
58
59 #[test]
60 fn resolve_author_sanitizes_newlines_and_nulls() {
61 let (name, email) = resolve_author("Al\nice\0", "al\rice@\nex.com", "agent-1");
62 assert_eq!(name, "Alice");
63 assert_eq!(email, "alice@ex.com");
64 }
65
66 #[test]
67 fn resolve_author_falls_back_when_input_is_only_stripped_chars() {
68 let (name, email) = resolve_author("\n", "\r\0", "agent-1");
69 assert_eq!(name, "agent-1");
70 assert_eq!(email, "agent-1@dkod.dev");
71 }
72
73 #[test]
74 fn resolve_author_strips_angle_brackets() {
75 let (name, email) = resolve_author("Alice <hacker>", "a<b>c@ex.com", "agent-1");
76 assert_eq!(name, "Alice hacker");
77 assert_eq!(email, "abc@ex.com");
78 }
79}