Skip to content

Commit 545edf5

Browse files
committed
fix!: Make SignatureRef::to_owned() fallible.
The contained `time` field is now a string, which has to be parsed into a time for conversion to an owned type. Additionally, replace `From<SignatureRef> for Signature` with `TryFrom`.
1 parent 577c54e commit 545edf5

File tree

4 files changed

+58
-45
lines changed

4 files changed

+58
-45
lines changed

gix-actor/src/lib.rs

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
///
1414
/// For convenience to allow using `bstr` without adding it to own cargo manifest.
1515
pub use bstr;
16-
use bstr::{BStr, BString, ByteSlice};
16+
use bstr::{BStr, BString};
1717
/// The re-exported `gix-date` crate.
1818
///
1919
/// For convenience to allow using `gix-date` without adding it to own cargo manifest.
@@ -52,7 +52,7 @@ pub struct IdentityRef<'a> {
5252
pub email: &'a BStr,
5353
}
5454

55-
/// A mutable signature is created by an actor at a certain time.
55+
/// A mutable signature that is created by an actor at a certain time.
5656
///
5757
/// Note that this is not a cryptographical signature.
5858
#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
@@ -70,13 +70,7 @@ pub struct Signature {
7070
pub time: date::Time,
7171
}
7272

73-
impl Signature {
74-
/// Seconds since unix epoch from the time
75-
pub fn seconds(&self) -> gix_date::SecondsSinceUnixEpoch {
76-
self.time.seconds
77-
}
78-
}
79-
/// A immutable signature is created by an actor at a certain time.
73+
/// An immutable signature that is created by an actor at a certain time.
8074
///
8175
/// Note that this is not a cryptographical signature.
8276
#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
@@ -94,16 +88,3 @@ pub struct SignatureRef<'a> {
9488
/// The time stamp at which the signature was performed.
9589
pub time: &'a BStr,
9690
}
97-
98-
impl SignatureRef<'_> {
99-
/// Seconds since unix epoch from the time
100-
pub fn seconds(&self) -> gix_date::SecondsSinceUnixEpoch {
101-
use winnow::stream::AsChar;
102-
self.time
103-
.split(|b| b.is_space())
104-
.next()
105-
.and_then(|i| i.to_str().ok())
106-
.and_then(|s| s.parse().ok())
107-
.unwrap_or(Default::default())
108-
}
109-
}

