1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2026 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use crate::ast::{SourceInfo, Template, TemplateElement};
19
use crate::reader::Reader;
20
use crate::types::ToSource;
21

            
22
use super::error::{ParseError, ParseErrorKind};
23
use super::primitives::try_literal;
24
use super::{ParseResult, placeholder, string};
25

            
26
/// Parses a string into a [key string](https://hurl.dev/docs/grammar.html#key-string).
27
33356
pub fn parse(reader: &mut Reader) -> ParseResult<Template> {
28
33356
    let start = reader.cursor();
29
33356
    let mut elements = vec![];
30
    loop {
31
64688
        let save_state = reader.cursor();
32
64688
        match placeholder::parse(reader) {
33
200
            Ok(placeholder) => {
34
200
                let element = TemplateElement::Placeholder(placeholder);
35
200
                elements.push(element);
36
            }
37
64488
            Err(e) => {
38
64488
                if e.recoverable {
39
64488
                    reader.seek(save_state);
40
64488
                    let value = key_string_content(reader)?;
41
64488
                    if value.is_empty() {
42
33356
                        break;
43
                    }
44
31132
                    let source = reader.read_from(start.index).to_source();
45
31132
                    let element = TemplateElement::String { value, source };
46
31132
                    elements.push(element);
47
                } else {
48
                    return Err(e);
49
                }
50
            }
51
        }
52
    }
53
33356
    if elements.is_empty() {
54
2024
        let kind = ParseErrorKind::Expecting {
55
2024
            value: "key-string".to_string(),
56
2024
        };
57
2024
        return Err(ParseError::new(start.pos, false, kind));
58
    }
59
31332
    if let Some(TemplateElement::String { source, .. }) = elements.first()
60
31132
        && source.starts_with('[')
61
    {
62
10668
        let kind = ParseErrorKind::Expecting {
63
10668
            value: "key-string".to_string(),
64
10668
        };
65
10668
        return Err(ParseError::new(start.pos, false, kind));
66
    }
67

            
68
20664
    let end = reader.cursor();
69
20664
    let template = Template::new(None, elements, SourceInfo::new(start.pos, end.pos));
70
20664
    Ok(template)
71
}
72

            
73
64488
fn key_string_content(reader: &mut Reader) -> ParseResult<String> {
74
64488
    let mut s = String::new();
75
    loop {
76
95812
        match key_string_escaped_char(reader) {
77
136
            Ok(c) => {
78
136
                s.push(c);
79
            }
80
95676
            Err(e) => {
81
95676
                if e.recoverable {
82
95676
                    let s2 = key_string_text(reader);
83
95676
                    if s2.is_empty() {
84
64488
                        break;
85
31188
                    } else {
86
31188
                        s.push_str(&s2);
87
                    }
88
                } else {
89
                    return Err(e);
90
                }
91
            }
92
        }
93
    }
94
64488
    Ok(s)
95
}
96

            
97
95676
fn key_string_text(reader: &mut Reader) -> String {
98
95676
    let mut s = String::new();
99
    loop {
100
301124
        let save = reader.cursor();
101
301124
        match reader.read() {
102
376
            None => break,
103
300748
            Some(c) => {
104
300748
                if c.is_alphanumeric()
105
120212
                    || c == '_'
106
119252
                    || c == '-'
107
117392
                    || c == '.'
108
117332
                    || c == '['
109
106116
                    || c == ']'
110
95424
                    || c == '@'
111
95364
                    || c == '$'
112
205448
                {
113
205448
                    s.push(c);
114
205448
                } else {
115
95300
                    reader.seek(save);
116
95300
                    break;
117
                }
118
            }
119
        }
120
    }
121

            
122
95676
    s
123
}
124

            
125
95812
fn key_string_escaped_char(reader: &mut Reader) -> ParseResult<char> {
126
95812
    try_literal("\\", reader)?;
127
136
    let start = reader.cursor();
128
136
    match reader.read() {
129
        Some('#') => Ok('#'),
130
        Some(':') => Ok(':'),
131
        Some('\\') => Ok('\\'),
132
        Some('/') => Ok('/'),
133
        Some('b') => Ok('\x08'),
134
        Some('f') => Ok('\x0c'),
135
        Some('n') => Ok('\n'),
136
        Some('r') => Ok('\r'),
137
        Some('t') => Ok('\t'),
138
136
        Some('u') => string::unicode(reader),
139
        _ => Err(ParseError::new(
140
            start.pos,
141
            false,
142
            ParseErrorKind::EscapeChar,
143
        )),
144
    }
145
}
146

            
147
#[cfg(test)]
148
mod tests {
149
    use super::*;
150
    use crate::reader::{CharPos, Pos};
151

            
152
    #[test]
153
    fn test_key_string() {
154
        let mut reader = Reader::new("aaa\\: ");
155
        assert_eq!(
156
            parse(&mut reader).unwrap(),
157
            Template::new(
158
                None,
159
                vec![TemplateElement::String {
160
                    value: "aaa:".to_string(),
161
                    source: "aaa\\:".to_source(),
162
                }],
163
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 6)),
164
            )
165
        );
166
        assert_eq!(reader.cursor().index, CharPos(5));
