@@ -114,6 +114,76 @@ impl Deref for BodyRef<'_> {
114114 self . body_without_trailer
115115 }
116116}
117+
118+ impl TrailerRef < ' _ > {
119+ /// Check if this trailer is a "Signed-off-by" trailer (case-insensitive).
120+ pub fn is_signed_off_by ( & self ) -> bool {
121+ self . token . eq_ignore_ascii_case ( b"Signed-off-by" )
122+ }
123+
124+ /// Check if this trailer is a "Co-authored-by" trailer (case-insensitive).
125+ pub fn is_co_authored_by ( & self ) -> bool {
126+ self . token . eq_ignore_ascii_case ( b"Co-authored-by" )
127+ }
128+
129+ /// Check if this trailer is an "Acked-by" trailer (case-insensitive).
130+ pub fn is_acked_by ( & self ) -> bool {
131+ self . token . eq_ignore_ascii_case ( b"Acked-by" )
132+ }
133+
134+ /// Check if this trailer is a "Reviewed-by" trailer (case-insensitive).
135+ pub fn is_reviewed_by ( & self ) -> bool {
136+ self . token . eq_ignore_ascii_case ( b"Reviewed-by" )
137+ }
138+
139+ /// Check if this trailer is a "Tested-by" trailer (case-insensitive).
140+ pub fn is_tested_by ( & self ) -> bool {
141+ self . token . eq_ignore_ascii_case ( b"Tested-by" )
142+ }
143+
144+ /// Check if this trailer represents any kind of authorship or attribution
145+ /// (Signed-off-by, Co-authored-by, etc.).
146+ pub fn is_attribution ( & self ) -> bool {
147+ self . is_signed_off_by ( )
148+ || self . is_co_authored_by ( )
149+ || self . is_acked_by ( )
150+ || self . is_reviewed_by ( )
151+ || self . is_tested_by ( )
152+ }
153+
154+ /// Get the token as a case-normalized string for comparison purposes.
155+ /// This can be useful for custom filtering logic.
156+ pub fn token_normalized ( & self ) -> String {
157+ String :: from_utf8_lossy ( self . token ) . to_ascii_lowercase ( )
158+ }
159+ }
160+
161+ impl < ' a > Trailers < ' a > {
162+ /// Filter trailers to only include "Signed-off-by" entries.
163+ pub fn signed_off_by ( self ) -> impl Iterator < Item = TrailerRef < ' a > > {
164+ self . filter ( TrailerRef :: is_signed_off_by)
165+ }
166+
167+ /// Filter trailers to only include "Co-authored-by" entries.
168+ pub fn co_authored_by ( self ) -> impl Iterator < Item = TrailerRef < ' a > > {
169+ self . filter ( TrailerRef :: is_co_authored_by)
170+ }
171+
172+ /// Filter trailers to only include attribution-related entries
173+ /// (Signed-off-by, Co-authored-by, Acked-by, Reviewed-by, Tested-by).
174+ pub fn attributions ( self ) -> impl Iterator < Item = TrailerRef < ' a > > {
175+ self . filter ( TrailerRef :: is_attribution)
176+ }
177+
178+ /// Collect all unique authors from Signed-off-by and Co-authored-by trailers.
179+ /// Returns a Vec of author strings.
180+ pub fn collect_authors ( self ) -> Vec < & ' a BStr > {
181+ self . filter ( |trailer| trailer. is_signed_off_by ( ) || trailer. is_co_authored_by ( ) )
182+ . map ( |trailer| trailer. value )
183+ . collect ( )
184+ }
185+ }
186+
117187#[ cfg( test) ]
118188mod test_parse_trailer {
119189 use super :: * ;
@@ -151,3 +221,68 @@ mod test_parse_trailer {
151221 assert_eq ! ( parse( "foo: bar\r \n " ) , ( "foo" . into( ) , "bar" . into( ) ) ) ;
152222 }
153223}
224+
225+ #[ cfg( test) ]
226+ mod test_trailer_convenience {
227+ use super :: * ;
228+
229+ #[ test]
230+ fn test_signed_off_by_detection ( ) {
231+ let trailer = TrailerRef {
232+ token : "Signed-off-by" . into ( ) ,
233+ value : "John Doe <john@example.com>" . into ( ) ,
234+ } ;
235+ assert ! ( trailer. is_signed_off_by( ) ) ;
236+ assert ! ( !trailer. is_co_authored_by( ) ) ;
237+ assert ! ( trailer. is_attribution( ) ) ;
238+ }
239+
240+ #[ test]
241+ fn test_case_insensitive_detection ( ) {
242+ let trailer = TrailerRef {
243+ token : "signed-off-by" . into ( ) ,
244+ value : "John Doe <john@example.com>" . into ( ) ,
245+ } ;
246+ assert ! ( trailer. is_signed_off_by( ) ) ;
247+
248+ let trailer2 = TrailerRef {
249+ token : "CO-AUTHORED-BY" . into ( ) ,
250+ value : "Jane Smith <jane@example.com>" . into ( ) ,
251+ } ;
252+ assert ! ( trailer2. is_co_authored_by( ) ) ;
253+ assert ! ( trailer2. is_attribution( ) ) ;
254+ }
255+
256+ #[ test]
257+ fn test_multiple_trailer_types ( ) {
258+ let trailer1 = TrailerRef {
259+ token : "Reviewed-by" . into ( ) ,
260+ value : "Reviewer <reviewer@example.com>" . into ( ) ,
261+ } ;
262+ let trailer2 = TrailerRef {
263+ token : "Custom-Field" . into ( ) ,
264+ value : "Some value" . into ( ) ,
265+ } ;
266+
267+ assert ! ( trailer1. is_reviewed_by( ) ) ;
268+ assert ! ( trailer1. is_attribution( ) ) ;
269+ assert ! ( !trailer2. is_attribution( ) ) ;
270+ }
271+
272+ #[ test]
273+ fn test_collect_authors ( ) {
274+ // This would need to be tested with actual Trailers iterator
275+ // but shows the expected behavior
276+ let trailer1 = TrailerRef {
277+ token : "Signed-off-by" . into ( ) ,
278+ value : "John Doe <john@example.com>" . into ( ) ,
279+ } ;
280+ let trailer2 = TrailerRef {
281+ token : "Co-authored-by" . into ( ) ,
282+ value : "Jane Smith <jane@example.com>" . into ( ) ,
283+ } ;
284+
285+ assert ! ( trailer1. is_signed_off_by( ) ) ;
286+ assert ! ( trailer2. is_co_authored_by( ) ) ;
287+ }
288+ }
0 commit comments