1use std::{
2 borrow::Cow,
3 convert::TryFrom,
4 hash::{Hash, Hasher},
5};
6
7use miette::Diagnostic;
8use thiserror::Error;
9
10use crate::{body::Body, Fragment};
11
12#[derive(Debug, Clone, Eq, Ord, PartialOrd)]
15pub struct Trailer<'a> {
16 key: Cow<'a, str>,
17 value: Cow<'a, str>,
18}
19
20impl<'a> Trailer<'a> {
21 #[must_use]
41 pub const fn new(key: Cow<'a, str>, value: Cow<'a, str>) -> Self {
42 Self { key, value }
43 }
44
45 #[must_use]
59 pub fn get_key(&self) -> String {
60 format!("{}", self.key)
61 }
62
63 #[must_use]
77 pub fn get_value(&self) -> String {
78 self.value.to_string()
79 }
80}
81
82impl<'a> PartialEq for Trailer<'a> {
83 fn eq(&self, other: &Self) -> bool {
84 self.key == other.key && self.value.trim_end() == other.value.trim_end()
85 }
86}
87
88impl<'a> Hash for Trailer<'a> {
89 fn hash<H: Hasher>(&self, state: &mut H) {
90 self.key.hash(state);
91 self.value.trim_end().hash(state);
92 }
93}
94
95impl<'a> From<Trailer<'a>> for String {
96 fn from(trailer: Trailer<'_>) -> Self {
97 format!("{}: {}", trailer.key, trailer.value)
98 }
99}
100
101impl<'a> From<Trailer<'a>> for Fragment<'a> {
102 fn from(trailer: Trailer<'_>) -> Self {
103 let trailer: String = trailer.into();
104 Body::from(trailer).into()
105 }
106}
107
108impl<'a> TryFrom<Body<'a>> for Trailer<'a> {
109 type Error = Error;
110
111 fn try_from(body: Body<'a>) -> Result<Self, Self::Error> {
112 let content: String = body.clone().into();
113 let mut value_and_key = content.split(": ").map(ToString::to_string);
114
115 let key: String = value_and_key
116 .next()
117 .ok_or_else(|| Error::new_not_a_trailer(&body))?;
118
119 let value: String = value_and_key
120 .next()
121 .ok_or_else(|| Error::new_not_a_trailer(&body))?;
122
123 Ok(Trailer::new(key.into(), value.into()))
124 }
125}
126
127#[derive(Error, Debug, Diagnostic)]
129pub enum Error {
130 #[error("not a trailer")]
132 #[diagnostic(url(docsrs), code(mit_commit::trailer::error::not_atrailer))]
133 NotATrailer(
134 #[source_code] String,
135 #[label("no colon in body line")] (usize, usize),
136 ),
137}
138
139impl Error {
140 fn new_not_a_trailer(body: &Body<'_>) -> Self {
141 let text: String = body.clone().into();
142 Self::NotATrailer(text.clone(), (0, text.len()))
143 }
144}