Skip to content

Commit

Permalink
ExtendedCommunities
Browse files Browse the repository at this point in the history
- created parser, eval
- moved stuff from parse phase to eval for all communities
- returning Result for literal conversions (so eval can take care of it)
  • Loading branch information
density215 committed Dec 5, 2023
1 parent cfe9029 commit 64404cc
Show file tree
Hide file tree
Showing 6 changed files with 339 additions and 87 deletions.
110 changes: 82 additions & 28 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1999,35 +1999,28 @@ impl From<&'_ AsnLiteral> for Asn {
//------------ StandardCommunityLiteral --------------------------------------

#[derive(Clone, Debug)]
pub struct StandardCommunityLiteral(pub u16, pub u16);
pub struct StandardCommunityLiteral(pub String);

impl StandardCommunityLiteral {
pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> {
let (input, (asn, value)) = context(
let (input, std_comm) = context(
"Standard Community Literal",
separated_pair(
recognize(separated_pair(
take_while_m_n(1, 5, |ch: char| ch.is_dec_digit()),
tag(":"),
take_while_m_n(1, 5, |ch: char| ch.is_dec_digit()),
),
)),
)(input)?;

let asn = asn.parse::<u16>().map_err(|e| {
nom::Err::Failure(VerboseError::from_external_error(
input,
nom::error::ErrorKind::AlphaNumeric,
e,
))
})?;
// Let routecore doing all the heavy lifting here: Not erroring out
// here on an invalid tag, or too big a AS, because the eval phase of
// the compilation (that uses routecore's `from_str` will error out
// with a way more useful error. Also syntactically it might not be
// wrong to have unknown tags, or too big a AS here.

let value = value.parse::<u16>().map_err(|e| {
nom::Err::Failure(VerboseError::from_external_error(
input,
nom::error::ErrorKind::AlphaNumeric,
e,
))
})?;
Ok((input, Self(asn, value)))
// See tests in bgp_filters.rs for the kind of errors the eval() phase
// returns.
Ok((input, Self(std_comm.to_string())))
}
}

Expand All @@ -2037,18 +2030,74 @@ impl From<&'_ StandardCommunityLiteral> for ShortString {
}
}

impl From<&'_ StandardCommunityLiteral> for Community {
fn from(literal: &StandardCommunityLiteral) -> Self {
Community(
routecore::bgp::communities::StandardCommunity::new(
literal.0.into(),
routecore::bgp::communities::Tag::new(literal.1),
)
.into(),
)
impl TryFrom<&'_ StandardCommunityLiteral> for Community {
type Error = CompileError;

fn try_from(literal: &StandardCommunityLiteral) -> Result<Self, Self::Error> {
let comm = <routecore::bgp::communities::StandardCommunity as str::FromStr>::from_str(
&literal.0
).map_err(
|e| CompileError::from(format!(
"Cannot convert literal '{}' into Extended Community: {e}", literal.0,
)))?;

Ok(Community(routecore::bgp::communities::Community::Standard(comm)))
}
}

//------------ ExtendedCommunity ---------------------------------------------

#[derive(Clone, Debug)]
pub struct ExtendedCommunityLiteral(pub String);

impl ExtendedCommunityLiteral {
pub fn parse(input: &str) -> IResult<&str, Self, VerboseError<&str>> {
let (input, ext_comm) = context(
"Extended Community Literal",
recognize(tuple((
take_while1(|ch: char| ch.is_alpha()),
tag(":"),
take_while1(|ch: char| ch.is_dec_digit()),
tag(":"),
take_while_m_n(1, 5, |ch: char| ch.is_dec_digit()),
))),
)(input)?;

// Let routecore doing all the heavy lifting here: Not erroring out
// here on an invalid tag, or too big a AS or AN, because the eval
// phase of the compilation (that uses routecore's `from_str` will
// error out with a way more useful error. Also syntactically it might
// not be wrong to have unknown tags, or too big a AS or AN here.

// See tests in bgp_filters.rs for the kind of errors the eval() phase
// returns.

Ok((input, Self(ext_comm.to_string())))
}
}

impl From<&'_ ExtendedCommunityLiteral> for ShortString {
fn from(literal: &ExtendedCommunityLiteral) -> Self {
ShortString::from(literal.0.to_string().as_str())
}
}

impl<'a> TryFrom<&'a ExtendedCommunityLiteral> for Community {
type Error = CompileError;

fn try_from(literal: &ExtendedCommunityLiteral) -> Result<Self, Self::Error> {
let comm = <routecore::bgp::communities::ExtendedCommunity as str::FromStr>::from_str(
&literal.0
).map_err(
|e| CompileError::from(format!(
"Cannot convert literal '{}' into Extended Community: {e}", literal.0,
)))?;

Ok(Community(routecore::bgp::communities::Community::Extended(comm)))
}
}


