4
0

options.rs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. use std::ffi::OsStr;
  2. use std::fmt;
  3. use log::*;
  4. use dns::{QClass, find_qtype_number, qtype};
  5. use dns::record::{A, find_other_qtype_number};
  6. use crate::connect::TransportType;
  7. use crate::output::{OutputFormat, UseColours, TextFormat};
  8. use crate::requests::{RequestGenerator, Inputs, ProtocolTweaks, UseEDNS};
  9. use crate::resolve::Resolver;
  10. use crate::txid::TxidGenerator;
  11. /// The command-line options used when running dog.
  12. #[derive(PartialEq, Debug)]
  13. pub struct Options {
  14. /// The requests to make and how they should be generated.
  15. pub requests: RequestGenerator,
  16. /// Whether to display the time taken after every query.
  17. pub measure_time: bool,
  18. /// How to format the output data.
  19. pub format: OutputFormat,
  20. }
  21. impl Options {
  22. /// Parses and interprets a set of options from the user’s command-line
  23. /// arguments.
  24. ///
  25. /// This returns an `Ok` set of options if successful and running
  26. /// normally, a `Help` or `Version` variant if one of those options is
  27. /// specified, or an error variant if there’s an invalid option or
  28. /// inconsistency within the options after they were parsed.
  29. #[allow(unused_results)]
  30. pub fn getopts<C>(args: C) -> OptionsResult
  31. where C: IntoIterator,
  32. C::Item: AsRef<OsStr>,
  33. {
  34. let mut opts = getopts::Options::new();
  35. // Query options
  36. opts.optmulti("q", "query", "Host name or IP address to query", "HOST");
  37. opts.optmulti("t", "type", "Type of the DNS record being queried (A, MX, NS...)", "TYPE");
  38. opts.optmulti("n", "nameserver", "Address of the nameserver to send packets to", "ADDR");
  39. opts.optmulti("", "class", "Network class of the DNS record being queried (IN, CH, HS)", "CLASS");
  40. // Sending options
  41. opts.optopt ("", "edns", "Whether to OPT in to EDNS (disable, hide, show)", "SETTING");
  42. opts.optopt ("", "txid", "Set the transaction ID to a specific value", "NUMBER");
  43. opts.optopt ("Z", "", "Uncommon protocol tweaks", "TWEAKS");
  44. // Protocol options
  45. opts.optflag("U", "udp", "Use the DNS protocol over UDP");
  46. opts.optflag("T", "tcp", "Use the DNS protocol over TCP");
  47. opts.optflag("S", "tls", "Use the DNS-over-TLS protocol");
  48. opts.optflag("H", "https", "Use the DNS-over-HTTPS protocol");
  49. // Output options
  50. opts.optopt ("", "color", "When to use terminal colors", "WHEN");
  51. opts.optopt ("", "colour", "When to use terminal colours", "WHEN");
  52. opts.optflag("J", "json", "Display the output as JSON");
  53. opts.optflag("", "seconds", "Do not format durations, display them as seconds");
  54. opts.optflag("1", "short", "Short mode: display nothing but the first result");
  55. opts.optflag("", "time", "Print how long the response took to arrive");
  56. // Meta options
  57. opts.optflag("v", "version", "Print version information");
  58. opts.optflag("?", "help", "Print list of command-line options");
  59. let matches = match opts.parse(args) {
  60. Ok(m) => m,
  61. Err(e) => return OptionsResult::InvalidOptionsFormat(e),
  62. };
  63. let uc = UseColours::deduce(&matches);
  64. if matches.opt_present("version") {
  65. OptionsResult::Version(uc)
  66. }
  67. else if matches.opt_present("help") {
  68. OptionsResult::Help(HelpReason::Flag, uc)
  69. }
  70. else {
  71. match Self::deduce(matches) {
  72. Ok(opts) => {
  73. if opts.requests.inputs.domains.is_empty() {
  74. OptionsResult::Help(HelpReason::NoDomains, uc)
  75. }
  76. else {
  77. OptionsResult::Ok(opts)
  78. }
  79. }
  80. Err(e) => {
  81. OptionsResult::InvalidOptions(e)
  82. }
  83. }
  84. }
  85. }
  86. fn deduce(matches: getopts::Matches) -> Result<Self, OptionsError> {
  87. let measure_time = matches.opt_present("time");
  88. let format = OutputFormat::deduce(&matches);
  89. let requests = RequestGenerator::deduce(matches)?;
  90. Ok(Self { requests, measure_time, format })
  91. }
  92. }
  93. impl RequestGenerator {
  94. fn deduce(matches: getopts::Matches) -> Result<Self, OptionsError> {
  95. let edns = UseEDNS::deduce(&matches)?;
  96. let txid_generator = TxidGenerator::deduce(&matches)?;
  97. let protocol_tweaks = ProtocolTweaks::deduce(&matches)?;
  98. let inputs = Inputs::deduce(matches)?;
  99. Ok(Self { inputs, txid_generator, edns, protocol_tweaks })
  100. }
  101. }
  102. impl Inputs {
  103. fn deduce(matches: getopts::Matches) -> Result<Self, OptionsError> {
  104. let mut inputs = Self::default();
  105. inputs.load_transport_types(&matches);
  106. inputs.load_named_args(&matches)?;
  107. inputs.load_free_args(matches)?;
  108. inputs.load_fallbacks();
  109. Ok(inputs)
  110. }
  111. fn load_transport_types(&mut self, matches: &getopts::Matches) {
  112. if matches.opt_present("https") {
  113. self.transport_types.push(TransportType::HTTPS);
  114. }
  115. if matches.opt_present("tls") {
  116. self.transport_types.push(TransportType::TLS);
  117. }
  118. if matches.opt_present("tcp") {
  119. self.transport_types.push(TransportType::TCP);
  120. }
  121. if matches.opt_present("udp") {
  122. self.transport_types.push(TransportType::UDP);
  123. }
  124. }
  125. fn load_named_args(&mut self, matches: &getopts::Matches) -> Result<(), OptionsError> {
  126. for domain in matches.opt_strs("query") {
  127. self.domains.push(domain);
  128. }
  129. for qtype in matches.opt_strs("type") {
  130. self.add_type(&qtype)?;
  131. }
  132. for ns in matches.opt_strs("nameserver") {
  133. self.add_nameserver(&ns)?;
  134. }
  135. for qclass in matches.opt_strs("class") {
  136. self.add_class(&qclass)?;
  137. }
  138. Ok(())
  139. }
  140. fn add_type(&mut self, input: &str) -> Result<(), OptionsError> {
  141. if input == "OPT" {
  142. return Err(OptionsError::QueryTypeOPT);
  143. }
  144. let type_number = find_qtype_number(input)
  145. .or_else(|| find_other_qtype_number(input))
  146. .or_else(|| input.parse().ok());
  147. match type_number {
  148. Some(qtype) => Ok(self.types.push(qtype)),
  149. None => Err(OptionsError::InvalidQueryType(input.into())),
  150. }
  151. }
  152. fn add_nameserver(&mut self, input: &str) -> Result<(), OptionsError> {
  153. self.resolvers.push(Resolver::Specified(input.into()));
  154. Ok(())
  155. }
  156. fn parse_class_name(&self, input: &str) -> Option<QClass> {
  157. match input {
  158. "IN" => Some(QClass::IN),
  159. "CH" => Some(QClass::CH),
  160. "HS" => Some(QClass::HS),
  161. _ => None,
  162. }
  163. }
  164. fn add_class(&mut self, input: &str) -> Result<(), OptionsError> {
  165. let qclass = self.parse_class_name(input)
  166. .or_else(|| input.parse().ok().map(QClass::Other));
  167. match qclass {
  168. Some(class) => Ok(self.classes.push(class)),
  169. None => Err(OptionsError::InvalidQueryClass(input.into())),
  170. }
  171. }
  172. fn load_free_args(&mut self, matches: getopts::Matches) -> Result<(), OptionsError> {
  173. for a in matches.free {
  174. if a.starts_with('@') {
  175. trace!("Got nameserver -> {:?}", &a[1..]);
  176. self.add_nameserver(&a[1..])?;
  177. }
  178. else if a.chars().all(char::is_uppercase) {
  179. if let Some(class) = self.parse_class_name(&a) {
  180. trace!("Got qclass -> {:?}", &a);
  181. self.classes.push(class);
  182. }
  183. else {
  184. trace!("Got qtype -> {:?}", &a);
  185. self.add_type(&a)?;
  186. }
  187. }
  188. else {
  189. trace!("Got domain -> {:?}", &a);
  190. self.domains.push(a);
  191. }
  192. }
  193. Ok(())
  194. }
  195. fn load_fallbacks(&mut self) {
  196. if self.types.is_empty() {
  197. self.types.push(qtype!(A));
  198. }
  199. if self.classes.is_empty() {
  200. self.classes.push(QClass::IN);
  201. }
  202. if self.resolvers.is_empty() {
  203. self.resolvers.push(Resolver::SystemDefault);
  204. }
  205. if self.transport_types.is_empty() {
  206. self.transport_types.push(TransportType::Automatic);
  207. }
  208. }
  209. }
  210. impl TxidGenerator {
  211. fn deduce(matches: &getopts::Matches) -> Result<Self, OptionsError> {
  212. if let Some(starting_txid) = matches.opt_str("txid") {
  213. if let Some(start) = parse_dec_or_hex(&starting_txid) {
  214. Ok(Self::Sequence(start))
  215. }
  216. else {
  217. Err(OptionsError::InvalidTxid(starting_txid))
  218. }
  219. }
  220. else {
  221. Ok(Self::Random)
  222. }
  223. }
  224. }
  225. fn parse_dec_or_hex(input: &str) -> Option<u16> {
  226. if input.starts_with("0x") {
  227. match u16::from_str_radix(&input[2..], 16) {
  228. Ok(num) => {
  229. Some(num)
  230. }
  231. Err(e) => {
  232. warn!("Error parsing hex number: {}", e);
  233. None
  234. }
  235. }
  236. }
  237. else {
  238. match input.parse() {
  239. Ok(num) => {
  240. Some(num)
  241. }
  242. Err(e) => {
  243. warn!("Error parsing number: {}", e);
  244. None
  245. }
  246. }
  247. }
  248. }
  249. impl OutputFormat {
  250. fn deduce(matches: &getopts::Matches) -> Self {
  251. if matches.opt_present("short") {
  252. let summary_format = TextFormat::deduce(matches);
  253. Self::Short(summary_format)
  254. }
  255. else if matches.opt_present("json") {
  256. Self::JSON
  257. }
  258. else {
  259. let use_colours = UseColours::deduce(matches);
  260. let summary_format = TextFormat::deduce(matches);
  261. Self::Text(use_colours, summary_format)
  262. }
  263. }
  264. }
  265. impl UseColours {
  266. fn deduce(matches: &getopts::Matches) -> Self {
  267. match matches.opt_str("color").or_else(|| matches.opt_str("colour")).unwrap_or_default().as_str() {
  268. "automatic" | "auto" | "" => Self::Automatic,
  269. "always" | "yes" => Self::Always,
  270. "never" | "no" => Self::Never,
  271. otherwise => {
  272. warn!("Unknown colour setting {:?}", otherwise);
  273. Self::Automatic
  274. },
  275. }
  276. }
  277. }
  278. impl TextFormat {
  279. fn deduce(matches: &getopts::Matches) -> Self {
  280. let format_durations = ! matches.opt_present("seconds");
  281. Self { format_durations }
  282. }
  283. }
  284. impl UseEDNS {
  285. fn deduce(matches: &getopts::Matches) -> Result<Self, OptionsError> {
  286. if let Some(edns) = matches.opt_str("edns") {
  287. match edns.as_str() {
  288. "disable" | "off" => Ok(Self::Disable),
  289. "hide" => Ok(Self::SendAndHide),
  290. "show" => Ok(Self::SendAndShow),
  291. oh => Err(OptionsError::InvalidEDNS(oh.into())),
  292. }
  293. }
  294. else {
  295. Ok(Self::SendAndHide)
  296. }
  297. }
  298. }
  299. impl ProtocolTweaks {
  300. fn deduce(matches: &getopts::Matches) -> Result<Self, OptionsError> {
  301. let mut tweaks = Self::default();
  302. if let Some(tweak_strs) = matches.opt_str("Z") {
  303. for tweak_str in tweak_strs.split(',') {
  304. match &*tweak_str {
  305. "authentic" => { tweaks.set_authentic_flag = true; },
  306. otherwise => return Err(OptionsError::InvalidTweak(otherwise.into())),
  307. }
  308. }
  309. }
  310. Ok(tweaks)
  311. }
  312. }
  313. /// The result of the `Options::getopts` function.
  314. #[derive(PartialEq, Debug)]
  315. pub enum OptionsResult {
  316. /// The options were parsed successfully.
  317. Ok(Options),
  318. /// There was an error (from `getopts`) parsing the arguments.
  319. InvalidOptionsFormat(getopts::Fail),
  320. /// There was an error with the combination of options the user selected.
  321. InvalidOptions(OptionsError),
  322. /// Can’t run any checks because there’s help to display!
  323. Help(HelpReason, UseColours),
  324. /// One of the arguments was `--version`, to display the version number.
  325. Version(UseColours),
  326. }
  327. /// The reason that help is being displayed. If it’s for the `--help` flag,
  328. /// then we shouldn’t return an error exit status.
  329. #[derive(PartialEq, Debug, Copy, Clone)]
  330. pub enum HelpReason {
  331. /// Help was requested with the `--help` flag.
  332. Flag,
  333. /// There were no domains being queried, so display help instead.
  334. /// Unlike `dig`, we don’t implicitly search for the root domain.
  335. NoDomains,
  336. }
  337. /// Something wrong with the combination of options the user has picked.
  338. #[derive(PartialEq, Debug)]
  339. pub enum OptionsError {
  340. TooManyProtocols,
  341. InvalidEDNS(String),
  342. InvalidQueryType(String),
  343. InvalidQueryClass(String),
  344. InvalidTxid(String),
  345. InvalidTweak(String),
  346. QueryTypeOPT,
  347. }
  348. impl fmt::Display for OptionsError {
  349. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  350. match self {
  351. Self::TooManyProtocols => write!(f, "Too many protocols"),
  352. Self::InvalidEDNS(edns) => write!(f, "Invalid EDNS setting {:?}", edns),
  353. Self::InvalidQueryType(qt) => write!(f, "Invalid query type {:?}", qt),
  354. Self::InvalidQueryClass(qc) => write!(f, "Invalid query class {:?}", qc),
  355. Self::InvalidTxid(txid) => write!(f, "Invalid transaction ID {:?}", txid),
  356. Self::InvalidTweak(tweak) => write!(f, "Invalid protocol tweak {:?}", tweak),
  357. Self::QueryTypeOPT => write!(f, "OPT request is sent by default (see -Z flag)"),
  358. }
  359. }
  360. }
  361. #[cfg(test)]
  362. mod test {
  363. use super::*;
  364. use pretty_assertions::assert_eq;
  365. use dns::record::*;
  366. impl Inputs {
  367. fn fallbacks() -> Self {
  368. Inputs {
  369. domains: vec![ /* No domains by default */ ],
  370. types: vec![ qtype!(A) ],
  371. classes: vec![ QClass::IN ],
  372. resolvers: vec![ Resolver::SystemDefault ],
  373. transport_types: vec![ TransportType::Automatic ],
  374. }
  375. }
  376. }
  377. impl OptionsResult {
  378. fn unwrap(self) -> Options {
  379. match self {
  380. Self::Ok(o) => o,
  381. _ => panic!("{:?}", self),
  382. }
  383. }
  384. }
  385. // help tests
  386. #[test]
  387. fn help() {
  388. assert_eq!(Options::getopts(&[ "--help" ]),
  389. OptionsResult::Help(HelpReason::Flag, UseColours::Automatic));
  390. }
  391. #[test]
  392. fn help_no_colour() {
  393. assert_eq!(Options::getopts(&[ "--help", "--colour=never" ]),
  394. OptionsResult::Help(HelpReason::Flag, UseColours::Never));
  395. }
  396. #[test]
  397. fn version() {
  398. assert_eq!(Options::getopts(&[ "--version" ]),
  399. OptionsResult::Version(UseColours::Automatic));
  400. }
  401. #[test]
  402. fn version_yes_color() {
  403. assert_eq!(Options::getopts(&[ "--version", "--color", "always" ]),
  404. OptionsResult::Version(UseColours::Always));
  405. }
  406. #[test]
  407. fn fail() {
  408. assert_eq!(Options::getopts(&[ "--pear" ]),
  409. OptionsResult::InvalidOptionsFormat(getopts::Fail::UnrecognizedOption("pear".into())));
  410. }
  411. #[test]
  412. fn empty() {
  413. let nothing: Vec<&str> = vec![];
  414. assert_eq!(Options::getopts(nothing),
  415. OptionsResult::Help(HelpReason::NoDomains, UseColours::Automatic));
  416. }
  417. #[test]
  418. fn an_unrelated_argument() {
  419. assert_eq!(Options::getopts(&[ "--time" ]),
  420. OptionsResult::Help(HelpReason::NoDomains, UseColours::Automatic));
  421. }
  422. // query tests
  423. #[test]
  424. fn just_domain() {
  425. let options = Options::getopts(&[ "lookup.dog" ]).unwrap();
  426. assert_eq!(options.requests.inputs, Inputs {
  427. domains: vec![ String::from("lookup.dog") ],
  428. .. Inputs::fallbacks()
  429. });
  430. }
  431. #[test]
  432. fn just_named_domain() {
  433. let options = Options::getopts(&[ "-q", "lookup.dog" ]).unwrap();
  434. assert_eq!(options.requests.inputs, Inputs {
  435. domains: vec![ String::from("lookup.dog") ],
  436. .. Inputs::fallbacks()
  437. });
  438. }
  439. #[test]
  440. fn domain_and_type() {
  441. let options = Options::getopts(&[ "lookup.dog", "SOA" ]).unwrap();
  442. assert_eq!(options.requests.inputs, Inputs {
  443. domains: vec![ String::from("lookup.dog") ],
  444. types: vec![ qtype!(SOA) ],
  445. .. Inputs::fallbacks()
  446. });
  447. }
  448. #[test]
  449. fn domain_and_nameserver() {
  450. let options = Options::getopts(&[ "lookup.dog", "@1.1.1.1" ]).unwrap();
  451. assert_eq!(options.requests.inputs, Inputs {
  452. domains: vec![ String::from("lookup.dog") ],
  453. resolvers: vec![ Resolver::Specified("1.1.1.1".into()) ],
  454. .. Inputs::fallbacks()
  455. });
  456. }
  457. #[test]
  458. fn domain_and_class() {
  459. let options = Options::getopts(&[ "lookup.dog", "CH" ]).unwrap();
  460. assert_eq!(options.requests.inputs, Inputs {
  461. domains: vec![ String::from("lookup.dog") ],
  462. classes: vec![ QClass::CH ],
  463. .. Inputs::fallbacks()
  464. });
  465. }
  466. #[test]
  467. fn all_free() {
  468. let options = Options::getopts(&[ "lookup.dog", "CH", "NS", "@1.1.1.1" ]).unwrap();
  469. assert_eq!(options.requests.inputs, Inputs {
  470. domains: vec![ String::from("lookup.dog") ],
  471. classes: vec![ QClass::CH ],
  472. types: vec![ qtype!(NS) ],
  473. resolvers: vec![ Resolver::Specified("1.1.1.1".into()) ],
  474. .. Inputs::fallbacks()
  475. });
  476. }
  477. #[test]
  478. fn all_parameters() {
  479. let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "CH", "--type", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
  480. assert_eq!(options.requests.inputs, Inputs {
  481. domains: vec![ String::from("lookup.dog") ],
  482. classes: vec![ QClass::CH ],
  483. types: vec![ qtype!(SOA) ],
  484. resolvers: vec![ Resolver::Specified("1.1.1.1".into()) ],
  485. .. Inputs::fallbacks()
  486. });
  487. }
  488. #[test]
  489. fn two_types() {
  490. let options = Options::getopts(&[ "-q", "lookup.dog", "--type", "SRV", "--type", "AAAA" ]).unwrap();
  491. assert_eq!(options.requests.inputs, Inputs {
  492. domains: vec![ String::from("lookup.dog") ],
  493. types: vec![ qtype!(SRV), qtype!(AAAA) ],
  494. .. Inputs::fallbacks()
  495. });
  496. }
  497. #[test]
  498. fn two_classes() {
  499. let options = Options::getopts(&[ "-q", "lookup.dog", "--class", "IN", "--class", "CH" ]).unwrap();
  500. assert_eq!(options.requests.inputs, Inputs {
  501. domains: vec![ String::from("lookup.dog") ],
  502. classes: vec![ QClass::IN, QClass::CH ],
  503. .. Inputs::fallbacks()
  504. });
  505. }
  506. #[test]
  507. fn all_mixed_1() {
  508. let options = Options::getopts(&[ "lookup.dog", "--class", "CH", "SOA", "--nameserver", "1.1.1.1" ]).unwrap();
  509. assert_eq!(options.requests.inputs, Inputs {
  510. domains: vec![ String::from("lookup.dog") ],
  511. classes: vec![ QClass::CH ],
  512. types: vec![ qtype!(SOA) ],
  513. resolvers: vec![ Resolver::Specified("1.1.1.1".into()) ],
  514. .. Inputs::fallbacks()
  515. });
  516. }
  517. #[test]
  518. fn all_mixed_2() {
  519. let options = Options::getopts(&[ "CH", "SOA", "MX", "IN", "-q", "lookup.dog", "--class", "HS" ]).unwrap();
  520. assert_eq!(options.requests.inputs, Inputs {
  521. domains: vec![ String::from("lookup.dog") ],
  522. classes: vec![ QClass::HS, QClass::CH, QClass::IN ],
  523. types: vec![ qtype!(SOA), qtype!(MX) ],
  524. .. Inputs::fallbacks()
  525. });
  526. }
  527. #[test]
  528. fn all_mixed_3() {
  529. let options = Options::getopts(&[ "lookup.dog", "--nameserver", "1.1.1.1", "--nameserver", "1.0.0.1" ]).unwrap();
  530. assert_eq!(options.requests.inputs, Inputs {
  531. domains: vec![ String::from("lookup.dog") ],
  532. resolvers: vec![ Resolver::Specified("1.1.1.1".into()),
  533. Resolver::Specified("1.0.0.1".into()), ],
  534. .. Inputs::fallbacks()
  535. });
  536. }
  537. #[test]
  538. fn explicit_numerics() {
  539. let options = Options::getopts(&[ "11", "--class", "22", "--type", "33" ]).unwrap();
  540. assert_eq!(options.requests.inputs, Inputs {
  541. domains: vec![ String::from("11") ],
  542. classes: vec![ QClass::Other(22) ],
  543. types: vec![ 33 ],
  544. .. Inputs::fallbacks()
  545. });
  546. }
  547. // invalid options tests
  548. #[test]
  549. fn invalid_named_class() {
  550. assert_eq!(Options::getopts(&[ "lookup.dog", "--class", "tubes" ]),
  551. OptionsResult::InvalidOptions(OptionsError::InvalidQueryClass("tubes".into())));
  552. }
  553. #[test]
  554. fn invalid_named_type() {
  555. assert_eq!(Options::getopts(&[ "lookup.dog", "--type", "tubes" ]),
  556. OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("tubes".into())));
  557. }
  558. #[test]
  559. fn invalid_capsword() {
  560. assert_eq!(Options::getopts(&[ "SMH", "lookup.dog" ]),
  561. OptionsResult::InvalidOptions(OptionsError::InvalidQueryType("SMH".into())));
  562. }
  563. #[test]
  564. fn invalid_txid() {
  565. assert_eq!(Options::getopts(&[ "lookup.dog", "--txid=0x10000" ]),
  566. OptionsResult::InvalidOptions(OptionsError::InvalidTxid("0x10000".into())));
  567. }
  568. #[test]
  569. fn opt() {
  570. assert_eq!(Options::getopts(&[ "OPT", "lookup.dog" ]),
  571. OptionsResult::InvalidOptions(OptionsError::QueryTypeOPT));
  572. }
  573. // txid tests
  574. #[test]
  575. fn number_parsing() {
  576. assert_eq!(parse_dec_or_hex("1234"), Some(1234));
  577. assert_eq!(parse_dec_or_hex("0x1234"), Some(0x1234));
  578. assert_eq!(parse_dec_or_hex("0xABcd"), Some(0xABcd));
  579. assert_eq!(parse_dec_or_hex("65536"), None);
  580. assert_eq!(parse_dec_or_hex("0x65536"), None);
  581. assert_eq!(parse_dec_or_hex(""), None);
  582. assert_eq!(parse_dec_or_hex("0x"), None);
  583. }
  584. }