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