questdb_confstr/
lib.rs

1/*******************************************************************************
2 *     ___                  _   ____  ____
3 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
4 *   | | | | | | |/ _ \/ __| __| | | |  _ \
5 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
6 *    \__\_\\__,_|\___||___/\__|____/|____/
7 *
8 *  Copyright (c) 2014-2019 Appsicle
9 *  Copyright (c) 2019-2024 QuestDB
10 *
11 *  Licensed under the Apache License, Version 2.0 (the "License");
12 *  you may not use this file except in compliance with the License.
13 *  You may obtain a copy of the License at
14 *
15 *  http://www.apache.org/licenses/LICENSE-2.0
16 *
17 *  Unless required by applicable law or agreed to in writing, software
18 *  distributed under the License is distributed on an "AS IS" BASIS,
19 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 *  See the License for the specific language governing permissions and
21 *  limitations under the License.
22 *
23 ******************************************************************************/
24
25#![doc = include_str!("../README.md")]
26
27use crate::peekable2::{Peekable2, Peekable2Ext};
28use std::collections::HashMap;
29use std::fmt;
30use std::fmt::{Debug, Display, Formatter};
31use std::str::CharIndices;
32
33mod peekable2;
34
35/// Parameter keys are ascii lowercase strings.
36pub type Key = String;
37
38/// Parameter values are strings.
39pub type Value = String;
40
41/// Parameters are stored in a `Vec` of `(Key, Value)` pairs.
42/// Keys are always lowercase.
43pub type Params = HashMap<Key, Value>;
44
45/// Parsed configuration string.
46///
47/// The parameters are stored in a `Vec` of `(Key, Value)` pairs.
48pub struct ConfStr {
49    service: String,
50    params: Params,
51}
52
53impl Debug for ConfStr {
54    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
55        // Params are hidden from debug output in case they contain sensitive information.
56        write!(f, "ConfStr {{ service: {:?}, .. }}", self.service)
57    }
58}
59
60impl ConfStr {
61    /// Create a new configuration string object.
62    pub fn new(service: String, params: Params) -> Self {
63        ConfStr { service, params }
64    }
65
66    /// Access the service name.
67    pub fn service(&self) -> &str {
68        &self.service
69    }
70
71    /// Access the parameters.
72    pub fn params(&self) -> &Params {
73        &self.params
74    }
75
76    /// Get a parameter.
77    /// Key should always be specified as lowercase.
78    pub fn get(&self, key: &str) -> Option<&str> {
79        self.params.get(key).map(|s| s.as_str())
80    }
81}
82
83/// Byte position in the input string where the parsing error occurred.
84pub type Position = usize;
85
86/// The type of parsing error.
87#[derive(Debug, PartialEq, Eq, Clone)]
88pub enum ErrorKind {
89    ExpectedIdentifierNot(char),
90    MustBeAlphanumeric(char),
91    ExpectedIdentifierNotEmpty,
92    BadSeparator((char, char)),
93    IncompleteKeyValue,
94    InvalidCharInValue(char),
95    MissingTrailingSemicolon,
96    DuplicateKey(String),
97}
98
99impl<'a> PartialEq<&'a ErrorKind> for ErrorKind {
100    fn eq(&self, other: &&'a ErrorKind) -> bool {
101        self == *other
102    }
103}
104
105impl<'a> PartialEq<ErrorKind> for &'a ErrorKind {
106    fn eq(&self, other: &ErrorKind) -> bool {
107        *self == other
108    }
109}
110
111impl Display for ErrorKind {
112    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
113        // Ensure no values are leaked in error messages.
114        match self {
115            ErrorKind::ExpectedIdentifierNot(c) => {
116                write!(
117                    f,
118                    "expected identifier to start with ascii letter, not {:?}",
119                    c
120                )
121            }
122            ErrorKind::MustBeAlphanumeric(c) => write!(f, "must be alphanumeric, not {:?}", c),
123            ErrorKind::ExpectedIdentifierNotEmpty => {
124                write!(f, "expected identifier, not an empty string")
125            }
126            ErrorKind::BadSeparator((e, c)) => {
127                write!(f, "bad separator, expected {:?} got {:?}", e, c)
128            }
129            ErrorKind::IncompleteKeyValue => {
130                write!(f, "incomplete key-value pair before end of input")
131            }
132            ErrorKind::InvalidCharInValue(c) => write!(f, "invalid char {:?} in value", c),
133            ErrorKind::MissingTrailingSemicolon => write!(f, "missing trailing semicolon"),
134            ErrorKind::DuplicateKey(s) => write!(f, "duplicate key {:?}", s),
135        }
136    }
137}
138
139/// The parsing error.
140#[derive(Debug)]
141pub struct ParsingError {
142    kind: ErrorKind,
143    position: usize,
144}
145
146impl ParsingError {
147    /// Access the byte position in the input string where the parsing error occurred.
148    pub fn position(&self) -> usize {
149        self.position
150    }
151
152    /// Access the type of parsing error.
153    pub fn kind(&self) -> &ErrorKind {
154        &self.kind
155    }
156}
157
158fn parse_err(kind: ErrorKind, position: Position) -> ParsingError {
159    ParsingError { kind, position }
160}
161
162impl Display for ParsingError {
163    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
164        write!(f, "{} at position {}", self.kind, self.position)
165    }
166}
167
168impl std::error::Error for ParsingError {}
169
170fn parse_ident(
171    iter: &mut Peekable2<CharIndices>,
172    next_pos: &mut Position,
173) -> Result<Key, ParsingError> {
174    let mut token = String::new();
175    while let Some((pos, c)) = iter.peek0() {
176        *next_pos = *pos;
177        if c.is_ascii_alphanumeric() || *c == '_' {
178            token.push(*c);
179            iter.next();
180        } else {
181            if token.is_empty() {
182                return Err(parse_err(ErrorKind::ExpectedIdentifierNot(*c), *next_pos));
183            } else if !c.is_ascii() || matches!(c, '\0'..=' ') {
184                return Err(parse_err(ErrorKind::MustBeAlphanumeric(*c), *next_pos));
185            }
186            break;
187        }
188    }
189
190    if token.is_empty() {
191        return Err(parse_err(ErrorKind::ExpectedIdentifierNotEmpty, *next_pos));
192    }
193
194    Ok(token)
195}
196
197fn parse_value(
198    iter: &mut Peekable2<CharIndices>,
199    next_pos: &mut Position,
200    input_len: usize,
201) -> Result<Value, ParsingError> {
202    let mut value = String::new();
203    loop {
204        let c1 = iter.peek0().cloned();
205        let c2 = iter.peek1().cloned();
206        if let Some((p, _)) = c1 {
207            *next_pos = p;
208        }
209        match (c1, c2) {
210            (Some((_, ';')), Some((_, ';'))) => {
211                let _ = iter.next();
212                let _ = iter.next();
213                value.push(';');
214            }
215            (Some((_, ';')), _) => break,
216            (Some((p, c)), _) => {
217                if matches!(c, '\u{0}'..='\u{1f}' | '\u{7f}'..='\u{9f}') {
218                    return Err(parse_err(ErrorKind::InvalidCharInValue(c), p));
219                }
220                value.push(c);
221                let _ = iter.next();
222            }
223            (None, _) => return Err(parse_err(ErrorKind::MissingTrailingSemicolon, input_len)),
224        }
225    }
226    Ok(value)
227}
228
229fn parse_double_colon(
230    iter: &mut Peekable2<CharIndices>,
231    next_pos: &mut Position,
232) -> Result<bool, ParsingError> {
233    let c1 = iter.next();
234    let c2 = iter.next();
235    match (c1, c2) {
236        (Some((_, ':')), Some((_, ':'))) => {
237            *next_pos += 2;
238            Ok(true)
239        }
240        (None, None) => Ok(false),
241        (Some((_, ':')), Some((p, c))) => Err(parse_err(ErrorKind::BadSeparator((':', c)), p)),
242        (Some((p, c)), _) => Err(parse_err(ErrorKind::BadSeparator((':', c)), p)),
243        (None, _) => unreachable!("peekable2 guarantees that the second item is always None"),
244    }
245}
246
247fn parse_params(
248    iter: &mut Peekable2<CharIndices>,
249    next_pos: &mut Position,
250    input_len: usize,
251) -> Result<Params, ParsingError> {
252    let mut params = Params::new();
253    while let Some((p, _)) = iter.peek0() {
254        *next_pos = *p;
255        let key_pos = *next_pos;
256        let key = parse_ident(iter, next_pos)?;
257        if params.contains_key(&key) {
258            return Err(parse_err(ErrorKind::DuplicateKey(key.clone()), key_pos));
259        }
260        match iter.next() {
261            Some((p, '=')) => *next_pos = p + 1,
262            Some((p, c)) => return Err(parse_err(ErrorKind::BadSeparator(('=', c)), p)),
263            None => return Err(parse_err(ErrorKind::IncompleteKeyValue, input_len)),
264        }
265        let value = parse_value(iter, next_pos, input_len)?;
266        iter.next().unwrap(); // skip ';'
267        params.insert(key, value);
268    }
269    Ok(params)
270}
271
272/// Parse a config string.
273///
274/// ```
275/// use questdb_confstr::parse_conf_str;
276/// # use questdb_confstr::ParsingError;
277/// let config = parse_conf_str("service::key1=value1;key2=value2;")?;
278/// assert_eq!(config.service(), "service");
279/// assert_eq!(config.get("key1"), Some("value1"));
280/// assert_eq!(config.get("key2"), Some("value2"));
281/// # Ok::<(), ParsingError>(())
282/// ```
283pub fn parse_conf_str(input: &str) -> Result<ConfStr, ParsingError> {
284    let mut iter = input.char_indices().peekable2();
285    let mut next_pos = 0;
286    let service = parse_ident(&mut iter, &mut next_pos)?;
287    let has_separator = parse_double_colon(&mut iter, &mut next_pos)?;
288    if !has_separator {
289        return Ok(ConfStr::new(service, Params::new()));
290    }
291    let params = parse_params(&mut iter, &mut next_pos, input.len())?;
292    Ok(ConfStr::new(service, params))
293}