//------------ FloatLiteral --------------------------------------------------

/// A float literal is a sequence of digits with a decimal point.
Expand Down Expand Up @@ -2203,6 +2252,7 @@ pub enum ValueExpr {
IntegerLiteral(IntegerLiteral),
PrefixLengthLiteral(PrefixLengthLiteral),
AsnLiteral(AsnLiteral),
ExtendedCommunityLiteral(ExtendedCommunityLiteral),
StandardCommunityLiteral(StandardCommunityLiteral),
HexLiteral(HexLiteral),
BooleanLit(BooleanLiteral),
Expand All @@ -2222,6 +2272,10 @@ impl ValueExpr {
alt((
map(StringLiteral::parse, ValueExpr::StringLiteral),
map(HexLiteral::parse, ValueExpr::HexLiteral),
map(
ExtendedCommunityLiteral::parse,
ValueExpr::ExtendedCommunityLiteral,
),
map(
StandardCommunityLiteral::parse,
ValueExpr::StandardCommunityLiteral,
Expand Down
11 changes: 10 additions & 1 deletion src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1731,7 +1731,16 @@ impl ast::ValueExpr {
Ok(symbols::Symbol::new_with_value(
std_comm_lit.into(),
SymbolKind::Constant,
TypeValue::Builtin(BuiltinTypeValue::Community(std_comm_lit.into())),
TypeValue::Builtin(BuiltinTypeValue::Community(std_comm_lit.try_into()?)),
vec![],
Token::Constant(None),
))
}
ast::ValueExpr::ExtendedCommunityLiteral(std_comm_lit) => {
Ok(symbols::Symbol::new_with_value(
std_comm_lit.into(),
SymbolKind::Constant,
TypeValue::Builtin(BuiltinTypeValue::Community(std_comm_lit.try_into()?)),
vec![],
Token::Constant(None),
))
Expand Down
24 changes: 22 additions & 2 deletions src/types/builtin/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ mod route {
Type,
Consume,

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 1.71.0, --no-default-features)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 1.71.0)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, 1.71.0, --all-features)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, stable, --no-default-features)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, stable)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, stable, --all-features)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, beta, --no-default-features)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, beta)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, beta, --all-features)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, nightly, --no-default-features)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, nightly)

variant `Consume` is never constructed

Check warning on line 17 in src/types/builtin/tests.rs

View workflow job for this annotation

GitHub Actions / test (ubuntu-latest, nightly, --all-features)

