1use crate::{Database, Strategy};
2use std::convert::From;
3use std::error::Error;
4use std::fmt::{Display, Formatter};
5use std::str::FromStr;
6use url::{ParseError, Url};
7
8#[derive(Debug)]
9pub enum DICTUrlError {
10 ParseError(ParseError),
11 UnknownAccess(String),
12 MissingParameters,
13 MissingHost,
14 Unsupported(&'static str),
15}
16
17impl Display for DICTUrlError {
18 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
19 write!(
20 f,
21 "{}",
22 match self {
23 Self::ParseError(_) => "Parse error",
24 Self::Unsupported(_) => "Unsuported",
25 Self::UnknownAccess(_) => "Unknown access method",
26 Self::MissingParameters => "Missing parameters",
27 Self::MissingHost => "Missing host",
28 }
29 )
30 }
31}
32
33impl From<ParseError> for DICTUrlError {
34 fn from(inner: ParseError) -> Self {
35 DICTUrlError::ParseError(inner)
36 }
37}
38
39impl Error for DICTUrlError {}
40
41pub enum DICTUrlAccess {
42 AccessOnly,
43 Define(String, Database, Option<usize>),
44 Match(String, Database, Strategy, Option<usize>),
45}
46
47impl FromStr for DICTUrlAccess {
48 type Err = DICTUrlError;
49 fn from_str(s: &str) -> Result<Self, Self::Err> {
50 let path = if let Some(p) = s.strip_prefix("/") {
51 p
52 } else {
53 return Ok(DICTUrlAccess::AccessOnly);
54 };
55
56 let mut parts = path.split(':');
57
58 match parts.next() {
59 None | Some("") => Ok(DICTUrlAccess::AccessOnly),
60 Some("d") => {
61 let word = match parts.next() {
62 Some(w) if !w.is_empty() => w.to_string(),
63 _ => {
64 return Err(DICTUrlError::MissingParameters);
65 }
66 };
67
68 let db = match parts.next() {
69 Some(d) if !d.is_empty() => Database::from(d.to_string()),
70 _ => Database::default(),
71 };
72
73 let nr = if let Some(maybe_n) = parts.next() {
74 if let Ok(n) = maybe_n.parse::<usize>() {
75 Some(n)
76 } else {
77 Some(0)
78 }
79 } else {
80 None
81 };
82
83 Ok(DICTUrlAccess::Define(word, db, nr))
84 }
85 Some("m") => {
86 let word = match parts.next() {
87 Some(w) if !w.is_empty() => w.to_string(),
88 _ => {
89 return Err(DICTUrlError::MissingParameters);
90 }
91 };
92
93 let db = match parts.next() {
94 Some(d) if !d.is_empty() => Database::from(d.to_string()),
95 _ => Database::default(),
96 };
97
98 let strat = match parts.next() {
99 Some(s) if !s.is_empty() => Strategy::from(s.to_string()),
100 _ => Strategy::default(),
101 };
102
103 let nr = if let Some(maybe_n) = parts.next() {
104 if let Ok(n) = maybe_n.parse::<usize>() {
105 Some(n)
106 } else {
107 Some(0)
108 }
109 } else {
110 None
111 };
112
113 Ok(DICTUrlAccess::Match(word, db, strat, nr))
114 }
115 Some(s) => Err(DICTUrlError::UnknownAccess(s.to_string())),
116 }
117 }
118}
119
120pub struct DICTUrl {
121 pub host: String,
122 pub port: u16,
123 pub access_method: DICTUrlAccess,
124}
125
126impl DICTUrl {
127 pub fn new(src: &str) -> Result<Self, DICTUrlError> {
128 let raw_url = Url::parse(src)?;
129
130 if !raw_url.username().is_empty() {
131 return Err(DICTUrlError::Unsupported("Auth part is not supported"));
132 }
133
134 let host: String = raw_url
135 .host_str()
136 .ok_or(DICTUrlError::MissingHost)?
137 .to_string();
138 let port: u16 = raw_url.port().or(Some(2628)).unwrap();
139 let access_method = DICTUrlAccess::from_str(raw_url.path())?;
140
141 Ok(DICTUrl {
142 host,
143 port,
144 access_method,
145 })
146 }
147}
148
149#[cfg(test)]
150mod test {
151 use super::*;
152
153 #[test]
154 fn basic_parsing() {
155 let url = DICTUrl::new("dict://dict.org/d:shortcake:").unwrap();
156
157 assert_eq!(url.host, "dict.org");
158 assert_eq!(url.port, 2628);
159
160 if let DICTUrlAccess::Define(word, _, _) = url.access_method {
161 assert_eq!(word, String::from("shortcake"));
162 } else {
163 panic!("Did not return correct access method");
164 }
165 }
166}