rpsl_parser/parser/
main.rs

1use super::component;
2use crate::rpsl;
3use nom::{
4    branch::alt,
5    bytes::complete::tag,
6    character::complete::multispace0,
7    combinator::all_consuming,
8    error::Error,
9    multi::{many0, many1},
10    sequence::delimited,
11    Finish,
12};
13
14/// Parse a string containing a RPSL object.
15///
16/// # Errors
17/// Returns nom `Error` if any error occurs during parsing.
18///
19/// # Examples
20/// ```
21/// # use rpsl_parser::{parse_rpsl_object, rpsl};
22/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
23/// let role_acme = "
24/// role:        ACME Company
25/// address:     Packet Street 6
26/// address:     128 Series of Tubes
27/// address:     Internet
28/// email:       rpsl-parser@github.com
29/// nic-hdl:     RPSL1-RIPE
30/// source:      RIPE
31/// ";
32/// let parsed_attributes = parse_rpsl_object(role_acme)?;
33/// assert_eq!(
34///     parsed_attributes,
35///     rpsl::Object::new(vec![
36///         rpsl::Attribute::new("role".to_string(), vec![Some("ACME Company".to_string())]),
37///         rpsl::Attribute::new(
38///             "address".to_string(),
39///             vec![Some("Packet Street 6".to_string())],
40///         ),
41///         rpsl::Attribute::new(
42///             "address".to_string(),
43///             vec![Some("128 Series of Tubes".to_string())],
44///         ),
45///         rpsl::Attribute::new("address".to_string(), vec![Some("Internet".to_string())]),
46///         rpsl::Attribute::new(
47///             "email".to_string(),
48///             vec![Some("rpsl-parser@github.com".to_string())],
49///         ),
50///         rpsl::Attribute::new("nic-hdl".to_string(), vec![Some("RPSL1-RIPE".to_string())]),
51///         rpsl::Attribute::new("source".to_string(), vec![Some("RIPE".to_string())]),
52///     ])
53/// );
54/// # Ok(())
55/// # }
56/// ```
57///
58/// Values spread over multiple lines can be parsed too.
59/// ```
60/// # use rpsl_parser::{parse_rpsl_object, rpsl};
61/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
62/// let multi_value = "
63/// remarks:     Value 1
64///              Value 2
65/// ";
66/// assert_eq!(
67///     parse_rpsl_object(multi_value)?,
68///     rpsl::Object::new(vec![rpsl::Attribute::new(
69///         "remarks".to_string(),
70///         vec![Some("Value 1".to_string()), Some("Value 2".to_string())]
71///     ),])
72/// );
73/// # Ok(())
74/// # }
75/// ```
76///
77/// Empty values are valid RPSL and are represented as `None`.
78/// ```
79/// # use rpsl_parser::{parse_rpsl_object, rpsl};
80/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
81/// let empty_value = "
82/// as-name:     REMARKABLE
83/// remarks:
84/// remarks:     ^^^^^^^^^^ nothing here
85/// ";
86/// assert_eq!(
87///     parse_rpsl_object(empty_value)?,
88///     rpsl::Object::new(vec![
89///         rpsl::Attribute::new("as-name".to_string(), vec![Some("REMARKABLE".to_string())]),
90///         rpsl::Attribute::new("remarks".to_string(), vec![None]),
91///         rpsl::Attribute::new(
92///             "remarks".to_string(),
93///             vec![Some("^^^^^^^^^^ nothing here".to_string())]
94///         ),
95///     ])
96/// );
97/// # Ok(())
98/// # }
99/// ```
100///
101/// The same goes for values containing only whitespace.
102/// Since whitespace to the left of a value is trimmed, they are equivalent to an empty value.
103///
104/// ```
105/// # use rpsl_parser::{parse_rpsl_object, rpsl};
106/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
107/// let whitespace_value = "
108/// as-name:     REMARKABLE
109/// remarks:               
110/// remarks:     ^^^^^^^^^^ nothing but hot air
111/// ";
112/// assert_eq!(
113///     parse_rpsl_object(whitespace_value)?,
114///     rpsl::Object::new(vec![
115///         rpsl::Attribute::new("as-name".to_string(), vec![Some("REMARKABLE".to_string())]),
116///         rpsl::Attribute::new("remarks".to_string(), vec![None]),
117///         rpsl::Attribute::new(
118///             "remarks".to_string(),
119///             vec![Some("^^^^^^^^^^ nothing but hot air".to_string())]
120///         ),
121///     ])
122/// );
123/// # Ok(())
124/// # }
125/// ```
126pub fn parse_rpsl_object(rpsl: &str) -> Result<rpsl::Object, Error<&str>> {
127    let (_, object) = all_consuming(delimited(
128        multispace0,
129        many1(component::attribute),
130        multispace0,
131    ))(rpsl)
132    .finish()?;
133
134    Ok(rpsl::Object::from(object))
135}
136
137/// Parse a string containing a whois server response into a vector of RPSL objects.
138/// # Errors
139/// Returns nom `Error` if any error occurs during parsing.
140///
141/// # Examples
142/// ```
143/// # use rpsl_parser::{parse_whois_server_response, rpsl};
144/// # fn main() -> Result<(), nom::error::Error<&'static str>> {
145/// let whois_response = "
146/// ASNumber:       32934
147/// ASName:         FACEBOOK
148/// ASHandle:       AS32934
149/// RegDate:        2004-08-24
150/// Updated:        2012-02-24
151/// Comment:        Please send abuse reports to abuse@facebook.com
152/// Ref:            https://rdap.arin.net/registry/autnum/32934
153///
154///
155/// OrgName:        Facebook, Inc.
156/// OrgId:          THEFA-3
157/// Address:        1601 Willow Rd.
158/// City:           Menlo Park
159/// StateProv:      CA
160/// PostalCode:     94025
161/// Country:        US
162/// RegDate:        2004-08-11
163/// Updated:        2012-04-17
164/// Ref:            https://rdap.arin.net/registry/entity/THEFA-3
165/// ";
166/// let parsed_attributes = parse_whois_server_response(whois_response)?;
167/// assert_eq!(
168///     parsed_attributes,
169///     rpsl::ObjectCollection::new(vec![
170///         rpsl::Object::new(vec![
171///             rpsl::Attribute::new("ASNumber".to_string(), vec![Some("32934".to_string())]),
172///             rpsl::Attribute::new("ASName".to_string(), vec![Some("FACEBOOK".to_string())]),
173///             rpsl::Attribute::new("ASHandle".to_string(), vec![Some("AS32934".to_string())]),
174///             rpsl::Attribute::new("RegDate".to_string(), vec![Some("2004-08-24".to_string())]),
175///             rpsl::Attribute::new("Updated".to_string(), vec![Some("2012-02-24".to_string())]),
176///             rpsl::Attribute::new("Comment".to_string(), vec![Some("Please send abuse reports to abuse@facebook.com".to_string())]),
177///             rpsl::Attribute::new("Ref".to_string(), vec![Some("https://rdap.arin.net/registry/autnum/32934".to_string())]),
178///         ]),
179///         rpsl::Object::new(vec![
180///             rpsl::Attribute::new("OrgName".to_string(), vec![Some("Facebook, Inc.".to_string())]),
181///             rpsl::Attribute::new("OrgId".to_string(), vec![Some("THEFA-3".to_string())]),
182///             rpsl::Attribute::new("Address".to_string(), vec![Some("1601 Willow Rd.".to_string())]),
183///             rpsl::Attribute::new("City".to_string(), vec![Some("Menlo Park".to_string())]),
184///             rpsl::Attribute::new("StateProv".to_string(), vec![Some("CA".to_string())]),
185///             rpsl::Attribute::new("PostalCode".to_string(), vec![Some("94025".to_string())]),
186///             rpsl::Attribute::new("Country".to_string(), vec![Some("US".to_string())]),
187///             rpsl::Attribute::new("RegDate".to_string(), vec![Some("2004-08-11".to_string())]),
188///             rpsl::Attribute::new("Updated".to_string(), vec![Some("2012-04-17".to_string())]),
189///             rpsl::Attribute::new("Ref".to_string(), vec![Some("https://rdap.arin.net/registry/entity/THEFA-3".to_string())]),
190///        ]),
191///    ])
192/// );
193/// # Ok(())
194/// # }
195/// ```     
196pub fn parse_whois_server_response(response: &str) -> Result<rpsl::ObjectCollection, Error<&str>> {
197    let rpsl_object = many1(component::attribute);
198
199    let (_, objects) = all_consuming(many1(delimited(
200        many0(alt((component::server_message, tag("\n")))),
201        rpsl_object,
202        many0(alt((component::server_message, tag("\n")))), // TODO: DRY
203    )))(response)
204    .finish()?;
205
206    Ok(rpsl::ObjectCollection::from(objects))
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use crate::rpsl;
213
214    #[test]
215    fn parse_valid_rpsl_object() {
216        let rpsl = concat!(
217            "\n",
218            "role:           Twelve99 Routing Registry\n",
219            "remarks:\n",
220            "remarks:        This is a remark.\n",
221            "address:        Arelion Sweden AB\n",
222            "address:        Evenemangsgatan 2C\n",
223            "address:        SE-169 79 SOLNA\n",
224            "address:        Sweden\n",
225            "e-mail:         routing-registry@99.net\n",
226            "nic-hdl:        TRR2-RIPE\n",
227            "notify:         routing-registry@99.net\n",
228            "mnt-by:         Twelve99-IRR-MNT\n",
229            "created:        2002-05-27T15:05:16Z\n",
230            "last-modified:  2023-01-30T11:49:56Z\n",
231            "source:         RIPE\n",
232            "\n",
233            "\n",
234        );
235        let expected = rpsl::Object::new(vec![
236            rpsl::Attribute::new(
237                "role".to_string(),
238                vec![Some("Twelve99 Routing Registry".to_string())],
239            ),
240            rpsl::Attribute::new("remarks".to_string(), vec![None]),
241            rpsl::Attribute::new(
242                "remarks".to_string(),
243                vec![Some("This is a remark.".to_string())],
244            ),
245            rpsl::Attribute::new(
246                "address".to_string(),
247                vec![Some("Arelion Sweden AB".to_string())],
248            ),
249            rpsl::Attribute::new(
250                "address".to_string(),
251                vec![Some("Evenemangsgatan 2C".to_string())],
252            ),
253            rpsl::Attribute::new(
254                "address".to_string(),
255                vec![Some("SE-169 79 SOLNA".to_string())],
256            ),
257            rpsl::Attribute::new("address".to_string(), vec![Some("Sweden".to_string())]),
258            rpsl::Attribute::new(
259                "e-mail".to_string(),
260                vec![Some("routing-registry@99.net".to_string())],
261            ),
262            rpsl::Attribute::new("nic-hdl".to_string(), vec![Some("TRR2-RIPE".to_string())]),
263            rpsl::Attribute::new(
264                "notify".to_string(),
265                vec![Some("routing-registry@99.net".to_string())],
266            ),
267            rpsl::Attribute::new(
268                "mnt-by".to_string(),
269                vec![Some("Twelve99-IRR-MNT".to_string())],
270            ),
271            rpsl::Attribute::new(
272                "created".to_string(),
273                vec![Some("2002-05-27T15:05:16Z".to_string())],
274            ),
275            rpsl::Attribute::new(
276                "last-modified".to_string(),
277                vec![Some("2023-01-30T11:49:56Z".to_string())],
278            ),
279            rpsl::Attribute::new("source".to_string(), vec![Some("RIPE".to_string())]),
280        ]);
281
282        assert_eq!(parse_rpsl_object(rpsl).unwrap(), expected);
283    }
284
285    #[test]
286    fn parse_valid_server_response() {
287        let rpsl = concat!(
288            "as-block:       AS12557 - AS13223\n",
289            "descr:          RIPE NCC ASN block\n",
290            "remarks:        These AS Numbers are assigned to network operators in the RIPE NCC service region.\n",
291            "mnt-by:         RIPE-NCC-HM-MNT\n",
292            "created:        2018-11-22T15:27:24Z\n",
293            "last-modified:  2018-11-22T15:27:24Z\n",
294            "source:         RIPE\n",
295            "\n",
296            "% Information related to 'AS13030\n",
297            "\n",
298            "% Abuse contact for 'AS13030' is 'abuse@init7.net'\n",
299            "\n",
300            "aut-num:        AS13030\n",
301            "as-name:        INIT7\n",
302            "org:            ORG-ISA1-RIPE\n",
303            "remarks:        Init7 Global Backbone\n",
304            "\n",
305            "organisation:   ORG-ISA1-RIPE\n",
306            "org-name:       Init7 (Switzerland) Ltd.\n",
307            "\n"
308        );
309        let expected = rpsl::ObjectCollection::new(vec![
310            rpsl::Object::new(vec![
311                rpsl::Attribute::new(
312                    "as-block".to_string(),
313                    vec![Some("AS12557 - AS13223".to_string())],
314                ),
315                rpsl::Attribute::new(
316                    "descr".to_string(),
317                    vec![Some("RIPE NCC ASN block".to_string())],
318                ),
319                rpsl::Attribute::new(
320                    "remarks".to_string(),
321                    vec![Some(
322                        "These AS Numbers are assigned to network operators in the RIPE NCC service region.".to_string(),
323                    )],
324                ),
325                rpsl::Attribute::new(
326                    "mnt-by".to_string(),
327                    vec![Some("RIPE-NCC-HM-MNT".to_string())],
328                ),
329                rpsl::Attribute::new(
330                    "created".to_string(),
331                    vec![Some("2018-11-22T15:27:24Z".to_string())],
332                ),
333                rpsl::Attribute::new(
334                    "last-modified".to_string(),
335                    vec![Some("2018-11-22T15:27:24Z".to_string())],
336                ),
337                rpsl::Attribute::new("source".to_string(), vec![Some("RIPE".to_string())]),
338            ]),
339            rpsl::Object::new(vec![
340                rpsl::Attribute::new("aut-num".to_string(), vec![Some("AS13030".to_string())]),
341                rpsl::Attribute::new("as-name".to_string(), vec![Some("INIT7".to_string())]),
342                rpsl::Attribute::new("org".to_string(), vec![Some("ORG-ISA1-RIPE".to_string())]),
343                rpsl::Attribute::new(
344                    "remarks".to_string(),
345                    vec![Some("Init7 Global Backbone".to_string())],
346                ),
347            ]),
348            rpsl::Object::new(vec![
349                rpsl::Attribute::new(
350                    "organisation".to_string(),
351                    vec![Some("ORG-ISA1-RIPE".to_string())],
352                ),
353                rpsl::Attribute::new(
354                    "org-name".to_string(),
355                    vec![Some("Init7 (Switzerland) Ltd.".to_string())],
356                ),
357            ]),
358        ]);
359
360        assert_eq!(parse_whois_server_response(rpsl).unwrap(), expected);
361    }
362}