use crate::ast::*;
use crate::parser::error::*;
use crate::parser::primitives::*;
use crate::parser::reader::Reader;
use crate::parser::{expr, ParseResult};
pub fn path(reader: &mut Reader) -> ParseResult<Template> {
let start = reader.state;
let mut elements = vec![];
let mut buffer = String::new();
loop {
let save = reader.state;
match line_terminator(reader) {
Ok(_) => {
reader.state = save;
break;
}
_ => reader.state = save,
}
match expr::parse(reader) {
Ok(value) => {
if !buffer.is_empty() {
elements.push(TemplateElement::String {
value: buffer.clone(),
encoded: buffer.clone(),
});
buffer = String::new();
}
elements.push(TemplateElement::Expression(value));
}
Err(e) => {
if !e.recoverable {
return Err(e);
} else {
reader.state = save;
match reader.read() {
None => break,
Some(c) => {
if c.is_alphanumeric()
| [
':', '/', '.', '-', '?', '=', '&', '_', '%', '*', ',', '@',
'~', '+', '!', '$', '\'', '(', ')', ';', '[', ']',
]
.contains(&c)
{
buffer.push(c);
} else {
reader.state = save;
break;
}
}
}
}
}
}
}
if !buffer.is_empty() {
elements.push(TemplateElement::String {
value: buffer.clone(),
encoded: buffer,
});
}
let save = reader.state;
if line_terminator(reader).is_err() {
reader.state = save;
let c = reader.peek().unwrap();
return Err(Error::new(
reader.state.pos,
false,
ParseError::PathIllegalCharacter(c),
));
}
reader.state = save;
Ok(Template {
delimiter: None,
elements,
source_info: SourceInfo {
start: start.pos,
end: reader.state.pos,
},
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_path() {
let mut reader = Reader::new("http://google.fr # ");
assert_eq!(
path(&mut reader).unwrap(),
Template {
elements: vec![TemplateElement::String {
value: String::from("http://google.fr"),
encoded: String::from("http://google.fr"),
}],
delimiter: None,
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 17)),
}
);
assert_eq!(reader.state.cursor, 16);
}
#[test]
fn test_path2() {
let mut reader = Reader::new("http://localhost:8000/cookies/set-session-cookie2-valueA");
assert_eq!(
path(&mut reader).unwrap(),
Template {
elements: vec![TemplateElement::String {
value: String::from("http://localhost:8000/cookies/set-session-cookie2-valueA"),
encoded: String::from(
"http://localhost:8000/cookies/set-session-cookie2-valueA"
),
}],
delimiter: None,
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 57)),
}
);
assert_eq!(reader.state.cursor, 56);
}
#[test]
fn test_path_with_expression() {
let mut reader = Reader::new("http://{{host}}.fr ");
assert_eq!(
path(&mut reader).unwrap(),
Template {
elements: vec![
TemplateElement::String {
value: String::from("http://"),
encoded: String::from("http://"),
},
TemplateElement::Expression(Expr {
space0: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(1, 10)),
},
variable: Variable {
name: String::from("host"),
source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(1, 14)),
},
space1: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 14), Pos::new(1, 14)),
},
}),
TemplateElement::String {
value: String::from(".fr"),
encoded: String::from(".fr"),
},
],
delimiter: None,
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 19)),
}
);
assert_eq!(reader.state.cursor, 18);
}
#[test]
fn test_path_error_variable() {
let mut reader = Reader::new("http://{{host>}}.fr");
let error = path(&mut reader).err().unwrap();
assert_eq!(
error.pos,
Pos {
line: 1,
column: 14,
}
);
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("}}")
}
);
assert!(!error.recoverable);
assert_eq!(reader.state.cursor, 14);
}
#[test]
fn test_path_error_missing_delimiter() {
let mut reader = Reader::new("http://{{host");
let error = path(&mut reader).err().unwrap();
assert_eq!(
error.pos,
Pos {
line: 1,
column: 14,
}
);
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("}}")
}
);
assert!(!error.recoverable);
}
#[test]
fn test_valid_paths() {
let valid_paths = [
"http://www.google.com",
"http://www.google.com/",
"http://www.google.com/file%20one%26two",
"http://www.google.com/#file%20one%26two",
"http://www.google.com/?",
"http://www.google.com/?foo=bar?",
"http://www.google.com/?q=go+language",
"http://www.google.com/?q=go%20language",
"http://www.google.com/a%20b?q=c+d",
"http://user:password@google.com",
"http://user:password@google.com",
"http://j@ne:password@google.com",
"http://j%40ne:password@google.com",
"http://jane:p@ssword@google.com",
"http://j@ne:password@google.com/p@th?q=@go",
"http://j%40ne:password@google.com/p@th?q=@go",
"http://www.google.com/?q=go+language#foo",
"http://www.google.com/?q=go+language#foo&bar",
"http://www.google.com/?q=go+language#foo&bar",
"http://www.google.com/?q=go+language#foo%26bar",
"http://www.google.com/?q=go+language#foo%26bar",
"http://%3Fam:pa%3Fsword@google.com",
"http://192.168.0.1/",
"http://192.168.0.1:8080/",
"http://[fe80::1]/",
"http://[fe80::1]:8080/",
"http://[fe80::1%25en0]/",
"http://[fe80::1%25en0]:8080/",
"http://[fe80::1%25%65%6e%301-._~]/",
"http://[fe80::1%25en01-._~]/",
"http://[fe80::1%25%65%6e%301-._~]:8080/",
"http://rest.rsc.io/foo%2fbar/baz%2Fquux?alt=media",
"http://host/!$&'()*+,;=:@[hello]",
"http://example.com/oid/[order_id]",
"http://192.168.0.2:8080/foo",
"http://192.168.0.2:/foo",
"http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:8080/foo",
"http://2b01:e34:ef40:7730:8e70:5aff:fefe:edac:/foo",
"http://[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:8080/foo",
"http://[2b01:e34:ef40:7730:8e70:5aff:fefe:edac]:/foo",
"http://hello.世界.com/foo",
"http://hello.%E4%B8%96%E7%95%8C.com/foo",
"http://hello.%e4%b8%96%e7%95%8c.com/foo",
"http://hello.%E4%B8%96%E7%95%8C.com/foo",
"http://hello.%E4%B8%96%E7%95%8C.com/foo",
"http://example.com//foo",
];
for s in valid_paths {
let mut reader = Reader::new(s);
assert!(path(&mut reader).is_ok());
}
}
}