wards 0.1.10

Библиотека для камеры Beward B2530RZQ-LP
Documentation
use std::str::FromStr;

use chrono::{Local, NaiveDate, NaiveDateTime, TimeZone};

use crate::{
    car::{Car, Event, Notify},
    errors::{
        WardError, ERROR_PARSING_CAR_NUMBER, ERROR_PARSING_DATETIME, ERROR_PARSING_EVENT_STATUS,
    },
};

const DATE_TIME_FORMAT: &str = "%Y-%m-%d %H:%M:%S";

/// Парсер списка автомобильных номеров.
///
/// Возвращает вектор типа Car.
///
/// Принимает на вход данные в виде:
///
/// Number111=carNumber
///
/// Begin111=2020-11-13
///
/// End111=2020-12-22
///
/// Notify111=on
///
/// Note111=комментарий
///
/// 111 - порядковый номер номера автомобиля в камере.
///
/// У Notify возможны два варианта: on или off
///
/// # Examples
///
/// ```
/// let raw_data = r#"Number0=X111XX777
/// Begin0=2020-11-13
/// End0=2020-12-22
/// Notify0=on
/// Note0="#;
/// let cars = raw_numbers(raw_data);
///
/// assert_eq!(cars[0].number, "X111XX777");
/// assert_eq!(cars[0].begin, "2020-11-13");
/// assert_eq!(cars[0].end, "2020-12-22");
/// assert_eq!(cars[0].notify, Notify::On);
/// assert_eq!(cars[0].note, None);
/// ```
///
pub(crate) fn raw_numbers(raw_list: &str) -> Vec<Car> {
    let lines = raw_list.lines();
    let mut cars: Vec<Car> = vec![];

    for line in lines {
        let number_count: usize;

        // LineTitle111=LineData
        let (line_title, line_data) = line.split_once("=").unwrap();

        if line_title.contains(&"Number") {
            number_count = find_sequence(line_title);
            let car_number = line_data;

            let car = Car {
                number: car_number.to_string(),
                begin: None,
                end: None,
                notify: Notify::Off,
                note: None,
            };

            cars.insert(number_count, car);
        }

        if line_title.contains(&"Begin") {
            let car = &mut cars[find_sequence(line_title)];
            car.begin = date_from_str(line_data);
        }

        if line_title.contains(&"End") {
            let car = &mut cars[find_sequence(line_title)];
            car.end = date_from_str(line_data);
        }

        if line_title.contains(&"Notify") {
            let car = &mut cars[find_sequence(line_title)];
            car.notify = Notify::from_str(line_data).unwrap();
        }

        if line_title.contains(&"Note") {
            let car = &mut cars[find_sequence(line_title)];

            car.note = match line_data {
                "" => None,
                data => Some(data.to_string()),
            };
        }
    }

    cars
}

/// Ищет число в строке
///
/// # Examples
///
/// ```rust
/// let line = "line913post";
/// let number = find_sequence_car_number(line);
/// assert_eq!(number, 913);
/// ```
fn find_sequence(line: &str) -> usize {
    let mut result = String::new();

    line.chars().into_iter().for_each(|c| {
        if c.is_numeric() {
            result.push(c)
        };
    });
    result.parse::<usize>().unwrap()
}

/// Преобразует строку в дату
///
/// Формат даты: 2024-11-22
///
/// # Examples
///
/// ```rust
/// let date_str = "2024-11-22";
/// let date = date_from_str(date_str);
///
/// assert_eq!(date.year(), 2024);
/// assert_eq!(date.month(), 11);
/// assert_eq!(date.day(), 22);
/// ```
fn date_from_str(date: &str) -> Option<NaiveDate> {
    match NaiveDate::from_str(date) {
        Ok(date) => Some(date),
        Err(_) => None,
    }
}

/// Преобразует дату в строку
///
/// # Examples
///
/// ```rust
/// let date = NaiveDate::default();
/// assert_eq!(str_from_date(Some(date)), "1970-01-01");
/// assert_eq!(str_from_date(None), "");
/// ```
pub fn str_from_date(date: Option<NaiveDate>) -> String {
    match date {
        Some(date) => date.to_string(),
        None => "".to_string(),
    }
}

