Removed Nix and the sample-main, added a CI/CD pipeline and updated the README.md
Some checks failed
Check, format and test / build (push) Failing after 49s
Some checks failed
Check, format and test / build (push) Failing after 49s
This commit is contained in:
122
src/record.rs
Normal file
122
src/record.rs
Normal file
@ -0,0 +1,122 @@
|
||||
use chrono::{DateTime, Local, TimeZone};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Clock {
|
||||
#[default]
|
||||
FirstIn,
|
||||
FirstOut,
|
||||
SecondIn,
|
||||
SecondOut,
|
||||
}
|
||||
|
||||
// unsafe impl Send for Clock {}
|
||||
// unsafe impl Sync for Clock {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct Record {
|
||||
pub employee_id: u32,
|
||||
pub clock: Clock,
|
||||
pub datetime: DateTime<Local>,
|
||||
}
|
||||
|
||||
// unsafe impl Send for Record {}
|
||||
// unsafe impl Sync for Record {}
|
||||
|
||||
impl TryFrom<&[u8]> for Record {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(record_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||
// Return an error if the slice length is less than 12
|
||||
if record_bytes.len() != 12 {
|
||||
return Err("Slice must be of length 12 to be converted into Record");
|
||||
}
|
||||
|
||||
// Extract the employee ID from the central 4 bytes of the record in
|
||||
// little endian
|
||||
let mut employee_id = [0u8; 4];
|
||||
employee_id.clone_from_slice(&record_bytes[4..8]);
|
||||
let employee_id = u32::from_le_bytes(employee_id);
|
||||
|
||||
// Extract the DateTime from the last 4 bytes of the record in little
|
||||
// endian
|
||||
let mut datetime = [0u8; 4];
|
||||
datetime.clone_from_slice(&record_bytes[8..12]);
|
||||
let datetime = u32::from_le_bytes(datetime);
|
||||
|
||||
// Return a new Record
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
Ok(Self {
|
||||
employee_id,
|
||||
clock: match record_bytes[1] >> 6 {
|
||||
0 => Clock::FirstIn,
|
||||
1 => Clock::FirstOut,
|
||||
2 => Clock::SecondIn,
|
||||
3 => Clock::SecondOut,
|
||||
_ => unreachable!("Math has broken"),
|
||||
},
|
||||
datetime: Local
|
||||
.with_ymd_and_hms(
|
||||
// DateTime data is in this format:
|
||||
//
|
||||
// mmmmmmhh hhhddddd MMMMyyyy yyyyyyyy
|
||||
//
|
||||
// Using some bitshifts and some bitwise operations we can
|
||||
// extract the data and put it into a DateTime struct
|
||||
(datetime & 0x0fff) as i32,
|
||||
(datetime >> 12) & 0x0f,
|
||||
(datetime >> 16) & 0x1f,
|
||||
(datetime >> 21) & 0x1f,
|
||||
(datetime >> 26) & 0x3f,
|
||||
0,
|
||||
)
|
||||
.single()
|
||||
.ok_or("DateTime conversion error")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn valid_record_conversion() {
|
||||
let record_bytes: &[u8] = &[0x10, 0x23, 0x0b, 0x1d, 0x01, 0, 0, 0, 0xb2, 0x17, 0x01, 0];
|
||||
|
||||
assert_eq!(
|
||||
record_bytes.try_into(),
|
||||
Ok(Record {
|
||||
employee_id: 1,
|
||||
clock: Clock::FirstIn,
|
||||
datetime: Local
|
||||
.with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
|
||||
.single()
|
||||
.expect("Datetime is not unique!"),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_length() {
|
||||
let record_bytes: &[u8] = &[0u8; 11];
|
||||
|
||||
assert_eq!(
|
||||
record_bytes.try_into(),
|
||||
Err::<Record, &str>("Slice must be at least of length 12 to be converted into Record")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_datetime() {
|
||||
let record_bytes: &[u8] = &[0x10, 0x23, 0x0b, 0x1d, 0x01, 0, 0, 0, 0xb2, 0x17, 0x20, 0];
|
||||
|
||||
assert_eq!(
|
||||
record_bytes.try_into(),
|
||||
Err::<Record, &str>("DateTime conversion error")
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user