use std::convert::TryInto;
use std::fmt;
use std::iter::{once, Once};
use std::str::FromStr;
use std::time::Duration;
use rpsl::names::{AsSet, AutNum, Mntner, RouteSet};
use crate::{error::Error, parse, pipeline::ResponseContent};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Query {
Version,
SetClientId(String),
SetTimeout(Duration),
GetSources,
SetSources(Vec<String>),
UnsetSources,
AsSetMembers(AsSet),
AsSetMembersRecursive(AsSet),
RouteSetMembers(RouteSet),
RouteSetMembersRecursive(RouteSet),
Ipv4Routes(AutNum),
Ipv6Routes(AutNum),
RpslObject(RpslObjectClass, String),
MntBy(Mntner),
Origins(String),
RoutesExact(String),
RoutesLess(String),
RoutesLessEqual(String),
RoutesMore(String),
}
impl Query {
pub(crate) fn cmd(&self) -> String {
match self {
Self::Version => "!v\n".to_owned(),
Self::SetClientId(id) => format!("!n{id}\n"),
Self::SetTimeout(dur) => format!("!t{}\n", dur.as_secs()),
Self::GetSources => "!s-lc\n".to_owned(),
Self::SetSources(sources) => format!("!s{}\n", sources.join(",")),
Self::UnsetSources => "!s-*\n".to_owned(),
Self::AsSetMembers(q) => format!("!i{q}\n"),
Self::AsSetMembersRecursive(q) => format!("!i{q},1\n"),
Self::RouteSetMembers(q) => format!("!i{q}\n"),
Self::RouteSetMembersRecursive(q) => format!("!i{q},1\n"),
Self::Ipv4Routes(q) => format!("!g{q}\n"),
Self::Ipv6Routes(q) => format!("!6{q}\n"),
Self::RpslObject(class, q) => format!("!m{class},{q}\n"),
Self::MntBy(q) => format!("!o{q}\n"),
Self::Origins(q) => format!("!r{q},o\n"),
Self::RoutesExact(q) => format!("!r{q}\n"),
Self::RoutesLess(q) => format!("!r{q},l\n"),
Self::RoutesLessEqual(q) => format!("!r{q},L\n"),
Self::RoutesMore(q) => format!("!r{q},M\n"),
}
}
pub(crate) const fn expect_data(&self) -> bool {
matches!(
self,
Self::Version
| Self::GetSources
| Self::AsSetMembers(_)
| Self::RouteSetMembers(_)
| Self::AsSetMembersRecursive(_)
| Self::RouteSetMembersRecursive(_)
| Self::Ipv4Routes(_)
| Self::Ipv6Routes(_)
| Self::RpslObject(..)
| Self::MntBy(_)
| Self::Origins(_)
| Self::RoutesExact(_)
| Self::RoutesLess(_)
| Self::RoutesLessEqual(_)
| Self::RoutesMore(_)
)
}
pub(crate) fn parse_item<T>(&self, input: &[u8]) -> Result<(usize, ResponseContent<T>), Error>
where
T: FromStr + fmt::Debug,
T::Err: std::error::Error + Send + Sync + 'static,
{
let (_, (consumed, item)) = match self {
_ if !self.expect_data() => parse::noop(input)?,
Self::Version => parse::all(input)?,
Self::RpslObject(..)
| Self::MntBy(_)
| Self::RoutesExact(_)
| Self::RoutesLess(_)
| Self::RoutesLessEqual(_)
| Self::RoutesMore(_) => parse::paragraph(input)?,
_ => parse::word(input)?,
};
let content = item
.try_into()
.map_err(|err| Error::ParseItem(err, consumed))?;
Ok((consumed, content))
}
}
impl IntoIterator for Query {
type Item = Self;
type IntoIter = Once<Self>;
fn into_iter(self) -> Self::IntoIter {
once(self)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::Display)]
#[cfg_attr(test, derive(strum::EnumIter))]
pub enum RpslObjectClass {
#[strum(to_string = "mntner")]
Mntner,
#[strum(to_string = "person")]
Person,
#[strum(to_string = "role")]
Role,
#[strum(to_string = "route")]
Route,
#[strum(to_string = "route6")]
Route6,
#[strum(to_string = "aut-num")]
AutNum,
#[strum(to_string = "inet-rtr")]
InetRtr,
#[strum(to_string = "as-set")]
AsSet,
#[strum(to_string = "route-set")]
RouteSet,
#[strum(to_string = "filter-set")]
FilterSet,
#[strum(to_string = "rtr-set")]
RtrSet,
#[strum(to_string = "peering-set")]
PeeringSet,
}
#[cfg(test)]
#[allow(unknown_lints)]
#[allow(clippy::ignored_unit_patterns)]
#[warn(unknown_lints)]
mod tests {
use super::*;
#[test]
fn query_is_singleton_iterator() {
let q = Query::Version;
let mut iter = q.clone().into_iter();
assert_eq!(iter.next(), Some(q));
assert_eq!(iter.next(), None);
}
mod proptests {
use proptest::{prelude::*, strategy::Union};
use strum::IntoEnumIterator;
use super::*;
impl Arbitrary for RpslObjectClass {
type Parameters = ();
type Strategy = Union<Just<Self>>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
Union::new(Self::iter().map(Just))
}
}
impl Arbitrary for Query {
type Parameters = ();
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with((): Self::Parameters) -> Self::Strategy {
prop_oneof![
Just(Self::Version),
any::<String>().prop_map(Self::SetClientId),
any::<Duration>().prop_map(Self::SetTimeout),
Just(Self::GetSources),
any::<Vec<String>>().prop_map(Self::SetSources),
Just(Self::UnsetSources),
any::<AsSet>().prop_map(Self::AsSetMembers),
any::<AsSet>().prop_map(Self::AsSetMembersRecursive),
any::<RouteSet>().prop_map(Self::RouteSetMembers),
any::<RouteSet>().prop_map(Self::RouteSetMembersRecursive),
any::<AutNum>().prop_map(Self::Ipv4Routes),
any::<AutNum>().prop_map(Self::Ipv6Routes),
any::<(RpslObjectClass, String)>()
.prop_map(|(class, object)| Self::RpslObject(class, object)),
any::<Mntner>().prop_map(Self::MntBy),
any::<String>().prop_map(Self::Origins),
any::<String>().prop_map(Self::RoutesExact),
any::<String>().prop_map(Self::RoutesLess),
any::<String>().prop_map(Self::RoutesLessEqual),
any::<String>().prop_map(Self::RoutesMore),
]
.boxed()
}
}
proptest! {
#[test]
fn cmd_begins_with_bang(q in any::<Query>()) {
assert!(q.cmd().starts_with('!'));
}
#[test]
fn cmd_ends_with_newline(q in any::<Query>()) {
assert!(q.cmd().ends_with('\n'));
}
#[test]
#[allow(unused_must_use)]
fn parse_item_never_panics(q in any::<Query>(), input in any::<Vec<u8>>()) {
q.parse_item::<String>(&input);
}
}
}
}