1use std::convert::TryInto;
2use std::fmt;
3use std::iter::{once, Once};
4use std::str::FromStr;
5use std::time::Duration;
6
7use rpsl::names::{AsSet, AutNum, Mntner, RouteSet};
8
9use crate::{error::Error, parse, pipeline::ResponseContent};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
14pub enum Query {
15 Version,
17 SetClientId(String),
23 SetTimeout(Duration),
29 GetSources,
31 SetSources(Vec<String>),
33 UnsetSources,
35 AsSetMembers(AsSet),
37 AsSetMembersRecursive(AsSet),
40 RouteSetMembers(RouteSet),
42 RouteSetMembersRecursive(RouteSet),
45 Ipv4Routes(AutNum),
48 Ipv6Routes(AutNum),
51 RpslObject(RpslObjectClass, String),
54 MntBy(Mntner),
57 Origins(String),
60 RoutesExact(String),
63 RoutesLess(String),
66 RoutesLessEqual(String),
69 RoutesMore(String),
72}
73
74impl Query {
75 pub(crate) fn cmd(&self) -> String {
76 match self {
77 Self::Version => "!v\n".to_owned(),
78 Self::SetClientId(id) => format!("!n{id}\n"),
79 Self::SetTimeout(dur) => format!("!t{}\n", dur.as_secs()),
80 Self::GetSources => "!s-lc\n".to_owned(),
81 Self::SetSources(sources) => format!("!s{}\n", sources.join(",")),
82 Self::UnsetSources => "!s-*\n".to_owned(),
83 Self::AsSetMembers(q) => format!("!i{q}\n"),
84 Self::AsSetMembersRecursive(q) => format!("!i{q},1\n"),
85 Self::RouteSetMembers(q) => format!("!i{q}\n"),
86 Self::RouteSetMembersRecursive(q) => format!("!i{q},1\n"),
87 Self::Ipv4Routes(q) => format!("!g{q}\n"),
88 Self::Ipv6Routes(q) => format!("!6{q}\n"),
89 Self::RpslObject(class, q) => format!("!m{class},{q}\n"),
90 Self::MntBy(q) => format!("!o{q}\n"),
91 Self::Origins(q) => format!("!r{q},o\n"),
92 Self::RoutesExact(q) => format!("!r{q}\n"),
93 Self::RoutesLess(q) => format!("!r{q},l\n"),
94 Self::RoutesLessEqual(q) => format!("!r{q},L\n"),
95 Self::RoutesMore(q) => format!("!r{q},M\n"),
96 }
97 }
98
99 pub(crate) const fn expect_data(&self) -> bool {
100 matches!(
101 self,
102 Self::Version
103 | Self::GetSources
104 | Self::AsSetMembers(_)
105 | Self::RouteSetMembers(_)
106 | Self::AsSetMembersRecursive(_)
107 | Self::RouteSetMembersRecursive(_)
108 | Self::Ipv4Routes(_)
109 | Self::Ipv6Routes(_)
110 | Self::RpslObject(..)
111 | Self::MntBy(_)
112 | Self::Origins(_)
113 | Self::RoutesExact(_)
114 | Self::RoutesLess(_)
115 | Self::RoutesLessEqual(_)
116 | Self::RoutesMore(_)
117 )
118 }
119
120 pub(crate) fn parse_item<T>(&self, input: &[u8]) -> Result<(usize, ResponseContent<T>), Error>
121 where
122 T: FromStr + fmt::Debug,
123 T::Err: std::error::Error + Send + Sync + 'static,
124 {
125 let (_, (consumed, item)) = match self {
126 _ if !self.expect_data() => parse::noop(input)?,
127 Self::Version => parse::all(input)?,
128 Self::RpslObject(..)
129 | Self::MntBy(_)
130 | Self::RoutesExact(_)
131 | Self::RoutesLess(_)
132 | Self::RoutesLessEqual(_)
133 | Self::RoutesMore(_) => parse::paragraph(input)?,
134 _ => parse::word(input)?,
135 };
136 let content = item
137 .try_into()
138 .map_err(|err| Error::ParseItem(err, consumed))?;
139 Ok((consumed, content))
140 }
141}
142
143impl IntoIterator for Query {
144 type Item = Self;
145 type IntoIter = Once<Self>;
146 fn into_iter(self) -> Self::IntoIter {
147 once(self)
148 }
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)]
153#[cfg_attr(test, derive(strum::EnumIter))]
154pub enum RpslObjectClass {
155 #[strum(to_string = "mntner")]
157 Mntner,
158 #[strum(to_string = "person")]
160 Person,
161 #[strum(to_string = "role")]
163 Role,
164 #[strum(to_string = "route")]
166 Route,
167 #[strum(to_string = "route6")]
169 Route6,
170 #[strum(to_string = "aut-num")]
172 AutNum,
173 #[strum(to_string = "inet-rtr")]
175 InetRtr,
176 #[strum(to_string = "as-set")]
178 AsSet,
179 #[strum(to_string = "route-set")]
181 RouteSet,
182 #[strum(to_string = "filter-set")]
184 FilterSet,
185 #[strum(to_string = "rtr-set")]
187 RtrSet,
188 #[strum(to_string = "peering-set")]
190 PeeringSet,
191}
192
193#[cfg(test)]
194#[allow(unknown_lints)]
196#[allow(clippy::ignored_unit_patterns)]
197#[warn(unknown_lints)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn query_is_singleton_iterator() {
203 let q = Query::Version;
204 let mut iter = q.clone().into_iter();
205 assert_eq!(iter.next(), Some(q));
206 assert_eq!(iter.next(), None);
207 }
208
209 mod proptests {
210 use proptest::{prelude::*, strategy::Union};
211 use strum::IntoEnumIterator;
212
213 use super::*;
214
215 impl Arbitrary for RpslObjectClass {
216 type Parameters = ();
217 type Strategy = Union<Just<Self>>;
218 fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
219 Union::new(Self::iter().map(Just))
220 }
221 }
222
223 impl Arbitrary for Query {
224 type Parameters = ();
225 type Strategy = BoxedStrategy<Self>;
226
227 fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
228 prop_oneof![
229 Just(Self::Version),
230 any::<String>().prop_map(Self::SetClientId),
231 any::<Duration>().prop_map(Self::SetTimeout),
232 Just(Self::GetSources),
233 any::<Vec<String>>().prop_map(Self::SetSources),
234 Just(Self::UnsetSources),
235 any::<AsSet>().prop_map(Self::AsSetMembers),
236 any::<AsSet>().prop_map(Self::AsSetMembersRecursive),
237 any::<RouteSet>().prop_map(Self::RouteSetMembers),
238 any::<RouteSet>().prop_map(Self::RouteSetMembersRecursive),
239 any::<AutNum>().prop_map(Self::Ipv4Routes),
240 any::<AutNum>().prop_map(Self::Ipv6Routes),
241 any::<(RpslObjectClass, String)>()
242 .prop_map(|(class, object)| Self::RpslObject(class, object)),
243 any::<Mntner>().prop_map(Self::MntBy),
244 any::<String>().prop_map(Self::Origins),
245 any::<String>().prop_map(Self::RoutesExact),
246 any::<String>().prop_map(Self::RoutesLess),
247 any::<String>().prop_map(Self::RoutesLessEqual),
248 any::<String>().prop_map(Self::RoutesMore),
249 ]
250 .boxed()
251 }
252 }
253
254 proptest! {
255 #[test]
256 fn cmd_begins_with_bang(q in any::<Query>()) {
257 assert!(q.cmd().starts_with('!'));
258 }
259
260 #[test]
261 fn cmd_ends_with_newline(q in any::<Query>()) {
262 assert!(q.cmd().ends_with('\n'));
263 }
264
265 #[test]
266 #[allow(unused_must_use)]
267 fn parse_item_never_panics(q in any::<Query>(), input in any::<Vec<u8>>()) {
268 q.parse_item::<String>(&input);
269 }
270 }
271 }
272}