167

            
168
        let mut reader = Reader::new("$top:");
169
        assert_eq!(
170
            parse(&mut reader).unwrap(),
171
            Template::new(
172
                None,
173
                vec![TemplateElement::String {
174
                    value: "$top".to_string(),
175
                    source: "$top".to_source(),
176
                }],
177
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 5)),
178
            )
179
        );
180
        assert_eq!(reader.cursor().index, CharPos(4));
181

            
182
        let mut reader = Reader::new("key\\u{20}\\u{3a} :");
183
        assert_eq!(
184
            parse(&mut reader).unwrap(),
185
            Template::new(
186
                None,
187
                vec![TemplateElement::String {
188
                    value: "key :".to_string(),
189
                    source: "key\\u{20}\\u{3a}".to_source(),
190
                }],
191
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 16))
192
            )
193
        );
194
        assert_eq!(reader.cursor().index, CharPos(15));
195

            
196
        let mut reader = Reader::new("values\\u{5b}0\\u{5d} :");
197
        assert_eq!(
198
            parse(&mut reader).unwrap(),
199
            Template::new(
200
                None,
201
                vec![TemplateElement::String {
202
                    value: "values[0]".to_string(),
203
                    source: "values\\u{5b}0\\u{5d}".to_source(),
204
                }],
205
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 20))
206
            )
207
        );
208
        assert_eq!(reader.cursor().index, CharPos(19));
209

            
210
        let mut reader = Reader::new("values[0] :");
211
        assert_eq!(
212
            parse(&mut reader).unwrap(),
213
            Template::new(
214
                None,
215
                vec![TemplateElement::String {
216
                    value: "values[0]".to_string(),
217
                    source: "values[0]".to_source(),
218
                }],
219
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 10)),
220
            )
221
        );
222
        assert_eq!(reader.cursor().index, CharPos(9));
223

            
224
        let mut reader = Reader::new("\\u{5b}0\\u{5d}");
225
        assert_eq!(
226
            parse(&mut reader).unwrap(),
227
            Template::new(
228
                None,
229
                vec![TemplateElement::String {
230
                    value: "[0]".to_string(),
231
                    source: "\\u{5b}0\\u{5d}".to_source(),
232
                }],
233
                SourceInfo::new(Pos::new(1, 1), Pos::new(1, 14)),
234
            )
235
        );
236
        assert_eq!(reader.cursor().index, CharPos(13));
237
    }
238

            
239
    #[test]
240
    fn test_key_string_error() {
241
        let mut reader = Reader::new("");
242
        let error = parse(&mut reader).err().unwrap();
243
        assert!(!error.recoverable);
244
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
245

            
246
        let mut reader = Reader::new("{{key");
247
        let error = parse(&mut reader).err().unwrap();
248
        assert!(!error.recoverable);
249
        assert_eq!(error.pos, Pos { line: 1, column: 6 });
250

            
251
        // key string can not start with a '[' (reserved for section)
252
        let mut reader = Reader::new("[0]:");
253
        let error = parse(&mut reader).err().unwrap();
254
        assert!(!error.recoverable);
255
        assert_eq!(reader.cursor().index, CharPos(3));
256

            
257
        let mut reader = Reader::new("\\l");
258
        let error = parse(&mut reader).err().unwrap();
259
        assert_eq!(error.pos, Pos { line: 1, column: 2 });
260
        assert_eq!(error.kind, ParseErrorKind::EscapeChar);
261

            
262
        let mut reader = Reader::new(r#"{"id":1}"#);
263
        let error = parse(&mut reader).err().unwrap();
264
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
265
        assert_eq!(
266
            error.kind,
267
            ParseErrorKind::Expecting {
268
                value: "key-string".to_string()
269
            }
270
        );
271
    }
272

            
273
    #[test]
274
    fn test_key_string_content() {
275
        let mut reader = Reader::new("aaa\\:");
276
        assert_eq!(key_string_content(&mut reader).unwrap(), "aaa:");
277
    }
278

            
279
    #[test]
280
    fn test_key_string_text() {
281
        let mut reader = Reader::new("aaa\\:");
282
        assert_eq!(key_string_text(&mut reader), "aaa");
283
        assert_eq!(reader.cursor().index, CharPos(3));
284
    }
285

            
286
    #[test]
287
    fn test_key_string_escaped_char() {
288
        let mut reader = Reader::new("\\u{0a}");
289
        assert_eq!(key_string_escaped_char(&mut reader).unwrap(), '\n');
290
        assert_eq!(reader.cursor().index, CharPos(6));
291

            
292
        let mut reader = Reader::new("\\:");
293
        assert_eq!(key_string_escaped_char(&mut reader).unwrap(), ':');
294
        assert_eq!(reader.cursor().index, CharPos(2));
295

            
296
        let mut reader = Reader::new("x");
297
        let error = key_string_escaped_char(&mut reader).err().unwrap();
298
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
299
        assert_eq!(
300
            error.kind,
301
            ParseErrorKind::Expecting {
302
                value: "\\".to_string()
303
            }
304
        );
305
        assert!(error.recoverable);
306
        assert_eq!(reader.cursor().index, CharPos(0));
307
    }
308
}