use std::fmt;
use ip::{Any, Ipv4, Ipv6};
use crate::{
error::{ParseError, ParseResult},
parser::{
debug_construction, impl_from_str, next_into_or, rule_mismatch, ParserRule, TokenPair,
},
primitive::{ParserAfi, Protocol},
};
use super::filter::{self, ExprAfi as FilterExprAfi};
#[cfg(any(test, feature = "arbitrary"))]
use proptest::{arbitrary::ParamsFor, prelude::*};
pub trait ExprAfi: ParserAfi {
type FilterExprAfi: filter::ExprAfi;
const COMPONENTS_EXPR_RULE: ParserRule;
const COMPONENTS_PROTO_TERMS_RULE: ParserRule;
const COMPONENTS_PROTO_TERM_RULE: ParserRule;
}
impl ExprAfi for Ipv4 {
type FilterExprAfi = Self;
const COMPONENTS_EXPR_RULE: ParserRule = ParserRule::components_expr;
const COMPONENTS_PROTO_TERMS_RULE: ParserRule = ParserRule::components_proto_terms;
const COMPONENTS_PROTO_TERM_RULE: ParserRule = ParserRule::components_proto_term;
}
impl ExprAfi for Ipv6 {
type FilterExprAfi = Any;
const COMPONENTS_EXPR_RULE: ParserRule = ParserRule::components6_expr;
const COMPONENTS_PROTO_TERMS_RULE: ParserRule = ParserRule::components6_proto_terms;
const COMPONENTS_PROTO_TERM_RULE: ParserRule = ParserRule::components6_proto_term;
}
#[allow(clippy::module_name_repetitions)]
pub type ComponentsExpr = Expr<Ipv4>;
impl_from_str!(ParserRule::just_components_expr => ComponentsExpr);
#[allow(clippy::module_name_repetitions)]
pub type Components6Expr = Expr<Ipv6>;
impl_from_str!(ParserRule::just_components6_expr => Components6Expr);
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Expr<A: ExprAfi> {
atomic: bool,
filter: Option<filter::Expr<A::FilterExprAfi>>,
proto_terms: ProtocolTerms<A>,
}
impl<A: ExprAfi> TryFrom<TokenPair<'_>> for Expr<A> {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => Expr);
match pair.as_rule() {
rule if rule == A::COMPONENTS_EXPR_RULE => {
let (mut atomic, mut filter, mut proto_terms) =
(false, None, ProtocolTerms::default());
for inner_pair in pair.into_inner() {
match inner_pair.as_rule() {
ParserRule::atomic => {
atomic = true;
}
rule if A::FilterExprAfi::match_filter_expr_rule(rule) => {
filter = Some(inner_pair.try_into()?);
}
rule if rule == A::COMPONENTS_PROTO_TERMS_RULE => {
proto_terms = inner_pair.try_into()?;
}
_ => {
return Err(
rule_mismatch!(inner_pair => "components expression element"),
)
}
}
}
Ok(Self {
atomic,
filter,
proto_terms,
})
}
_ => Err(rule_mismatch!(pair => "components expression")),
}
}
}
impl<A: ExprAfi> fmt::Display for Expr<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut statements = Vec::new();
if self.atomic {
statements.push("ATOMIC".to_string());
}
if let Some(filter_expr) = &self.filter {
statements.push(filter_expr.to_string());
}
statements.push(self.proto_terms.to_string());
statements.join(" ").fmt(f)
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<A> Arbitrary for Expr<A>
where
A: ExprAfi + 'static,
A::Address: Arbitrary,
A::FilterExprAfi: 'static,
<A::FilterExprAfi as ip::AfiClass>::Prefix: Arbitrary,
<<A::FilterExprAfi as ip::AfiClass>::Prefix as Arbitrary>::Parameters: Clone,
<<A::FilterExprAfi as ip::AfiClass>::Prefix as ip::traits::Prefix>::Length: AsRef<u8>,
<A::FilterExprAfi as ip::AfiClass>::PrefixLength: AsRef<u8>,
{
type Parameters = (
ParamsFor<Option<filter::Expr<A::FilterExprAfi>>>,
ParamsFor<ProtocolTerms<A>>,
);
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
(
any::<bool>(),
any_with::<Option<filter::Expr<A::FilterExprAfi>>>(params.0),
any_with::<ProtocolTerms<A>>(params.1),
)
.prop_map(|(atomic, filter, proto_terms)| Self {
atomic,
filter,
proto_terms,
})
.boxed()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ProtocolTerms<A: ExprAfi>(Vec<ProtocolTerm<A>>);
impl<A: ExprAfi> Default for ProtocolTerms<A> {
fn default() -> Self {
Self(Vec::default())
}
}
impl<A: ExprAfi> TryFrom<TokenPair<'_>> for ProtocolTerms<A> {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => ProtocolTerms);
match pair.as_rule() {
rule if rule == A::COMPONENTS_PROTO_TERMS_RULE => Ok(Self(
pair.into_inner()
.map(ProtocolTerm::try_from)
.collect::<ParseResult<_>>()?,
)),
_ => Err(rule_mismatch!(pair => "components expression protocol terms")),
}
}
}
impl<A: ExprAfi> fmt::Display for ProtocolTerms<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0
.iter()
.map(ProtocolTerm::to_string)
.collect::<Vec<_>>()
.join(" ")
.fmt(f)
}
}
impl<A: ExprAfi> FromIterator<ProtocolTerm<A>> for ProtocolTerms<A> {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = ProtocolTerm<A>>,
{
Self(iter.into_iter().collect())
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<A> Arbitrary for ProtocolTerms<A>
where
A: ExprAfi + 'static,
A::FilterExprAfi: 'static,
<A::FilterExprAfi as ip::AfiClass>::Prefix: Arbitrary,
<<A::FilterExprAfi as ip::AfiClass>::Prefix as Arbitrary>::Parameters: Clone,
<<A::FilterExprAfi as ip::AfiClass>::Prefix as ip::traits::Prefix>::Length: AsRef<u8>,
<A::FilterExprAfi as ip::AfiClass>::PrefixLength: AsRef<u8>,
{
type Parameters = ParamsFor<ProtocolTerm<A>>;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
proptest::collection::vec(any_with::<ProtocolTerm<A>>(params), 0..4)
.prop_map(Self)
.boxed()
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct ProtocolTerm<A: ExprAfi> {
protocol: Protocol,
filter: filter::Expr<A::FilterExprAfi>,
}
impl<A: ExprAfi> TryFrom<TokenPair<'_>> for ProtocolTerm<A> {
type Error = ParseError;
fn try_from(pair: TokenPair<'_>) -> ParseResult<Self> {
debug_construction!(pair => ProtocolTerm);
match pair.as_rule() {
rule if rule == A::COMPONENTS_PROTO_TERM_RULE => {
let mut pairs = pair.into_inner();
let protocol = next_into_or!(pairs => "failed to get protocol name")?;
let filter = next_into_or!(pairs => "failed to get filter expression")?;
Ok(Self { protocol, filter })
}
_ => Err(rule_mismatch!(pair => "components protocol term")),
}
}
}
impl<A: ExprAfi> fmt::Display for ProtocolTerm<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "protocol {} {}", self.protocol, self.filter)
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl<A> Arbitrary for ProtocolTerm<A>
where
A: ExprAfi,
A::FilterExprAfi: 'static,
<A::FilterExprAfi as ip::AfiClass>::Prefix: Arbitrary,
<<A::FilterExprAfi as ip::AfiClass>::Prefix as Arbitrary>::Parameters: Clone,
<<A::FilterExprAfi as ip::AfiClass>::Prefix as ip::traits::Prefix>::Length: AsRef<u8>,
<A::FilterExprAfi as ip::AfiClass>::PrefixLength: AsRef<u8>,
{
type Parameters = ParamsFor<filter::Expr<A::FilterExprAfi>>;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
(
any::<Protocol>(),
any_with::<filter::Expr<A::FilterExprAfi>>(params),
)
.prop_map(|(protocol, filter)| Self { protocol, filter })
.boxed()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{compare_ast, display_fmt_parses};
display_fmt_parses! {
ComponentsExpr,
Components6Expr,
}
compare_ast! {
ComponentsExpr {
rfc2622_fig29_route_example1: "<^AS2>" => {
ComponentsExpr {
atomic: false,
filter: Some("<^AS2>".parse().unwrap()),
proto_terms: ProtocolTerms::default(),
}
}
rfc2622_fig29_route_example2: "\
protocol BGP4 {128.8.0.0/16^+}
protocol OSPF {128.9.0.0/16^+}" => {
ComponentsExpr {
atomic: false,
filter: None,
proto_terms: vec![
ProtocolTerm {
protocol: Protocol::Bgp4,
filter: "{128.8.0.0/16^+}".parse().unwrap(),
},
ProtocolTerm {
protocol: Protocol::Ospf,
filter: "{128.9.0.0/16^+}".parse().unwrap(),
},
].into_iter().collect(),
}
}
rfc2622_fig30_route_example1: "{128.8.0.0/15^-}" => {
ComponentsExpr {
atomic: false,
filter: Some("{128.8.0.0/15^-}".parse().unwrap()),
proto_terms: ProtocolTerms::default(),
}
}
rfc2622_fig33_route_example: "{128.8.0.0/16, 128.9.0.0/16}" => {
ComponentsExpr {
atomic: false,
filter: Some("{128.8.0.0/16, 128.9.0.0/16}".parse().unwrap()),
proto_terms: ProtocolTerms::default(),
}
}
regression1: "" => {
ComponentsExpr {
atomic: false,
filter: None,
proto_terms: ProtocolTerms::default(),
}
}
}
}
compare_ast! {
Components6Expr {
regression1: "" => {
Components6Expr {
atomic: false,
filter: None,
proto_terms: ProtocolTerms::default(),
}
}
}
}
}