use crate::ast::VersionValue::VersionAny;
use crate::ast::*;
use crate::parser::bytes::*;
use crate::parser::combinators::*;
use crate::parser::error::*;
use crate::parser::number::natural;
use crate::parser::path::path;
use crate::parser::primitives::*;
use crate::parser::reader::Reader;
use crate::parser::sections::*;
use crate::parser::ParseResult;
pub fn impostor_file(reader: &mut Reader) -> ParseResult<ImpostorFile> {
let entries = zero_or_more(entry, reader)?;
let line_terminators = optional_line_terminators(reader)?;
eof(reader)?;
Ok(ImpostorFile {
entries,
line_terminators,
})
}
fn entry(reader: &mut Reader) -> ParseResult<Entry> {
let req = request(reader)?;
let resp = response(reader)?;
Ok(Entry {
request: req,
response: resp,
})
}
fn request(reader: &mut Reader) -> ParseResult<Request> {
let start = reader.state;
let line_terminators = optional_line_terminators(reader)?;
let space0 = zero_or_more_spaces(reader)?;
let m = method(reader)?;
let space1 = one_or_more_spaces(reader)?;
let u = path(reader)?;
let line_terminator0 = line_terminator(reader)?;
let headers = zero_or_more(key_value, reader)?;
let sections = request_sections(reader)?;
let b = optional(body, reader)?;
let source_info = SourceInfo::new(start.pos, reader.state.pos);
let mut section_names = vec![];
for section in sections.clone() {
if section_names.contains(§ion.name().to_string()) {
return Err(Error::new(
section.source_info.start,
false,
ParseError::DuplicateSection,
));
} else {
section_names.push(section.name().to_string());
}
}
Ok(Request {
line_terminators,
space0,
method: m,
space1,
path: u,
line_terminator0,
headers,
sections,
body: b,
source_info,
})
}
fn response(reader: &mut Reader) -> ParseResult<Response> {
let start = reader.state;
let line_terminators = optional_line_terminators(reader)?;
let space0 = zero_or_more_spaces(reader)?;
let _version = version(reader)?;
let space1 = one_or_more_spaces(reader)?;
let _status = status(reader)?;
let line_terminator0 = line_terminator(reader)?;
let headers = zero_or_more(key_value, reader)?;
let b = optional(body, reader)?;
Ok(Response {
line_terminators,
space0,
version: _version,
space1,
status: _status,
line_terminator0,
headers,
body: b,
source_info: SourceInfo::new(start.pos, reader.state.pos),
})
}
fn method(reader: &mut Reader) -> ParseResult<Method> {
if reader.is_eof() {
let inner = ParseError::Method {
name: "<EOF>".to_string(),
};
return Err(Error::new(reader.state.pos, true, inner));
}
let start = reader.state;
let name = reader.read_while(|c| c.is_ascii_alphabetic());
if name.is_empty() || name.to_uppercase() != name {
let inner = ParseError::Method { name };
Err(Error::new(start.pos, false, inner))
} else {
Ok(Method(name))
}
}
fn version(reader: &mut Reader) -> ParseResult<Version> {
let start = reader.state;
try_literal("HTTP", reader)?;
let next_c = reader.peek();
match next_c {
Some('/') => {
let available_version = vec![
("/1.0", VersionValue::Version1),
("/1.1", VersionValue::Version11),
("/2", VersionValue::Version2),
("/3", VersionValue::Version3),
("/*", VersionValue::VersionAnyLegacy),
];
for (s, value) in available_version.iter() {
if try_literal(s, reader).is_ok() {
return Ok(Version {
value: value.clone(),
source_info: SourceInfo::new(start.pos, reader.state.pos),
});
}
}
Err(Error::new(start.pos, false, ParseError::Version))
}
Some(' ') => Ok(Version {
value: VersionAny,
source_info: SourceInfo::new(start.pos, reader.state.pos),
}),
_ => Err(Error::new(start.pos, false, ParseError::Version)),
}
}
fn status(reader: &mut Reader) -> ParseResult<Status> {
let start = reader.state.pos;
let value = match natural(reader) {
Ok(value) => match value.try_into() {
Ok(value) => value,
Err(_) => return Err(Error::new(start, false, ParseError::Status)),
},
Err(_) => return Err(Error::new(start, false, ParseError::Status)),
};
let end = reader.state.pos;
Ok(Status {
value,
source_info: SourceInfo { start, end },
})
}
fn body(reader: &mut Reader) -> ParseResult<Body> {
let line_terminators = optional_line_terminators(reader)?;
let space0 = zero_or_more_spaces(reader)?;
let value = bytes(reader)?;
let line_terminator0 = line_terminator(reader)?;
Ok(Body {
line_terminators,
space0,
value,
line_terminator0,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_impostor_file() {
let mut reader = Reader::new("GET http://google.fr\nHTTP 200");
let hurl_file = impostor_file(&mut reader).unwrap();
assert_eq!(hurl_file.entries.len(), 1);
}
#[test]
fn test_entry() {
let mut reader = Reader::new("GET http://google.fr\nHTTP 200");
let e = entry(&mut reader).unwrap();
assert_eq!(e.request.method, Method("GET".to_string()));
assert_eq!(reader.state.cursor, 29);
}
#[test]
fn test_several_entry() {
let mut reader =
Reader::new("GET http://google.fr\nHTTP 200\nGET http://google.fr\nHTTP 200");
let e = entry(&mut reader).unwrap();
assert_eq!(e.request.method, Method("GET".to_string()));
assert_eq!(reader.state.cursor, 30);
assert_eq!(reader.state.pos.line, 3);
let e = entry(&mut reader).unwrap();
assert_eq!(e.request.method, Method("GET".to_string()));
assert_eq!(reader.state.cursor, 59);
assert_eq!(reader.state.pos.line, 4);
let mut reader = Reader::new(
"GET http://google.fr # comment1\nHTTP 200\nGET http://google.fr # comment2\nHTTP 200",
);
let e = entry(&mut reader).unwrap();
assert_eq!(e.request.method, Method("GET".to_string()));
assert_eq!(reader.state.cursor, 41);
assert_eq!(reader.state.pos.line, 3);
let e = entry(&mut reader).unwrap();
assert_eq!(e.request.method, Method("GET".to_string()));
assert_eq!(reader.state.cursor, 81);
assert_eq!(reader.state.pos.line, 4);
}
#[test]
fn test_entry_with_response() {
let mut reader = Reader::new("GET http://google.fr\nHTTP/1.1 200");
let e = entry(&mut reader).unwrap();
assert_eq!(e.request.method, Method("GET".to_string()));
assert_eq!(e.response.status.value, 200);
}
#[test]
fn test_request() {
let mut reader = Reader::new("GET http://google.fr");
let default_request = Request {
line_terminators: vec![],
space0: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
},
method: Method("GET".to_string()),
space1: Whitespace {
value: " ".to_string(),
source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 5)),
},
path: 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, 5), Pos::new(1, 21)),
},
line_terminator0: LineTerminator {
space0: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 21), Pos::new(1, 21)),
},
comment: None,
newline: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 21), Pos::new(1, 21)),
},
},
headers: vec![],
sections: vec![],
body: None,
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 21)),
};
assert_eq!(request(&mut reader).unwrap(), default_request);
assert_eq!(reader.state.cursor, 20);
let mut reader = Reader::new("GET http://google.fr # comment");
let default_request = Request {
line_terminators: vec![],
space0: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
},
method: Method("GET".to_string()),
space1: Whitespace {
value: " ".to_string(),
source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 6)),
},
path: 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, 6), Pos::new(1, 22)),
},
line_terminator0: LineTerminator {
space0: Whitespace {
value: " ".to_string(),
source_info: SourceInfo::new(Pos::new(1, 22), Pos::new(1, 23)),
},
comment: Some(Comment {
value: " comment".to_string(),
source_info: SourceInfo::new(Pos::new(1, 24), Pos::new(1, 32)),
}),
newline: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(1, 32), Pos::new(1, 32)),
},
},
headers: vec![],
sections: vec![],
body: None,
source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 32)),
};
assert_eq!(request(&mut reader).unwrap(), default_request);
assert_eq!(reader.state.cursor, 31);
let mut reader = Reader::new("GET http://google.fr\nGET http://google.fr");
let r = request(&mut reader).unwrap();
assert_eq!(r.method, Method("GET".to_string()));
assert_eq!(reader.state.cursor, 21);
let r = request(&mut reader).unwrap();
assert_eq!(r.method, Method("GET".to_string()));
}
#[test]
fn test_request_multilines() {
let mut reader = Reader::new("GET http://google.fr\n```\nHello World!\n```");
let req = request(&mut reader).unwrap();
assert_eq!(
req.body.unwrap(),
Body {
line_terminators: vec![],
space0: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(2, 1), Pos::new(2, 1)),
},
value: Bytes::MultilineString(MultilineString::Text(Text {
space: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(2, 4), Pos::new(2, 4)),
},
newline: Whitespace {
source_info: SourceInfo::new(Pos::new(2, 4), Pos::new(3, 1)),
value: "\n".to_string(),
},
value: Template {
elements: vec![TemplateElement::String {
value: String::from("Hello World!\n"),
encoded: String::from("Hello World!\n"),
}],
delimiter: None,
source_info: SourceInfo::new(Pos::new(3, 1), Pos::new(4, 1)),
},
})),
line_terminator0: LineTerminator {
space0: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(4, 4), Pos::new(4, 4)),
},
comment: None,
newline: Whitespace {
value: String::new(),
source_info: SourceInfo::new(Pos::new(4, 4), Pos::new(4, 4)),
},
},
}
);
}
#[test]
fn test_request_post_json() {
let mut reader = Reader::new("POST http://localhost:8000/post-json-array\n[1,2,3]");
let r = request(&mut reader).unwrap();
assert_eq!(r.method, Method("POST".to_string()));
assert_eq!(
r.body.unwrap().value,
Bytes::Json(JsonValue::List {
space0: String::new(),
elements: vec![
JsonListElement {
space0: String::new(),
value: JsonValue::Number("1".to_string()),
space1: String::new(),
},
JsonListElement {
space0: String::new(),
value: JsonValue::Number("2".to_string()),
space1: String::new(),
},
JsonListElement {
space0: String::new(),
value: JsonValue::Number("3".to_string()),
space1: String::new(),
},
],
})
);
let mut reader = Reader::new("POST http://localhost:8000/post-json-string\n\"Hello\"");
let r = request(&mut reader).unwrap();
assert_eq!(r.method, Method("POST".to_string()));
assert_eq!(
r.body.unwrap().value,
Bytes::Json(JsonValue::String(Template {
delimiter: Some('"'),
elements: vec![TemplateElement::String {
value: "Hello".to_string(),
encoded: "Hello".to_string(),
}],
source_info: SourceInfo::new(Pos::new(2, 2), Pos::new(2, 7)),
}))
);
let mut reader = Reader::new("POST http://localhost:8000/post-json-number\n100");
let r = request(&mut reader).unwrap();
assert_eq!(r.method, Method("POST".to_string()));
assert_eq!(
r.body.unwrap().value,
Bytes::Json(JsonValue::Number("100".to_string()))
);
}
#[test]
fn test_request_with_headers() {
let mut reader =
Reader::new("GET http://google.fr\nAccept: text/plain\nContent-Type: text/plain");
let r = request(&mut reader).unwrap();
assert_eq!(r.method, Method("GET".to_string()));
assert_eq!(r.headers.len(), 2);
assert_eq!(r.headers[0].key.encoded(), "Accept");
assert_eq!(r.headers[0].value.encoded(), "text/plain");
assert_eq!(r.headers[1].key.encoded(), "Content-Type");
assert_eq!(r.headers[1].value.encoded(), "text/plain");
}
#[test]
fn test_request_with_asserts() {
let mut reader = Reader::new(
"GET /hello\n\
Accept: text/plain\n\
[Asserts]\n\
header \"Content-Type\" == \"text/plain\"\n",
);
let r = request(&mut reader).unwrap();
assert_eq!(r.method, Method("GET".to_string()));
assert_eq!(r.headers.len(), 1);
assert_eq!(r.headers[0].key.encoded(), "Accept");
assert_eq!(r.headers[0].value.encoded(), "text/plain");
assert_eq!(r.sections.len(), 1);
assert_eq!(r.sections[0].name(), "Asserts");
assert_eq!(r.asserts().len(), 1);
if let crate::ast::QueryValue::Header { space0: _, name } = &r.asserts()[0].query.value {
assert_eq!(name.encoded(), r#""Content-Type""#);
} else {
panic!("Not Header");
}
let value = if let crate::ast::PredicateFuncValue::Equal {
space0: _,
value,
operator: _,
} = r.asserts()[0].clone().predicate.predicate_func.value
{
value
} else {
panic!("Not Equal");
};
if let crate::ast::PredicateValue::String(s) = value {
assert_eq!(s.encoded(), r#""text/plain""#);
} else {
panic!("Not Equal");
}
}
#[test]
fn test_request_error() {
let mut reader = Reader::new("xxx");
let error = request(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
}
#[test]
fn test_response() {
let mut reader = Reader::new("HTTP/1.1 200");
let r = response(&mut reader).unwrap();
assert_eq!(r.version.value, VersionValue::Version11);
assert_eq!(r.status.value, 200);
}
#[test]
fn test_method() {
let mut reader = Reader::new("xxx ");
let error = method(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::new("");
let error = method(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(reader.state.cursor, 0);
let mut reader = Reader::new("GET ");
assert_eq!(method(&mut reader).unwrap(), Method("GET".to_string()));
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::new("CUSTOM");
assert_eq!(method(&mut reader).unwrap(), Method("CUSTOM".to_string()));
assert_eq!(reader.state.cursor, 6);
}
#[test]
fn test_version() {
let mut reader = Reader::new("HTTP/1.1 200");
assert_eq!(version(&mut reader).unwrap().value, VersionValue::Version11);
let mut reader = Reader::new("HTTP/1. 200");
let error = version(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
}
#[test]
fn test_status() {
let mut reader = Reader::new("*");
let result = status(&mut reader);
assert!(result.is_err());
let mut reader = Reader::new("200");
let s = status(&mut reader).unwrap();
assert_eq!(s.value, 200);
let mut reader = Reader::new("xxx");
let result = status(&mut reader);
assert!(result.is_err());
}
#[test]
fn test_body_json() {
let mut reader = Reader::new("[1,2,3] ");
let b = body(&mut reader).unwrap();
assert_eq!(b.line_terminators.len(), 0);
assert_eq!(
b.value,
Bytes::Json(JsonValue::List {
space0: String::new(),
elements: vec![
JsonListElement {
space0: String::new(),
value: JsonValue::Number("1".to_string()),
space1: String::new(),
},
JsonListElement {
space0: String::new(),
value: JsonValue::Number("2".to_string()),
space1: String::new(),
},
JsonListElement {
space0: String::new(),
value: JsonValue::Number("3".to_string()),
space1: String::new(),
},
],
})
);
assert_eq!(reader.state.cursor, 8);
let mut reader = Reader::new("{}");
let b = body(&mut reader).unwrap();
assert_eq!(b.line_terminators.len(), 0);
assert_eq!(
b.value,
Bytes::Json(JsonValue::Object {
space0: String::new(),
elements: vec![],
})
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::new("# comment\n {} # comment\nxxx");
let b = body(&mut reader).unwrap();
assert_eq!(b.line_terminators.len(), 1);
assert_eq!(
b.value,
Bytes::Json(JsonValue::Object {
space0: String::new(),
elements: vec![],
})
);
assert_eq!(reader.state.cursor, 24);
let mut reader = Reader::new("{x");
let error = body(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 2 });
assert!(!error.recoverable);
}
#[test]
fn test_basic_get_mock() {
let mock = r#"
GET /hello
HTTP 200
Content-Type: text/plain
"Hello, World!"
"#;
let mut reader = Reader::new(mock);
let impostor_file = impostor_file(&mut reader).unwrap();
assert_eq!(impostor_file.entries.len(), 1);
assert_eq!(reader.state.cursor, mock.len());
println!("{:#?}", impostor_file);
}
}