rpsl/parser/
api.rs

1use winnow::{
2    ascii::multispace0,
3    combinator::{delimited, repeat},
4    error::ContextError,
5    Parser,
6};
7
8use super::core::{object_block, object_block_padded};
9use crate::{Object, ParseError};
10
11/// Parse RPSL into an [`Object`], borrowing from the source.
12///
13/// ```text
14/// role:           ACME Company
15/// address:        Packet Street 6
16/// address:        128 Series of Tubes
17/// address:        Internet
18/// email:          rpsl-rs@github.com
19/// nic-hdl:        RPSL1-RIPE
20/// source:         RIPE
21///                        ↓
22/// role:           ACME Company ◀─────────────── &"role":      &"ACME Company"
23/// address:        Packet Street 6 ◀──────────── &"address":   &"Packet Street 6"
24/// address:        128 Series of Tubes ◀──────── &"address":   &"128 Series of Tubes"
25/// address:        Internet ◀─────────────────── &"address":   &"Internet"
26/// email:          rpsl-rs@github.com ◀───────── &"email":     &"rpsl-rs@github.com"
27/// nic-hdl:        RPSL1-RIPE ◀───────────────── &"nic-hdl":   &"RPSL1-RIPE"
28/// source:         RIPE ◀─────────────────────── &"source":    &"RIPE"
29/// ```
30///
31/// # Errors
32/// Returns a [`ParseError`] if the input is not valid RPSL.
33///
34/// # Examples
35/// ```
36/// # use rpsl::{parse_object, object};
37/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
38/// let role_acme = "
39/// role:        ACME Company
40/// address:     Packet Street 6
41/// address:     128 Series of Tubes
42/// address:     Internet
43/// email:       rpsl-rs@github.com
44/// nic-hdl:     RPSL1-RIPE
45/// source:      RIPE
46///
47/// ";
48/// let parsed = parse_object(role_acme)?;
49/// assert_eq!(
50///     parsed,
51///     object! {
52///         "role": "ACME Company";
53///         "address": "Packet Street 6";
54///         "address": "128 Series of Tubes";
55///         "address": "Internet";
56///         "email": "rpsl-rs@github.com";
57///         "nic-hdl": "RPSL1-RIPE";
58///         "source": "RIPE";
59///     }
60/// );
61/// # Ok(())
62/// # }
63/// ```
64///
65/// Values spread over multiple lines can be parsed too.
66/// ```
67/// # use rpsl::{parse_object, object};
68/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
69/// let multiline_remark = "
70/// remarks:     Value 1
71///              Value 2
72///
73/// ";
74/// assert_eq!(
75///     parse_object(multiline_remark)?,
76///     object! {
77///         "remarks": "Value 1", "Value 2";
78///     }
79/// );
80/// # Ok(())
81/// # }
82/// ```
83///
84/// An attribute that does not have a value is valid.
85/// ```
86/// # use rpsl::{parse_object, object};
87/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
88/// let without_value = "
89/// as-name:     REMARKABLE
90/// remarks:
91/// remarks:     ^^^^^^^^^^ nothing here
92///
93/// ";
94/// assert_eq!(
95///     parse_object(without_value)?,
96///     object! {
97///         "as-name": "REMARKABLE";
98///         "remarks": "";
99///         "remarks": "^^^^^^^^^^ nothing here";
100///     }
101/// );
102/// # Ok(())
103/// # }
104/// ```
105///
106/// The same goes for values containing only whitespace.
107/// Since whitespace to the left of a value is trimmed, they are equivalent to no value.
108///
109/// ```
110/// # use rpsl::{parse_object, object};
111/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
112/// let whitespace_value = "
113/// as-name:     REMARKABLE
114/// remarks:               
115/// remarks:     ^^^^^^^^^^ nothing but hot air (whitespace)
116///
117/// ";
118/// assert_eq!(
119///     parse_object(whitespace_value)?,
120///     object! {
121///         "as-name": "REMARKABLE";
122///         "remarks": "";
123///         "remarks": "^^^^^^^^^^ nothing but hot air (whitespace)";
124///     }
125/// );
126/// # Ok(())
127/// # }
128/// ```
129pub fn parse_object(rpsl: &str) -> Result<Object, ParseError> {
130    let block_parser = object_block::<ContextError>();
131    let object = delimited(multispace0, block_parser, multispace0).parse(rpsl)?;
132    Ok(object)
133}
134
135/// Parse a WHOIS server response into [`Object`]s contained within.
136///
137/// # Errors
138/// Returns a [`ParseError`] error if the input is not valid RPSL.
139///
140/// # Examples
141/// ```
142/// # use rpsl::{parse_whois_response, object};
143/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
144/// let whois_response = "
145/// ASNumber:       32934
146/// ASName:         FACEBOOK
147/// ASHandle:       AS32934
148/// RegDate:        2004-08-24
149/// Updated:        2012-02-24
150/// Comment:        Please send abuse reports to abuse@facebook.com
151/// Ref:            https://rdap.arin.net/registry/autnum/32934
152///
153///
154/// OrgName:        Facebook, Inc.
155/// OrgId:          THEFA-3
156/// Address:        1601 Willow Rd.
157/// City:           Menlo Park
158/// StateProv:      CA
159/// PostalCode:     94025
160/// Country:        US
161/// RegDate:        2004-08-11
162/// Updated:        2012-04-17
163/// Ref:            https://rdap.arin.net/registry/entity/THEFA-3
164///
165/// ";
166/// let objects = parse_whois_response(whois_response)?;
167/// assert_eq!(
168///     objects,
169///     vec![
170///             object! {
171///                 "ASNumber": "32934";
172///                 "ASName": "FACEBOOK";
173///                 "ASHandle": "AS32934";
174///                 "RegDate": "2004-08-24";
175///                 "Updated": "2012-02-24";
176///                 "Comment": "Please send abuse reports to abuse@facebook.com";
177///                 "Ref": "https://rdap.arin.net/registry/autnum/32934";
178///             },
179///             object! {
180///                 "OrgName": "Facebook, Inc.";
181///                 "OrgId": "THEFA-3";
182///                 "Address": "1601 Willow Rd.";
183///                 "City": "Menlo Park";
184///                 "StateProv": "CA";
185///                 "PostalCode": "94025";
186///                 "Country": "US";
187///                 "RegDate": "2004-08-11";
188///                 "Updated": "2012-04-17";
189///                 "Ref": "https://rdap.arin.net/registry/entity/THEFA-3";
190///             }
191///         ]
192/// );
193/// # Ok(())
194/// # }
195pub fn parse_whois_response(response: &str) -> Result<Vec<Object>, ParseError> {
196    let block_parser = object_block_padded(object_block::<ContextError>());
197    let objects = repeat(1.., block_parser).parse(response)?;
198    Ok(objects)
199}