gix-actor/src/signature/decode.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,12 @@ pub use function::identity;
8181
#[cfg(test)]
8282
mod tests {
8383
mod parse_signature {
84+
use crate::{signature, SignatureRef};
8485
use bstr::ByteSlice;
86+
use gix_date::time::Sign;
8587
use gix_testtools::to_bstr_err;
8688
use winnow::prelude::*;
8789

88-
use crate::{signature, SignatureRef};
89-
9090
fn decode<'i>(
9191
i: &mut &'i [u8],
9292
) -> ModalResult<SignatureRef<'i>, winnow::error::TreeError<&'i [u8], winnow::error::StrContext>> {
@@ -103,13 +103,23 @@ mod tests {
103103

104104
#[test]
105105
fn tz_minus() {
106+
let actual = decode
107+
.parse_peek(b"Sebastian Thiel <[email protected]> 1528473343 -0230")
108+
.expect("parse to work")
109+
.1;
106110
assert_eq!(
107-
decode
108-
.parse_peek(b"Sebastian Thiel <[email protected]> 1528473343 -0230")
109-
.expect("parse to work")
110-
.1,
111+
actual,
111112
signature("Sebastian Thiel", "[email protected]", "1528473343 -0230")
112113
);
114+
assert_eq!(actual.seconds(), 1528473343);
115+
assert_eq!(
116+
actual.time().expect("valid"),
117+
gix_date::Time {
118+
seconds: 1528473343,
119+
offset: -9000,
120+
sign: Sign::Minus
121+
}
122+
);
113123
}
114124

115125
#[test]

gix-actor/src/signature/mod.rs

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ mod _ref {
55

66
use crate::{signature::decode, IdentityRef, Signature, SignatureRef};
77

8+
/// Lifecycle
89
impl<'a> SignatureRef<'a> {
910
/// Deserialize a signature from the given `data`.
1011
pub fn from_bytes<E>(mut data: &'a [u8]) -> Result<SignatureRef<'a>, winnow::error::ErrMode<E>>
@@ -15,15 +16,18 @@ mod _ref {
1516
}
1617

1718
/// Create an owned instance from this shared one.
18-
pub fn to_owned(&self) -> Signature {
19-
Signature {
19+
pub fn to_owned(&self) -> Result<Signature, gix_date::parse::Error> {
20+
Ok(Signature {
2021
name: self.name.to_owned(),
2122
email: self.email.to_owned(),
22-
time: Time::from_bytes(self.time).expect("Time must be valid"),
23-
}
23+
time: self.time()?,
24+
})
2425
}
26+
}
2527

26-
/// Trim whitespace surrounding the name and email and return a new signature.
28+
/// Access
29+
impl<'a> SignatureRef<'a> {
30+
/// Trim the whitespace surrounding the `name`, `email` and `time` and return a new signature.
2731
pub fn trim(&self) -> SignatureRef<'a> {
2832
SignatureRef {
2933
name: self.name.trim().as_bstr(),
@@ -32,22 +36,40 @@ mod _ref {
3236
}
3337
}
3438

35-
/// Return the actor's name and email, effectively excluding the time stamp of this signature.
39+
/// Return the actor's name and email, effectively excluding the timestamp of this signature.
3640
pub fn actor(&self) -> IdentityRef<'a> {
3741
IdentityRef {
3842
name: self.name,
3943
email: self.email,
4044
}
4145
}
46+
47+
/// Parse only the seconds since unix epoch from the `time` field, or silently default to 0
48+
/// if parsing fails. Note that this ignores the timezone, so it can parse otherwise broken dates.
49+
///
50+
/// For a fallible and more complete, but slower version, use [`time()`](Self::time).
51+
pub fn seconds(&self) -> gix_date::SecondsSinceUnixEpoch {
52+
use winnow::stream::AsChar;
53+
self.time
54+
.trim()
55+
.split(|b| b.is_space())
56+
.next()
57+
.and_then(|i| i.to_str().ok()?.parse().ok())
58+
.unwrap_or_default()
59+
}
60+
61+
/// Parse the `time` field for access to the passed time since unix epoch, and the time offset.
62+
pub fn time(&self) -> Result<gix_date::Time, gix_date::parse::Error> {
63+
Time::from_bytes(self.time)
64+
}
4265
}
4366
}
4467

4568
mod convert {
4669
use crate::{Signature, SignatureRef};
47-
use gix_date::Time;
4870

4971
impl Signature {
50-
/// Borrow this instance as immutable
72+
/// Borrow this instance as immutable, serializing the `time` field into `buf`.
5173
pub fn to_ref<'a>(&'a self, buf: &'a mut Vec<u8>) -> SignatureRef<'a> {
5274
SignatureRef {
5375
name: self.name.as_ref(),
@@ -57,14 +79,11 @@ mod convert {
5779
}
5880
}
5981

60-
impl From<SignatureRef<'_>> for Signature {
61-
fn from(other: SignatureRef<'_>) -> Signature {
62-
let SignatureRef { name, email, time } = other;
63-
Signature {
64-
name: name.to_owned(),
65-
email: email.to_owned(),
66-
time: Time::from_bytes(time).expect("Time must be valid"),
67-
}
82+
impl TryFrom<SignatureRef<'_>> for Signature {
83+
type Error = gix_date::parse::Error;
84+
85+
fn try_from(other: SignatureRef<'_>) -> Result<Signature, Self::Error> {
86+
other.to_owned()
6887
}
6988
}
7089
}

gix-actor/tests/signature/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ fn round_trip() -> Result<(), Box<dyn std::error::Error>> {
6565
];
6666

6767
for input in DEFAULTS {
68-
let signature: Signature = gix_actor::SignatureRef::from_bytes::<()>(input).unwrap().into();
68+
let signature: Signature = gix_actor::SignatureRef::from_bytes::<()>(input)
69+
.unwrap()
70+
.try_into()
71+
.unwrap();
6972
let mut output = Vec::new();
7073
signature.write_to(&mut output)?;
7174
assert_eq!(output.as_bstr(), input.as_bstr());

0 commit comments

Comments
 (0)