1use std::net::IpAddr;
9use std::path::Path;
10use std::fs::File;
11use std::io::{self, BufRead};
12use thiserror::Error;
13
14#[derive(Error, Debug)]
15pub enum RecordError {
16    #[error("Invalid Ipv4Addr: Must be private or global")]
17    InvalidIpAddress(String),
18}
19
20#[derive(Clone, Debug, Eq, PartialEq)]
22pub struct Record {
23    addr: IpAddr,
25    names: Vec<String>,
27}
28impl Record {
29    pub fn new(addr: IpAddr, names: Vec<String>) -> Result<Self, RecordError> {
30        if addr.is_ipv4() || addr.is_ipv6() {
33            return Ok(Self {
34                addr: addr,
35                names: names,
36            });
37        }
38
39        Err(RecordError::InvalidIpAddress(addr.to_string()))
40    }
41}
42
43#[derive(Error, Debug)]
44pub enum ParserError {
45    #[error(transparent)]
46    CouldNotOpen(#[from] std::io::Error),
47
48    #[error("bad ipv4 addr, not global or loopback")]
49    ParseError(#[from] std::net::AddrParseError),
50
51    #[error("unknown")]
52    Unknown(String),
53}
54
55#[derive(Debug, Default)]
56pub enum Part {
57    Addr,
58    Names,
59    Comment,
60    #[default]
61    Unknown,
62}
63
64#[derive(Debug)]
66struct Parser {
67    line: i64,
68    part: Part,
69    records: Vec<Record>
70}
71
72impl Default for Parser {
73    fn default() -> Parser {
74        let records: Vec<Record> = Vec::new();
75        Parser {line: 0, part: Part::Unknown, records: records}
76    }
77}
78
79impl Parser {
80    pub fn parse(&mut self, file: &Path) -> Result<&Vec<Record>, ParserError> {
81        let file = File::open(file)?;
82        let buff = io::BufReader::new(file).lines();
83
84        self.part = Part::Names;
85
86        for line in buff {
87            if let Ok(a) = line {
88                if a.is_empty() { continue; }
89                if a.starts_with('#') { continue; }
90                let a = a.replace("\t", " ");
92
93                let mut record_info =
94                    a.split(' ').filter(|&s| !s.is_empty()).collect::<Vec<&str>>();
95
96                let name = record_info.remove(0).to_string();
97
98                let addrs =
99                    record_info.iter().map(|&s| s.to_string()).collect::<Vec<String>>();
100
101                if let Ok(record) = Record::new(name.parse()?, addrs) {
102                    self.records.push(record);
103                };
104            }
105        }
106
107        Ok(&self.records)
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn create_loopback() {
117        let addr = "127.0.0.1".parse().unwrap();
118        let names: Vec<String> = vec!["localhost".to_string()];
119        let record = Record::new(addr, names);
120        assert!(record.is_ok())
121    }
122
123    #[test]
124    fn create_private() {
125        let addr = "192.168.10.42".parse().unwrap();
126        let names: Vec<String> = vec!["core.naus".to_string()];
127        let record = Record::new(addr, names);
128        assert!(record.is_ok())
129    }
130
131    #[test]
132    fn test_parser() {
133        use std::path::Path;
134        let mut parser: Parser = Default::default();
135        let path = Path::new("/etc/hosts");
136        match parser.parse(path) {
137            Ok(_v) => println!("good to go"),
138            Err(e) => println!("{e:?}"),
139        }
140    }
141}