Lines
81.82 %
Functions
100 %
Branches
/*
* Hurl (https://hurl.dev)
* Copyright (C) 2024 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::ast::{SourceInfo, Template, TemplateElement};
use crate::parser::primitives::try_literal;
use crate::parser::{string, ParseError, ParseErrorKind, ParseResult};
use crate::reader::Reader;
use super::placeholder;
pub fn parse(reader: &mut Reader) -> ParseResult<Template> {
let start = reader.cursor();
let mut elements = vec![];
loop {
let save_state = reader.cursor();
match placeholder::parse(reader) {
Ok(placeholder) => {
let element = TemplateElement::Placeholder(placeholder);
elements.push(element);
}
Err(e) => {
if e.recoverable {
reader.seek(save_state);
let value = key_string_content(reader)?;
if value.is_empty() {
break;
let encoded = reader.read_from(start.index);
let element = TemplateElement::String { value, encoded };
} else {
return Err(e);
if elements.is_empty() {
let kind = ParseErrorKind::Expecting {
value: "key-string".to_string(),
};
return Err(ParseError::new(start.pos, false, kind));
if let Some(TemplateElement::String { encoded, .. }) = elements.first() {
if encoded.starts_with('[') {
let end = reader.cursor();
Ok(Template {
delimiter: None,
elements,
source_info: SourceInfo::new(start.pos, end.pos),
})
fn key_string_content(reader: &mut Reader) -> ParseResult<String> {
let mut s = String::new();
match key_string_escaped_char(reader) {
Ok(c) => {
s.push(c);
let s2 = key_string_text(reader);
if s2.is_empty() {
s.push_str(&s2);
Ok(s)
fn key_string_text(reader: &mut Reader) -> String {
let save = reader.cursor();
match reader.read() {
None => break,
Some(c) => {
if c.is_alphanumeric()
|| c == '_'
|| c == '-'
|| c == '.'
|| c == '['
|| c == ']'
|| c == '@'
|| c == '$'
{
reader.seek(save);
s
fn key_string_escaped_char(reader: &mut Reader) -> ParseResult<char> {
try_literal("\\", reader)?;
Some('#') => Ok('#'),
Some(':') => Ok(':'),
Some('\\') => Ok('\\'),
Some('/') => Ok('/'),
Some('b') => Ok('\x08'),
Some('f') => Ok('\x0c'),
Some('n') => Ok('\n'),
Some('r') => Ok('\r'),
Some('t') => Ok('\t'),
Some('u') => string::unicode(reader),
_ => Err(ParseError::new(
start.pos,
false,
ParseErrorKind::EscapeChar,
)),
#[cfg(test)]
mod tests {
use super::*;
use crate::reader::Pos;
#[test]
fn test_key_string() {
let mut reader = Reader::new("aaa\\: ");
assert_eq!(
parse(&mut reader).unwrap(),
Template {
elements: vec![TemplateElement::String {
value: "aaa:".to_string(),
encoded: "aaa\\:".to_string(),
}],
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 6)),
);
assert_eq!(reader.cursor().index, 5);
let mut reader = Reader::new("$top:");
value: "$top".to_string(),
encoded: "$top".to_string(),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 5)),
assert_eq!(reader.cursor().index, 4);
let mut reader = Reader::new("key\\u{20}\\u{3a} :");
value: "key :".to_string(),
encoded: "key\\u{20}\\u{3a}".to_string(),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 16)),
assert_eq!(reader.cursor().index, 15);
let mut reader = Reader::new("values\\u{5b}0\\u{5d} :");
value: "values[0]".to_string(),
encoded: "values\\u{5b}0\\u{5d}".to_string(),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 20)),
assert_eq!(reader.cursor().index, 19);
let mut reader = Reader::new("values[0] :");
encoded: "values[0]".to_string(),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 10)),
assert_eq!(reader.cursor().index, 9);
let mut reader = Reader::new("\\u{5b}0\\u{5d}");
value: "[0]".to_string(),
encoded: "\\u{5b}0\\u{5d}".to_string(),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 14)),
assert_eq!(reader.cursor().index, 13);
fn test_key_string_error() {
let mut reader = Reader::new("");
let error = parse(&mut reader).err().unwrap();
assert!(!error.recoverable);
assert_eq!(error.pos, Pos { line: 1, column: 1 });
let mut reader = Reader::new("{{key");
assert_eq!(error.pos, Pos { line: 1, column: 6 });
// key string can not start with a '[' (reserved for section)
let mut reader = Reader::new("[0]:");
assert_eq!(reader.cursor().index, 3);
let mut reader = Reader::new("\\l");
assert_eq!(error.pos, Pos { line: 1, column: 2 });
assert_eq!(error.kind, ParseErrorKind::EscapeChar);
let mut reader = Reader::new(r#"{"id":1}"#);
error.kind,
ParseErrorKind::Expecting {
value: "key-string".to_string()
fn test_key_string_content() {
let mut reader = Reader::new("aaa\\:");
assert_eq!(key_string_content(&mut reader).unwrap(), "aaa:");
fn test_key_string_text() {
assert_eq!(key_string_text(&mut reader), "aaa");
fn test_key_string_escaped_char() {
let mut reader = Reader::new("\\u{0a}");
assert_eq!(key_string_escaped_char(&mut reader).unwrap(), '\n');
assert_eq!(reader.cursor().index, 6);
let mut reader = Reader::new("\\:");
assert_eq!(key_string_escaped_char(&mut reader).unwrap(), ':');
assert_eq!(reader.cursor().index, 2);
let mut reader = Reader::new("x");
let error = key_string_escaped_char(&mut reader).err().unwrap();
value: "\\".to_string()
assert!(error.recoverable);
assert_eq!(reader.cursor().index, 0);