/// Парсер событий считывания номеров
/// Возвращает вектор типа Event
///
/// 2024-01-18 09:03:11,K147CP,-
/// 2024-01-18 09:03:48,P829EX,+
///
/// # Examples
///
/// ```rust
/// let raw_data = r#"2024-01-18 09:03:11,K147CP,-
/// 2024-01-18 09:03:48,P829EX,+"#;
///
/// let events = events(raw_data, None);
///
/// assert_eq!(events[0].time, "2024-01-18 09:03:11");
/// assert_eq!(events[0].car_number, "K147CP");
/// assert_eq!(events[0].status, false);
/// assert_eq!(events[1].time, "2024-01-18 09:03:48");
/// assert_eq!(events[1].car_number, "P829EX");
/// assert_eq!(events[1].status, true);
/// ```
pub(crate) fn events(raw_events: &str, status: Option<bool>) -> Result<Vec<Event>, WardError> {
    let mut events = vec![];
    let lines = raw_events.lines();

    for line in lines {
        let mut event = Event {
            time: None,
            car_number: "".to_string(),
            status: false,
        };

        let mut splitter = line.split(",");

        let naive_datetime = match splitter.next() {
            Some(date) => match NaiveDateTime::parse_from_str(date, DATE_TIME_FORMAT) {
                Ok(date_time) => date_time,
                Err(err) => return Err(WardError::ParseError(err.to_string())),
            },
            None => return Err(WardError::ParseError(ERROR_PARSING_DATETIME.to_string())),
        };

        let datetime = Local.from_local_datetime(&naive_datetime).unwrap();

        event.time = Some(datetime);

        event.car_number = match splitter.next() {
            Some(number) => number.to_string(),
            None => return Err(WardError::ParseError(ERROR_PARSING_CAR_NUMBER.to_string())),
        };

        event.status = match splitter.next() {
            Some(status) => match status {
                "+" => true,
                "-" => false,
                _ => false,
            },
            None => {
                return Err(WardError::ParseError(
                    ERROR_PARSING_EVENT_STATUS.to_string(),
                ))
            }
        };

        match status {
            Some(status) => {
                if event.status == status {
                    events.push(event)
                }
            }
            None => events.push(event),
        }
    }

    Ok(events)
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use chrono::{DateTime, Datelike, Local, NaiveDate};

    use crate::{car::Notify, parse::{self, date_from_str}};

    #[test]
    fn finding_sequence() {
        let number = parse::find_sequence("any_s438tring");
        assert_eq!(number, 438);
    }

    #[test]
    fn str_date() {
        let date_str = "2024-11-22";
        let date = parse::date_from_str(date_str).unwrap();

        assert_eq!(date.year(), 2024);
        assert_eq!(date.month(), 11);
        assert_eq!(date.day(), 22);
    }

    #[test]
    fn wrong_date_from_str() {
        let date_str = "not_data_str";
        let date = parse::date_from_str(date_str);

        assert!(date.is_none());

        let date_str = "";
        let date = parse::date_from_str(date_str);

        assert!(date.is_none());
    }

    #[test]
    fn string_from_date() {
        let date = NaiveDate::default();
        assert_eq!(parse::str_from_date(Some(date)), "1970-01-01");
        assert_eq!(parse::str_from_date(None), "");
    }

    #[test]
    fn parse_from_str() {
        let raw_data = r#"Number0=X111XX777
Begin0=2020-11-13
End0=2020-12-22
Notify0=on
Note0="#;
        let cars = parse::raw_numbers(raw_data);
        assert_eq!(cars[0].number, "X111XX777");
        assert_eq!(cars[0].begin, date_from_str("2020-11-13"));
        assert_eq!(cars[0].end, date_from_str("2020-12-22"));
        assert_eq!(cars[0].notify, Notify::On);
        assert_eq!(cars[0].note, None);
    }

    #[test]
    fn parse_from_str_with_comment() {
        let raw_data = r#"Number0=X111XX777
Begin0=2020-11-13
End0=2020-12-22
Notify0=on
Note0=comment"#;
        let cars = parse::raw_numbers(raw_data);
        assert_eq!(cars[0].number, "X111XX777");
        assert_eq!(cars[0].begin, date_from_str("2020-11-13"));
        assert_eq!(cars[0].end, date_from_str("2020-12-22"));
        assert_eq!(cars[0].notify, Notify::On);
        assert_eq!(cars[0].note, Some("comment".to_string()));
    }

    #[test]
    fn parse_from_str_with_long_comment() {
        let raw_data = r#"Number0=X111XX777
Begin0=2020-11-13
End0=2020-12-22
Notify0=on
Note0=Long string COMMENT"#;
        let cars = parse::raw_numbers(raw_data);
        assert_eq!(cars[0].number, "X111XX777");
        assert_eq!(cars[0].begin, date_from_str("2020-11-13"));
        assert_eq!(cars[0].end, date_from_str("2020-12-22"));
        assert_eq!(cars[0].notify, Notify::On);
        assert_eq!(cars[0].note, Some("Long string COMMENT".to_string()));
    }

    #[test]
    fn events() {
        let raw_data = r#"2024-01-18 09:03:11,K147CP,-
 2024-01-18 09:03:48,P829EX,+"#;

        // all events
        let events = parse::events(raw_data, None).unwrap();

        assert_eq!(events.len(), 2);

        assert_eq!(
            events[0].time.unwrap(),
            DateTime::<Local>::from_str("2024-01-18T09:03:11+03:00").unwrap()
        );
        assert_eq!(events[0].car_number, "K147CP");
        assert_eq!(events[0].status, false);
        assert_eq!(
            events[1].time.unwrap(),
            DateTime::<Local>::from_str("2024-01-18T09:03:48+03:00").unwrap()
        );
        assert_eq!(events[1].car_number, "P829EX");
        assert_eq!(events[1].status, true);

        // only false
        let events = parse::events(raw_data, Some(false)).unwrap();

        assert_eq!(events.len(), 1);

        assert_eq!(
            events[0].time.unwrap(),
            DateTime::<Local>::from_str("2024-01-18T09:03:11+03:00").unwrap()
        );
        assert_eq!(events[0].car_number, "K147CP");
        assert_eq!(events[0].status, false);

        // only true
        let events = parse::events(raw_data, Some(true)).unwrap();

        assert_eq!(events.len(), 1);

        assert_eq!(
            events[0].time.unwrap(),
            DateTime::<Local>::from_str("2024-01-18T09:03:48+03:00").unwrap()
        );
        assert_eq!(events[0].car_number, "P829EX");
        assert_eq!(events[0].status, true);
    }
}