variant `Consume` is never constructed
}
use routecore::bgp::communities::{StandardCommunity, Tag};
use routecore::bgp::communities::{StandardCommunity, Tag, ExtendedCommunity};
use routecore::bgp::{
message::{
nlri::{BasicNlri, Nlri},
Expand Down Expand Up @@ -935,7 +935,7 @@ src_ty.clone().test_type_conversion(arg_ty)"]
#[test]
fn test_standard_community_3() -> Result<(), CompileError> {
init();

let test_value: Community =
Community(routecore::bgp::communities::Community::from(
StandardCommunity::new(
Expand All @@ -952,4 +952,24 @@ src_ty.clone().test_type_conversion(arg_ty)"]

test_consume_method_on_type_value(test_value, "set", res)
}

#[test]
fn test_ext_community_1() -> Result<(), CompileError> {
init();

let test_value: Community =
Community(routecore::bgp::communities::Community::from(
ExtendedCommunity::from_str(
"ro:123:456"
).unwrap(),
));
let res = Community(routecore::bgp::communities::Community::from(
StandardCommunity::new(
routecore::asn::Asn16::from(7500),
Tag::new(3000),
),
));

test_consume_method_on_type_value(test_value, "set", res)
}
}
99 changes: 60 additions & 39 deletions src/types/collections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,42 +202,49 @@ impl Default for ElementTypeValue {
}

// Conversion for Records. Records hold `ElementTypeValue`s, the literals
// provided by the user in a a Roto script need to be converted.
impl From<ValueExpr> for ElementTypeValue {
fn from(value: ValueExpr) -> Self {
// provided by the user in a a Roto script need to be converted. This returns
// a Result because the conversion may fail in the eval() phase, where we have
// syntactically correct structures, but they overflow or have unknown values.
impl TryFrom<ValueExpr> for ElementTypeValue {
type Error = CompileError;

fn try_from(value: ValueExpr) -> Result<Self, Self::Error> {
match value {
ValueExpr::StringLiteral(s_lit) => {
ElementTypeValue::Primitive(s_lit.into())
Ok(ElementTypeValue::Primitive(s_lit.into()))
}
ValueExpr::IntegerLiteral(i_lit) => {
ElementTypeValue::Primitive(i_lit.into())
Ok(ElementTypeValue::Primitive(i_lit.into()))
}
ValueExpr::PrefixLengthLiteral(pl_lit) => {
ElementTypeValue::Primitive(pl_lit.into())
Ok(ElementTypeValue::Primitive(pl_lit.into()))
}
ValueExpr::AsnLiteral(asn_lit) => {
ElementTypeValue::Primitive(asn_lit.into())
Ok(ElementTypeValue::Primitive(asn_lit.into()))
}
ValueExpr::StandardCommunityLiteral(s_comm_lit) => {
ElementTypeValue::Primitive(s_comm_lit.into())
Ok(ElementTypeValue::Primitive(s_comm_lit.try_into()?))
}
ValueExpr::ExtendedCommunityLiteral(e_comm_lit) => {
Ok(ElementTypeValue::Primitive(e_comm_lit.try_into()?))
}
ValueExpr::HexLiteral(hex_lit) => {
ElementTypeValue::Primitive(hex_lit.into())
Ok(ElementTypeValue::Primitive(hex_lit.into()))
}
ValueExpr::BooleanLit(bool_lit) => {
ElementTypeValue::Primitive(bool_lit.into())
Ok(ElementTypeValue::Primitive(bool_lit.into()))
}
ValueExpr::PrefixMatchExpr(_) => todo!(),
ValueExpr::ComputeExpr(_) => todo!(),
ValueExpr::RootMethodCallExpr(_) => todo!(),
ValueExpr::AnonymousRecordExpr(rec) => {
ElementTypeValue::Nested(Box::new(rec.into()))
Ok(ElementTypeValue::Nested(Box::new(rec.try_into()?)))
}
ValueExpr::TypedRecordExpr(rec) => {
ElementTypeValue::Nested(Box::new(rec.into()))
Ok(ElementTypeValue::Nested(Box::new(rec.try_into()?)))
}
ValueExpr::ListExpr(list) => {
ElementTypeValue::Nested(Box::new(list.into()))
Ok(ElementTypeValue::Nested(Box::new(list.try_into()?)))
}
}
}
Expand Down Expand Up @@ -590,15 +597,19 @@ impl RotoType for List {
}
}

impl From<ListValueExpr> for List {
fn from(value: ListValueExpr) -> Self {
List(
value
.values
.iter()
.map(|v| v.clone().into())
.collect::<Vec<_>>(),
)
impl TryFrom<ListValueExpr> for List {
type Error = CompileError;

fn try_from(value: ListValueExpr) -> Result<Self, Self::Error> {
let mut lvs = vec![];
for v in value.values.iter() {
match v.clone().try_into() {
Ok(v) => { lvs.push(v) },
Err(e) => { return Err(e); }
};
}

Ok(List(lvs))
}
}

Expand Down Expand Up @@ -1049,29 +1060,39 @@ impl Serialize for Record {

// Value Expressions that contain a Record parsed as a pair of
// (field_name, value) pairs. This turns it into an actual Record.
impl From<AnonymousRecordValueExpr> for Record {
fn from(value: AnonymousRecordValueExpr) -> Self {
impl TryFrom<AnonymousRecordValueExpr> for Record {
type Error = CompileError;

fn try_from(value: AnonymousRecordValueExpr) -> Result<Self, Self::Error> {
trace!("FROM ANONYMOUS RECORD VALUE EXPR");
let mut kvs: Vec<(ShortString, ElementTypeValue)> = value
.key_values
.iter()
.map(|(s, t)| (s.ident.clone(), t.clone().into()))
.collect::<Vec<_>>();

let mut kvs: Vec<(ShortString, ElementTypeValue)> = vec![];
for (s, t) in value.key_values.iter() {
match ElementTypeValue::try_from(t.clone()) {
Ok(t) => { kvs.push((s.ident.clone(),t)) },
Err(e) => { return Err(e); }
};
}
kvs.sort_by(|a, b| a.0.cmp(&b.0));
Record(kvs)
Ok(Record(kvs))
}
}

impl From<TypedRecordValueExpr> for Record {
fn from(value: TypedRecordValueExpr) -> Self {
impl TryFrom<TypedRecordValueExpr> for Record {
type Error = CompileError;

fn try_from(value: TypedRecordValueExpr) -> Result<Self, Self::Error> {
trace!("FROM TYPED RECORD VALUE EXPR");
Record(
value
.key_values
.iter()
.map(|(s, t)| (s.ident.clone(), t.clone().into()))
.collect::<Vec<_>>(),
)

let mut kvs: Vec<(ShortString, ElementTypeValue)> = vec![];
for (s, t) in value.key_values.iter() {
match ElementTypeValue::try_from(t.clone()) {
Ok(t) => { kvs.push((s.ident.clone(),t)) },
Err(e) => { return Err(e); }
};
}

Ok(Record(kvs))
}
}

Expand Down
Loading

0 comments on commit 64404cc

Please sign in to comment.