From f5a9bca084153e7f84f471474c10b7eeb2f31ea5 Mon Sep 17 00:00:00 2001 From: Marvin Vogt Date: Sun, 29 Sep 2024 12:49:33 +0000 Subject: [PATCH] Move multiline attribute value parsing out of attribute parser --- src/parser/component.rs | 107 +++++++++++++++++++++------------------- src/parser/main.rs | 2 +- 2 files changed, 57 insertions(+), 52 deletions(-) diff --git a/src/parser/component.rs b/src/parser/component.rs index 0f09726..bd80aed 100644 --- a/src/parser/component.rs +++ b/src/parser/component.rs @@ -2,8 +2,8 @@ use std::iter::once; use winnow::{ ascii::{newline, space0}, - combinator::{delimited, peek, preceded, repeat, separated_pair, terminated}, - error::{ContextError, ParserError}, + combinator::{delimited, opt, preceded, repeat, separated_pair, terminated}, + error::ParserError, token::{one_of, take_while}, PResult, Parser, }; @@ -22,28 +22,14 @@ pub fn server_message<'s>(input: &mut &'s str) -> PResult<&'s str> { .parse_next(input) } -// A RPSL attribute consisting of a name and one or more values. -// The name is followed by a colon and optional spaces. -// Single value attributes are limited to one line, while multi value attributes span over multiple lines. -pub fn attribute<'s>(input: &mut &'s str) -> PResult> { - let (name, first_value) = separated_pair( - attribute_name(), - (':', space0), - terminated(attribute_value(), newline), - ) - .parse_next(input)?; - - if peek(continuation_char::()) - .parse_next(input) - .is_ok() - { - let continuation_values: Vec<&str> = - repeat(1.., continuation_line(attribute_value())).parse_next(input)?; - let value = Value::unchecked_multi(once(first_value).chain(continuation_values)); - return Ok(Attribute::new(name, value)); - } - - Ok(Attribute::new(name, Value::unchecked_single(first_value))) +// Generate an attribute parser. +// The attributes name and value are separated by a colon and optional spaces. +pub fn attribute<'s, E>() -> impl Parser<&'s str, Attribute<'s>, E> +where + E: ParserError<&'s str>, +{ + separated_pair(attribute_name(), (':', space0), attribute_value_opt_multi()) + .map(|(name, value)| Attribute::new(name, value)) } /// Generate an attribute value parser that parses an ASCII sequence of letters, @@ -66,17 +52,40 @@ fn attribute_value<'s, E>() -> impl Parser<&'s str, &'s str, E> where E: ParserError<&'s str>, { - take_while(0.., |c| Value::validate_char(c).is_ok()) + terminated( + take_while(0.., |c| Value::validate_char(c).is_ok()), + newline, + ) } -/// Generate a parser that extends an attributes value over multiple lines, -/// where each value is prefixed with a continuation character. -fn continuation_line<'s, P, E>(value_parser: P) -> impl Parser<&'s str, &'s str, E> +/// Generate a parser for continuation values. +fn attribute_value_continuation<'s, P, E>(value_parser: P) -> impl Parser<&'s str, Vec<&'s str>, E> where P: Parser<&'s str, &'s str, E>, E: ParserError<&'s str>, { - delimited(continuation_char(), preceded(space0, value_parser), newline) + repeat( + 1.., + preceded(continuation_char(), preceded(space0, value_parser)), + ) +} + +/// Generate an attribute value parser that optionally parses continuation lines. +fn attribute_value_opt_multi<'s, E>() -> impl Parser<&'s str, Value<'s>, E> +where + E: ParserError<&'s str>, +{ + ( + attribute_value(), + opt(attribute_value_continuation(attribute_value())), + ) + .map(|(first_value, continuation)| { + if let Some(continuation_values) = continuation { + Value::unchecked_multi(once(first_value).chain(continuation_values)) + } else { + Value::unchecked_single(first_value) + } + }) } /// Generate a parser for a single continuation character. @@ -91,6 +100,7 @@ where mod tests { use proptest::prelude::*; use rstest::*; + use winnow::error::ContextError; use super::*; @@ -131,7 +141,7 @@ mod tests { #[case] expected: Attribute, #[case] remaining: &str, ) { - let parsed = attribute(given).unwrap(); + let parsed = attribute::().parse_next(given).unwrap(); assert_eq!(parsed, expected); assert_eq!(*given, remaining); } @@ -159,7 +169,7 @@ mod tests { #[case] expected: Attribute, #[case] remaining: &str, ) { - let parsed = attribute(given).unwrap(); + let parsed = attribute::().parse_next(given).unwrap(); assert_eq!(parsed, expected); assert_eq!(*given, remaining); } @@ -207,27 +217,22 @@ mod tests { #[case( &mut "This is an example remark\n", "This is an example remark", - "\n" + "" )] #[case( &mut "Concerning abuse and spam ... mailto: abuse@asn.net\n", "Concerning abuse and spam ... mailto: abuse@asn.net", - "\n" + "" )] #[case( &mut "+49 176 07071964\n", "+49 176 07071964", - "\n" + "" )] #[case( &mut "* Equinix FR5, Kleyerstr, Frankfurt am Main\n", "* Equinix FR5, Kleyerstr, Frankfurt am Main", - "\n" - )] - #[case( - &mut "\n", - "", - "\n" + "" )] fn attribute_value_valid( #[case] given: &mut &str, @@ -241,38 +246,38 @@ mod tests { } proptest! { - /// Any non extended ASCII is not returned by the value parser. + /// Parsing any non extended ASCII returns an error. #[test] - fn attribute_value_non_extended_ascii_not_parsed(s in r"[^\x00-\xFF]+") { + fn attribute_value_non_extended_ascii_is_err(s in r"[^\x00-\xFF]+") { let mut parser = attribute_value::(); - let parsed = parser.parse_next(&mut s.as_str()).unwrap(); - prop_assert_eq!(parsed, ""); + assert!(parser.parse_next(&mut s.as_str()).is_err()); } } #[rstest] #[case( &mut " continuation value prefixed by a space\n", - "continuation value prefixed by a space", + vec!["continuation value prefixed by a space"], "" )] #[case( &mut "\t continuation value prefixed by a tab\n", - "continuation value prefixed by a tab", + vec!["continuation value prefixed by a tab"], "" )] #[case( &mut "+ continuation value prefixed by a plus\n", - "continuation value prefixed by a plus", + vec!["continuation value prefixed by a plus"], "" )] - fn continuation_line_valid( + fn attribute_value_continuation_valid( #[case] given: &mut &str, - #[case] expected: &str, + #[case] expected: Vec<&str>, #[case] remaining: &str, ) { - let mut parser = continuation_line::<_, ContextError>(attribute_value()); - let parsed = parser.parse_next(given).unwrap(); + let value_parser = attribute_value::(); + let mut continuation_parser = attribute_value_continuation::<_, ContextError>(value_parser); + let parsed = continuation_parser.parse_next(given).unwrap(); assert_eq!(parsed, expected); assert_eq!(*given, remaining); } diff --git a/src/parser/main.rs b/src/parser/main.rs index f65ff4d..afc2efe 100644 --- a/src/parser/main.rs +++ b/src/parser/main.rs @@ -12,7 +12,7 @@ use crate::{Object, ParseError}; /// As per [RFC 2622](https://datatracker.ietf.org/doc/html/rfc2622#section-2), an RPSL object /// is textually represented as a list of attribute-value pairs that ends when a blank line is encountered. fn object_block<'s>(input: &mut &'s str) -> PResult> { - let (attributes, source) = terminated(repeat(1.., component::attribute), newline) + let (attributes, source) = terminated(repeat(1.., component::attribute()), newline) .with_taken() .parse_next(input)?; Ok(Object::from_parsed(source, attributes))