rustfmt all your existing code!

This commit is contained in:
Fabrice Reix 2020-09-20 08:40:02 +02:00
parent 42c246c7f2
commit 591ea32660
69 changed files with 4847 additions and 2817 deletions

View File

@ -27,7 +27,6 @@ fn main() {
if let Some(rev) = git_revision_hash() {
println!("cargo:rustc-env=HURL_BUILD_GIT_HASH={}", rev);
}
}
fn git_revision_hash() -> Option<String> {

View File

@ -8,6 +8,8 @@ cargo doc --document-private-items
touch src/lib.rs
cargo clippy -- -D warnings
cargo fmt -- --check
echo
echo "!!! Build successful !!!"

View File

@ -5,11 +5,14 @@ set -e
#ROOT_DIR=$(pwd)
rustup component add clippy
rustup component add rustfmt
cargo clippy --version
cargo fmt --version
cargo install cargo-deb
# Python/Flask
sudo apt-get install python3-pip
sudo apt-get install -y python3-pip
python3 -V
pip3 install Flask
(cd integration && python3 server.py&)

View File

@ -19,8 +19,8 @@
use std::collections::HashMap;
use std::env;
use std::fs;
use std::io::{self, Read};
use std::io::prelude::*;
use std::io::{self, Read};
use std::path::{Path, PathBuf};
use atty::Stream;
@ -29,14 +29,13 @@ use clap::{AppSettings, ArgMatches};
use hurl::cli;
use hurl::core::common::FormatError;
use hurl::format;
use hurl::html;
use hurl::http;
use hurl::parser;
use hurl::runner;
use hurl::runner::core::*;
use hurl::runner::log_deserialize;
use hurl::format;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CLIOptions {
@ -53,13 +52,13 @@ pub struct CLIOptions {
pub cookie_input_file: Option<String>,
}
fn execute(filename: &str,
contents: String,
current_dir: &Path,
file_root: Option<String>,
cli_options: CLIOptions,
logger: format::logger::Logger,
fn execute(
filename: &str,
contents: String,
current_dir: &Path,
file_root: Option<String>,
cli_options: CLIOptions,
logger: format::logger::Logger,
) -> HurlResult {
match parser::parse_hurl_file(contents.as_str()) {
Err(e) => {
@ -93,10 +92,16 @@ fn execute(filename: &str,
}
}
if let Some(to_entry) = cli_options.to_entry {
if to_entry < hurl_file.entries.len() {
logger.verbose(format!("executing {}/{} entries", to_entry.to_string(), hurl_file.entries.len()).as_str());
logger.verbose(
format!(
"executing {}/{} entries",
to_entry.to_string(),
hurl_file.entries.len()
)
.as_str(),
);
} else {
logger.verbose("executing all entries");
}
@ -120,7 +125,6 @@ fn execute(filename: &str,
};
let mut client = http::Client::init(options);
let context_dir = match file_root {
None => {
if filename == "-" {
@ -131,9 +135,7 @@ fn execute(filename: &str,
parent.unwrap().to_str().unwrap().to_string()
}
}
Some(filename) => {
filename
}
Some(filename) => filename,
};
let options = RunnerOptions {
@ -141,18 +143,18 @@ fn execute(filename: &str,
variables: cli_options.variables,
to_entry: cli_options.to_entry,
};
runner::file::run(hurl_file,
&mut client,
filename.to_string(),
context_dir,
options,
logger,
runner::file::run(
hurl_file,
&mut client,
filename.to_string(),
context_dir,
options,
logger,
)
}
}
}
fn output_color(matches: ArgMatches) -> bool {
if matches.is_present("color") {
true
@ -163,24 +165,25 @@ fn output_color(matches: ArgMatches) -> bool {
}
}
fn to_entry(matches: ArgMatches, logger: format::logger::Logger) -> Option<usize> {
match matches.value_of("to_entry") {
Some(value) => {
match value.parse() {
Ok(v) => Some(v),
Err(_) => {
logger.error_message("Invalid value for option --to-entry - must be a positive integer!".to_string());
std::process::exit(1);
}
Some(value) => match value.parse() {
Ok(v) => Some(v),
Err(_) => {
logger.error_message(
"Invalid value for option --to-entry - must be a positive integer!".to_string(),
);
std::process::exit(1);
}
}
},
None => None,
}
}
fn json_file(matches: ArgMatches, logger: format::logger::Logger) -> (Vec<HurlResult>, Option<std::path::PathBuf>) {
fn json_file(
matches: ArgMatches,
logger: format::logger::Logger,
) -> (Vec<HurlResult>, Option<std::path::PathBuf>) {
if let Some(filename) = matches.value_of("json") {
let path = Path::new(filename);
@ -191,16 +194,20 @@ fn json_file(matches: ArgMatches, logger: format::logger::Logger) -> (Vec<HurlRe
let v: serde_json::Value = match serde_json::from_str(data.as_str()) {
Ok(v) => v,
Err(_) => {
logger.error_message(format!("The file {} is not a valid json file", path.display()));
logger.error_message(format!(
"The file {} is not a valid json file",
path.display()
));
std::process::exit(127);
}
};
match log_deserialize::parse_results(v) {
Err(msg) => {
logger.error_message(format!("Existing Hurl json can not be parsed! - {}", msg));
logger
.error_message(format!("Existing Hurl json can not be parsed! - {}", msg));
std::process::exit(127);
}
Ok(results) => results
Ok(results) => results,
}
} else {
if matches.is_present("verbose") {
@ -214,13 +221,19 @@ fn json_file(matches: ArgMatches, logger: format::logger::Logger) -> (Vec<HurlRe
}
}
fn html_report(matches: ArgMatches, logger: format::logger::Logger) -> Option<std::path::PathBuf> {
if let Some(dir) = matches.value_of("html_report") {
let path = Path::new(dir);
if std::path::Path::new(&path).exists() {
if !path.read_dir().map(|mut i| i.next().is_none()).unwrap_or(false) {
logger.error_message(format!("Html dir {} already exists and is not empty", path.display()));
if !path
.read_dir()
.map(|mut i| i.next().is_none())
.unwrap_or(false)
{
logger.error_message(format!(
"Html dir {} already exists and is not empty",
path.display()
));
std::process::exit(127)
}
Some(path.to_path_buf())
@ -230,7 +243,7 @@ fn html_report(matches: ArgMatches, logger: format::logger::Logger) -> Option<st
logger.error_message(format!("Html dir {} can not be created", path.display()));
std::process::exit(127)
}
Ok(_) => Some(path.to_path_buf())
Ok(_) => Some(path.to_path_buf()),
}
}
} else {
@ -238,7 +251,6 @@ fn html_report(matches: ArgMatches, logger: format::logger::Logger) -> Option<st
}
}
fn variables(matches: ArgMatches, logger: format::logger::Logger) -> HashMap<String, String> {
let mut variables = HashMap::new();
if matches.is_present("variable") {
@ -262,10 +274,9 @@ fn variables(matches: ArgMatches, logger: format::logger::Logger) -> HashMap<Str
variables
}
fn app() -> clap::App<'static, 'static> {
clap::App::new("hurl")
//.author(clap::crate_authors!())
//.author(clap::crate_authors!())
.version(clap::crate_version!())
.about("Run hurl FILE(s) or standard input")
.setting(AppSettings::DeriveDisplayOrder)
@ -274,12 +285,12 @@ fn app() -> clap::App<'static, 'static> {
clap::Arg::with_name("INPUT")
.help("Sets the input file to use")
.required(false)
.multiple(true)
.multiple(true),
)
.arg(clap::Arg::with_name("append")
.long("append")
.help("Append sessions to json output")
.arg(
clap::Arg::with_name("append")
.long("append")
.help("Append sessions to json output"),
)
.arg(
clap::Arg::with_name("color")
@ -299,26 +310,28 @@ fn app() -> clap::App<'static, 'static> {
.short("c")
.long("cookie-jar")
.value_name("FILE")
.help("Write cookies to FILE after running the session (only for one session)")
.help("Write cookies to FILE after running the session (only for one session)"),
)
.arg(clap::Arg::with_name("fail_at_end")
.long("fail-at-end")
.help("Fail at end")
.takes_value(false)
.arg(
clap::Arg::with_name("fail_at_end")
.long("fail-at-end")
.help("Fail at end")
.takes_value(false),
)
.arg(clap::Arg::with_name("file_root")
.long("file-root")
.value_name("DIR")
.help("set root filesystem to import file in hurl (default is current directory)")
.takes_value(true)
.arg(
clap::Arg::with_name("file_root")
.long("file-root")
.value_name("DIR")
.help("set root filesystem to import file in hurl (default is current directory)")
.takes_value(true),
)
.arg(clap::Arg::with_name("html_report")
.long("html")
.value_name("DIR")
.help("Generate html report to dir")
.takes_value(true)
.arg(
clap::Arg::with_name("html_report")
.long("html")
.value_name("DIR")
.help("Generate html report to dir")
.takes_value(true),
)
.arg(
clap::Arg::with_name("include")
.short("i")
@ -331,14 +344,13 @@ fn app() -> clap::App<'static, 'static> {
.long("insecure")
.help("Allow insecure SSl connections"),
)
.arg(clap::Arg::with_name("json")
.long("json")
.value_name("FILE")
.help("Write full session(s) to json file")
.takes_value(true)
.arg(
clap::Arg::with_name("json")
.long("json")
.value_name("FILE")
.help("Write full session(s) to json file")
.takes_value(true),
)
.arg(
clap::Arg::with_name("follow_location")
.short("L")
@ -358,11 +370,12 @@ fn app() -> clap::App<'static, 'static> {
.conflicts_with("color")
.help("Do not colorize Output"),
)
.arg(clap::Arg::with_name("noproxy")
.long("noproxy")
.value_name("HOST(S)")
.help("List of hosts which do not use proxy")
.takes_value(true)
.arg(
clap::Arg::with_name("noproxy")
.long("noproxy")
.value_name("HOST(S)")
.help("List of hosts which do not use proxy")
.takes_value(true),
)
.arg(
clap::Arg::with_name("output")
@ -378,19 +391,21 @@ fn app() -> clap::App<'static, 'static> {
.value_name("[PROTOCOL://]HOST[:PORT]")
.help("Use proxy on given protocol/host/port"),
)
.arg(clap::Arg::with_name("to_entry")
.long("to-entry")
.value_name("ENTRY_NUMBER")
.help("Execute hurl file to ENTRY_NUMBER (starting at 1)")
.takes_value(true)
.arg(
clap::Arg::with_name("to_entry")
.long("to-entry")
.value_name("ENTRY_NUMBER")
.help("Execute hurl file to ENTRY_NUMBER (starting at 1)")
.takes_value(true),
)
.arg(clap::Arg::with_name("variable")
.long("variable")
.value_name("NAME=VALUE")
.multiple(true)
.number_of_values(1)
.help("Define a variable")
.takes_value(true)
.arg(
clap::Arg::with_name("variable")
.long("variable")
.value_name("NAME=VALUE")
.multiple(true)
.number_of_values(1)
.help("Define a variable")
.takes_value(true),
)
.arg(
clap::Arg::with_name("verbose")
@ -400,7 +415,6 @@ fn app() -> clap::App<'static, 'static> {
)
}
pub fn unwrap_or_exit<T>(result: Result<T, cli::Error>, logger: format::logger::Logger) -> T {
match result {
Ok(v) => v,
@ -411,8 +425,10 @@ pub fn unwrap_or_exit<T>(result: Result<T, cli::Error>, logger: format::logger::
}
}
fn parse_options(matches: ArgMatches, logger: format::logger::Logger) -> Result<CLIOptions, cli::Error> {
fn parse_options(
matches: ArgMatches,
logger: format::logger::Logger,
) -> Result<CLIOptions, cli::Error> {
let verbose = matches.is_present("verbose");
let color = output_color(matches.clone());
let fail_fast = !matches.is_present("fail_at_end");
@ -426,12 +442,14 @@ fn parse_options(matches: ArgMatches, logger: format::logger::Logger) -> Result<
let max_redirect = match matches.value_of("max_redirects") {
None => Some(50),
Some("-1") => None,
Some(s) => {
match s.parse::<usize>() {
Ok(x) => Some(x),
Err(_) => return Err(cli::Error{ message: "max_redirs option can not be parsed".to_string() })
Some(s) => match s.parse::<usize>() {
Ok(x) => Some(x),
Err(_) => {
return Err(cli::Error {
message: "max_redirs option can not be parsed".to_string(),
})
}
}
},
};
Ok(CLIOptions {
@ -449,20 +467,20 @@ fn parse_options(matches: ArgMatches, logger: format::logger::Logger) -> Result<
})
}
fn main() -> Result<(), cli::Error> {
let app = app();
let matches = app.clone().get_matches();
let mut filenames = match matches.values_of("INPUT") {
None => vec![],
Some(v) => v.collect()
Some(v) => v.collect(),
};
if filenames.is_empty() && atty::is(Stream::Stdin) {
if app.clone().print_help().is_err() {
std::process::exit(1);
} else {}
} else {
}
} else if filenames.is_empty() {
filenames.push("-");
}
@ -472,7 +490,7 @@ fn main() -> Result<(), cli::Error> {
let file_root = match matches.value_of("file_root") {
Some(value) => Some(value.to_string()),
_ => None
_ => None,
};
let logger = format::logger::Logger {
@ -482,18 +500,23 @@ fn main() -> Result<(), cli::Error> {
color: output_color(matches.clone()),
};
let (mut hurl_results, json_file) = json_file(matches.clone(), logger.clone());
let html_report = html_report(matches.clone(), logger.clone());
let cookies_output_file = match matches.value_of("cookies_output_file") {
None => None,
Some(filename) => {
let filename = unwrap_or_exit(cli::options::cookies_output_file(filename.to_string(), filenames.len()), logger.clone());
let filename = unwrap_or_exit(
cli::options::cookies_output_file(filename.to_string(), filenames.len()),
logger.clone(),
);
Some(filename)
}
};
let cli_options = unwrap_or_exit(parse_options(matches.clone(), logger.clone()), logger.clone());
let cli_options = unwrap_or_exit(
parse_options(matches.clone(), logger.clone()),
logger.clone(),
);
for filename in filenames {
let contents = if filename == "-" {
@ -521,7 +544,6 @@ fn main() -> Result<(), cli::Error> {
color: cli_options.color,
};
let hurl_result = execute(
filename,
contents,
@ -537,7 +559,14 @@ fn main() -> Result<(), cli::Error> {
if let Some(entry_result) = hurl_result.entries.last() {
if let Some(response) = entry_result.response.clone() {
if matches.is_present("include") {
logger.info(format!("HTTP/{} {}", response.version.to_string(), response.status.to_string()).as_str());
logger.info(
format!(
"HTTP/{} {}",
response.version.to_string(),
response.status.to_string()
)
.as_str(),
);
for header in response.headers.clone() {
logger.info(format!("{}: {}", header.name, header.value).as_str());
}
@ -549,26 +578,39 @@ fn main() -> Result<(), cli::Error> {
logger.warning_message("no response has been received".to_string());
}
} else {
logger.warning_message(format!("warning: no entry have been executed {}", if filename == "-" { "".to_string() } else { format!("for file {}", filename) }));
logger.warning_message(format!(
"warning: no entry have been executed {}",
if filename == "-" {
"".to_string()
} else {
format!("for file {}", filename)
}
));
};
}
hurl_results.push(hurl_result.clone());
}
if let Some(file_path) = json_file {
let mut file = match std::fs::File::create(&file_path) {
Err(why) => {
logger.error_message(format!("Issue writing to {}: {:?}", file_path.display(), why));
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
}
Ok(file) => file,
};
let serialized = serde_json::to_string_pretty(&hurl_results).unwrap();
if let Err(why) = file.write_all(serialized.as_bytes()) {
logger.error_message(format!("Issue writing to {}: {:?}", file_path.display(), why));
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
}
}
@ -586,14 +628,20 @@ fn main() -> Result<(), cli::Error> {
std::process::exit(exit_code(hurl_results));
}
fn exit_code(hurl_results: Vec<HurlResult>) -> i32 {
let mut count_errors_runner = 0;
let mut count_errors_assert = 0;
for hurl_result in hurl_results.clone() {
let runner_errors: Vec<runner::core::Error> = hurl_result.clone().errors().iter().filter(|e| !e.assert).cloned().collect();
let runner_errors: Vec<runner::core::Error> = hurl_result
.clone()
.errors()
.iter()
.filter(|e| !e.assert)
.cloned()
.collect();
if hurl_result.clone().errors().is_empty() {} else if runner_errors.is_empty() {
if hurl_result.clone().errors().is_empty() {
} else if runner_errors.is_empty() {
count_errors_assert += 1;
} else {
count_errors_runner += 1;
@ -614,7 +662,8 @@ fn write_output(bytes: Vec<u8>, filename: Option<&str>, logger: format::logger::
let stdout = io::stdout();
let mut handle = stdout.lock();
handle.write_all(bytes.as_slice())
handle
.write_all(bytes.as_slice())
.expect("writing bytes to console");
}
Some(filename) => {
@ -626,16 +675,24 @@ fn write_output(bytes: Vec<u8>, filename: Option<&str>, logger: format::logger::
}
Ok(file) => file,
};
file.write_all(bytes.as_slice()).expect("writing bytes to file");
file.write_all(bytes.as_slice())
.expect("writing bytes to file");
}
}
}
fn write_cookies_file(file_path: PathBuf, hurl_results: Vec<HurlResult>, logger: format::logger::Logger) {
fn write_cookies_file(
file_path: PathBuf,
hurl_results: Vec<HurlResult>,
logger: format::logger::Logger,
) {
let mut file = match std::fs::File::create(&file_path) {
Err(why) => {
logger.error_message(format!("Issue writing to {}: {:?}", file_path.display(), why));
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
}
Ok(file) => file,
@ -643,7 +700,8 @@ fn write_cookies_file(file_path: PathBuf, hurl_results: Vec<HurlResult>, logger:
let mut s = r#"# Netscape HTTP Cookie File
# This file was generated by hurl
"#.to_string();
"#
.to_string();
match hurl_results.first() {
None => {
logger.error_message("Issue fetching results".to_string());
@ -658,14 +716,21 @@ fn write_cookies_file(file_path: PathBuf, hurl_results: Vec<HurlResult>, logger:
}
if let Err(why) = file.write_all(s.as_bytes()) {
logger.error_message(format!("Issue writing to {}: {:?}", file_path.display(), why));
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
}
}
fn write_html_report(dir_path: PathBuf, hurl_results: Vec<HurlResult>, logger: format::logger::Logger) {
//let now: DateTime<Utc> = Utc::now();
fn write_html_report(
dir_path: PathBuf,
hurl_results: Vec<HurlResult>,
logger: format::logger::Logger,
) {
//let now: DateTime<Utc> = Utc::now();
let now: DateTime<Local> = Local::now();
let html = create_html_index(now.to_rfc2822(), hurl_results);
let s = html.render();
@ -673,32 +738,46 @@ fn write_html_report(dir_path: PathBuf, hurl_results: Vec<HurlResult>, logger: f
let file_path = dir_path.join("index.html");
let mut file = match std::fs::File::create(&file_path) {
Err(why) => {
logger.error_message(format!("Issue writing to {}: {:?}", file_path.display(), why));
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
}
Ok(file) => file,
};
if let Err(why) = file.write_all(s.as_bytes()) {
logger.error_message(format!("Issue writing to {}: {:?}", file_path.display(), why));
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
}
let file_path = dir_path.join("report.css");
let mut file = match std::fs::File::create(&file_path) {
Err(why) => {
logger.error_message(format!("Issue writing to {}: {:?}", file_path.display(), why));
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
}
Ok(file) => file,
};
if let Err(why) = file.write_all(include_bytes!("report.css")) {
logger.error_message(format!("Issue writing to {}: {:?}", file_path.display(), why));
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
}
}
fn create_html_index(now: String, hurl_results: Vec<HurlResult>) -> html::ast::Html {
let head = html::ast::Head {
title: "Hurl Report".to_string(),
@ -710,27 +789,22 @@ fn create_html_index(now: String, hurl_results: Vec<HurlResult>) -> html::ast::H
html::ast::Element::NodeElement {
name: "h2".to_string(),
attributes: vec![],
children: vec![
html::ast::Element::TextElement("Hurl Report".to_string())
],
children: vec![html::ast::Element::TextElement("Hurl Report".to_string())],
},
html::ast::Element::NodeElement {
name: "div".to_string(),
attributes: vec![
html::ast::Attribute::Class("date".to_string())
],
children: vec![
html::ast::Element::TextElement(now)
],
}, html::ast::Element::NodeElement {
attributes: vec![html::ast::Attribute::Class("date".to_string())],
children: vec![html::ast::Element::TextElement(now)],
},
html::ast::Element::NodeElement {
name: "table".to_string(),
attributes: vec![],
children: vec![
create_html_table_header(),
create_html_table_body(hurl_results),
],
}
]
},
],
};
html::ast::Html { head, body }
}
@ -739,28 +813,22 @@ fn create_html_table_header() -> html::ast::Element {
html::ast::Element::NodeElement {
name: "thead".to_string(),
attributes: vec![],
children: vec![
html::ast::Element::NodeElement {
name: "tr".to_string(),
attributes: vec![],
children: vec![
html::ast::Element::NodeElement {
name: "td".to_string(),
attributes: vec![],
children: vec![
html::ast::Element::TextElement("filename".to_string())
],
},
html::ast::Element::NodeElement {
name: "td".to_string(),
attributes: vec![],
children: vec![
html::ast::Element::TextElement("duration".to_string())
],
}
],
}
],
children: vec![html::ast::Element::NodeElement {
name: "tr".to_string(),
attributes: vec![],
children: vec![
html::ast::Element::NodeElement {
name: "td".to_string(),
attributes: vec![],
children: vec![html::ast::Element::TextElement("filename".to_string())],
},
html::ast::Element::NodeElement {
name: "td".to_string(),
attributes: vec![],
children: vec![html::ast::Element::TextElement("duration".to_string())],
},
],
}],
}
}
@ -777,7 +845,6 @@ fn create_html_table_body(hurl_results: Vec<HurlResult>) -> html::ast::Element {
}
}
fn create_html_result(result: HurlResult) -> html::ast::Element {
let status = if result.success {
"success".to_string()
@ -790,20 +857,17 @@ fn create_html_result(result: HurlResult) -> html::ast::Element {
children: vec![
html::ast::Element::NodeElement {
name: "td".to_string(),
attributes: vec![
html::ast::Attribute::Class(status)
],
children: vec![
html::ast::Element::TextElement(result.filename.clone())
],
attributes: vec![html::ast::Attribute::Class(status)],
children: vec![html::ast::Element::TextElement(result.filename.clone())],
},
html::ast::Element::NodeElement {
name: "td".to_string(),
attributes: vec![],
children: vec![
html::ast::Element::TextElement(format!("{}s", result.time_in_ms as f64 / 1000.0))
],
}
children: vec![html::ast::Element::TextElement(format!(
"{}s",
result.time_in_ms as f64 / 1000.0
))],
},
],
}
}

View File

@ -18,8 +18,8 @@
extern crate clap;
use std::fs;
use std::io::{self, Read};
use std::io::Write;
use std::io::{self, Read};
use std::path::Path;
use std::process;

View File

@ -17,7 +17,6 @@
*/
pub mod options;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub message: String,

View File

@ -21,7 +21,7 @@ use super::Error;
pub fn cookies_output_file(filename: String, n: usize) -> Result<std::path::PathBuf, Error> {
if n > 1 {
Err(Error {
message: "Only save cookies for a unique session".to_string()
message: "Only save cookies for a unique session".to_string(),
})
} else {
let path = std::path::Path::new(&filename);
@ -29,7 +29,6 @@ pub fn cookies_output_file(filename: String, n: usize) -> Result<std::path::Path
}
}
pub fn output_color(color_present: bool, no_color_present: bool, stdout: bool) -> bool {
if color_present {
true
@ -40,9 +39,6 @@ pub fn output_color(color_present: bool, no_color_present: bool, stdout: bool) -
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -52,6 +48,4 @@ mod tests {
assert_eq!(output_color(true, false, true), true);
assert_eq!(output_color(false, false, true), true);
}
}

View File

@ -139,7 +139,7 @@ impl Method {
Method::Connect => "CONNECT",
Method::Options => "OPTIONS",
Method::Trace => "TRACE",
Method::Patch => "PATCH"
Method::Patch => "PATCH",
}
}
}
@ -177,7 +177,6 @@ impl VersionValue {
type Header = KeyValue;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Body {
pub line_terminators: Vec<LineTerminator>,
@ -207,7 +206,7 @@ impl Section {
SectionValue::FormParams(_) => "FormParams",
SectionValue::Cookies(_) => "Cookies",
SectionValue::Captures(_) => "Captures",
SectionValue::MultipartFormData(_) => "MultipartFormData"
SectionValue::MultipartFormData(_) => "MultipartFormData",
}
}
}
@ -275,7 +274,6 @@ pub struct FileValue {
pub content_type: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Capture {
pub line_terminators: Vec<LineTerminator>,
@ -361,24 +359,21 @@ pub enum CookieAttributeName {
SameSite(String),
}
impl CookieAttributeName {
pub fn value(&self) -> String {
match self {
CookieAttributeName::Value(value) |
CookieAttributeName::Expires(value) |
CookieAttributeName::MaxAge(value) |
CookieAttributeName::Domain(value) |
CookieAttributeName::Path(value) |
CookieAttributeName::Secure(value) |
CookieAttributeName::HttpOnly(value) |
CookieAttributeName::SameSite(value)
=> value.to_string()
CookieAttributeName::Value(value)
| CookieAttributeName::Expires(value)
| CookieAttributeName::MaxAge(value)
| CookieAttributeName::Domain(value)
| CookieAttributeName::Path(value)
| CookieAttributeName::Secure(value)
| CookieAttributeName::HttpOnly(value)
| CookieAttributeName::SameSite(value) => value.to_string(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Subquery {
pub source_info: SourceInfo,
@ -387,10 +382,7 @@ pub struct Subquery {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SubqueryValue {
Regex {
space0: Whitespace,
expr: Template,
},
Regex { space0: Whitespace, expr: Template },
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -415,68 +407,22 @@ pub struct PredicateFunc {
#[derive(Clone, Debug, PartialEq, Eq)]
#[allow(clippy::large_enum_variant)]
pub enum PredicateFuncValue {
EqualString {
space0: Whitespace,
value: Template,
},
EqualInt {
space0: Whitespace,
value: i64,
},
EqualFloat {
space0: Whitespace,
value: Float,
},
EqualBool {
space0: Whitespace,
value: bool,
},
EqualNull {
space0: Whitespace
},
EqualExpression {
space0: Whitespace,
value: Expr
},
CountEqual {
space0: Whitespace,
value: u64,
},
StartWith {
space0: Whitespace,
value: Template,
},
Contain {
space0: Whitespace,
value: Template,
},
IncludeString {
space0: Whitespace,
value: Template,
},
IncludeInt {
space0: Whitespace,
value: i64,
},
IncludeFloat {
space0: Whitespace,
value: Float,
},
IncludeBool {
space0: Whitespace,
value: bool,
},
IncludeNull {
space0: Whitespace,
},
IncludeExpression {
space0: Whitespace,
value: Expr,
},
Match {
space0: Whitespace,
value: Template,
},
EqualString { space0: Whitespace, value: Template },
EqualInt { space0: Whitespace, value: i64 },
EqualFloat { space0: Whitespace, value: Float },
EqualBool { space0: Whitespace, value: bool },
EqualNull { space0: Whitespace },
EqualExpression { space0: Whitespace, value: Expr },
CountEqual { space0: Whitespace, value: u64 },
StartWith { space0: Whitespace, value: Template },
Contain { space0: Whitespace, value: Template },
IncludeString { space0: Whitespace, value: Template },
IncludeInt { space0: Whitespace, value: i64 },
IncludeFloat { space0: Whitespace, value: Float },
IncludeBool { space0: Whitespace, value: bool },
IncludeNull { space0: Whitespace },
IncludeExpression { space0: Whitespace, value: Expr },
Match { space0: Whitespace, value: Template },
Exist {},
}
@ -513,7 +459,6 @@ pub struct EncodedString {
pub source_info: SourceInfo,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Whitespace {
pub value: String,
@ -544,17 +489,19 @@ pub struct Float {
pub int: i64,
pub decimal: u64,
// use 18 digits
pub decimal_digits: usize, // number of digits
pub decimal_digits: usize, // number of digits
}
impl fmt::Display for Float {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let decimal_str: String = format!("{:018}", self.decimal).chars().take(self.decimal_digits).collect();
let decimal_str: String = format!("{:018}", self.decimal)
.chars()
.take(self.decimal_digits)
.collect();
write!(f, "{}.{}", self.int, decimal_str)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct LineTerminator {
pub space0: Whitespace,
@ -613,9 +560,41 @@ mod tests {
#[test]
fn test_float() {
assert_eq!(Float { int: 1, decimal: 0, decimal_digits: 1 }.to_string(), "1.0");
assert_eq!(Float { int: 1, decimal: 10_000_000_000_000_000, decimal_digits: 2 }.to_string(), "1.01");
assert_eq!(Float { int: 1, decimal: 10_000_000_000_000_000, decimal_digits: 3 }.to_string(), "1.010");
assert_eq!(Float { int: -1, decimal: 333_333_333_333_333_333, decimal_digits: 3 }.to_string(), "-1.333");
assert_eq!(
Float {
int: 1,
decimal: 0,
decimal_digits: 1,
}
.to_string(),
"1.0"
);
assert_eq!(
Float {
int: 1,
decimal: 10_000_000_000_000_000,
decimal_digits: 2,
}
.to_string(),
"1.01"
);
assert_eq!(
Float {
int: 1,
decimal: 10_000_000_000_000_000,
decimal_digits: 3,
}
.to_string(),
"1.010"
);
assert_eq!(
Float {
int: -1,
decimal: 333_333_333_333_333_333,
decimal_digits: 3,
}
.to_string(),
"-1.333"
);
}
}

View File

@ -17,8 +17,8 @@
*/
use std::fmt;
use serde::Serialize;
use serde::ser::Serializer;
use serde::Serialize;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DeprecatedValue {
@ -85,9 +85,12 @@ impl Value {
}
}
pub fn from_f64(value: f64) -> Value {
let integer = if value < 0.0 { value.ceil() as i64 } else { value.floor() as i64 };
let integer = if value < 0.0 {
value.ceil() as i64
} else {
value.floor() as i64
};
let decimal = (value.abs().fract() * 1_000_000_000_000_000_000.0).round() as u64;
Value::Float(integer, decimal)
}
@ -103,20 +106,17 @@ impl Value {
match value {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(bool) => Value::Bool(*bool),
serde_json::Value::Number(n) =>
serde_json::Value::Number(n) => {
if n.is_f64() {
Value::from_f64(n.as_f64().unwrap())
} else {
Value::Integer(n.as_i64().unwrap())
}
,
}
serde_json::Value::String(s) => Value::String(s.to_string()),
serde_json::Value::Array(elements) =>
Value::List(elements
.iter()
.map(|e| Value::from_json(e))
.collect())
,
serde_json::Value::Array(elements) => {
Value::List(elements.iter().map(|e| Value::from_json(e)).collect())
}
serde_json::Value::Object(map) => {
let mut elements = vec![];
for (key, value) in map {
@ -129,7 +129,6 @@ impl Value {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Pos {
pub line: usize,
@ -168,11 +167,10 @@ pub trait FormatError {
fn fixme(&self) -> String;
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
match self {
Value::Bool(v) => serializer.serialize_bool(*v),
@ -188,7 +186,7 @@ impl Serialize for Value {
let size = *size as i64;
serializer.collect_map(vec![
("type", serde_json::Value::String("nodeset".to_string())),
("size", serde_json::Value::from(size))
("size", serde_json::Value::from(size)),
])
}
Value::Bytes(v) => {
@ -200,7 +198,6 @@ impl Serialize for Value {
}
}
impl Value {
pub fn to_json_value(&self) -> (String, serde_json::Value) {
match self.clone() {
@ -211,10 +208,7 @@ impl Value {
("float".to_string(), serde_json::Value::from(value))
}
Value::String(v) => ("string".to_string(), serde_json::Value::String(v)),
Value::List(_) => (
"list".to_string(),
serde_json::Value::Array(vec![])
),
Value::List(_) => ("list".to_string(), serde_json::Value::Array(vec![])),
Value::Object(_) => todo!(),
Value::Nodeset(_) => todo!(),
Value::Bytes(_) => todo!(),
@ -240,7 +234,6 @@ impl Value {
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -249,17 +242,38 @@ mod tests {
fn test_from_f64() {
assert_eq!(Value::from_f64(1.0), Value::Float(1, 0));
assert_eq!(Value::from_f64(-1.0), Value::Float(-1, 0));
assert_eq!(Value::from_f64(1.1), Value::Float(1, 100_000_000_000_000_096)); //TBC!!
assert_eq!(Value::from_f64(-1.1), Value::Float(-1, 100_000_000_000_000_096));
assert_eq!(Value::from_f64(1.5), Value::Float(1, 500_000_000_000_000_000));
assert_eq!(
Value::from_f64(1.1),
Value::Float(1, 100_000_000_000_000_096)
); //TBC!!
assert_eq!(
Value::from_f64(-1.1),
Value::Float(-1, 100_000_000_000_000_096)
);
assert_eq!(
Value::from_f64(1.5),
Value::Float(1, 500_000_000_000_000_000)
);
}
#[test]
fn test_from_json() {
assert_eq!(Value::from_json(&serde_json::Value::String("hello".to_string())), Value::String("hello".to_string()));
assert_eq!(Value::from_json(&serde_json::Value::Bool(true)), Value::Bool(true));
assert_eq!(Value::from_json(&serde_json::Value::from(1)), Value::Integer(1));
assert_eq!(Value::from_json(&serde_json::Value::from(1.5)), Value::Float(1, 500_000_000_000_000_000));
assert_eq!(
Value::from_json(&serde_json::Value::String("hello".to_string())),
Value::String("hello".to_string())
);
assert_eq!(
Value::from_json(&serde_json::Value::Bool(true)),
Value::Bool(true)
);
assert_eq!(
Value::from_json(&serde_json::Value::from(1)),
Value::Integer(1)
);
assert_eq!(
Value::from_json(&serde_json::Value::from(1.5)),
Value::Float(1, 500_000_000_000_000_000)
);
}
#[test]
@ -271,17 +285,48 @@ mod tests {
#[test]
fn test_serialize() {
assert_eq!(serde_json::to_string(&Value::Bool(true)).unwrap(), "true");
assert_eq!(serde_json::to_string(&Value::String("hello".to_string())).unwrap(), "\"hello\"");
assert_eq!(
serde_json::to_string(&Value::String("hello".to_string())).unwrap(),
"\"hello\""
);
assert_eq!(serde_json::to_string(&Value::Integer(1)).unwrap(), "1");
assert_eq!(serde_json::to_string(&Value::Float(1, 500_000_000_000_000_000)).unwrap(), "1.5");
assert_eq!(serde_json::to_string(&Value::Float(1, 100_000_000_000_000_000)).unwrap(), "1.1");
assert_eq!(serde_json::to_string(&Value::Float(1, 100_000_000_000_000_096)).unwrap(), "1.1");
assert_eq!(serde_json::to_string(&Value::List(vec![Value::Integer(1), Value::Integer(2), Value::Integer(3)])).unwrap(), "[1,2,3]");
assert_eq!(serde_json::to_string(&Value::Object(vec![
("name".to_string(), Value::String("Bob".to_string()))
])).unwrap(), r#"{"name":"Bob"}"#);
assert_eq!(serde_json::to_string(&Value::Nodeset(4)).unwrap(), r#"{"type":"nodeset","size":4}"#);
assert_eq!(serde_json::to_string(&Value::Bytes(vec![65])).unwrap(), r#""QQ==""#);
assert_eq!(
serde_json::to_string(&Value::Float(1, 500_000_000_000_000_000)).unwrap(),
"1.5"
);
assert_eq!(
serde_json::to_string(&Value::Float(1, 100_000_000_000_000_000)).unwrap(),
"1.1"
);
assert_eq!(
serde_json::to_string(&Value::Float(1, 100_000_000_000_000_096)).unwrap(),
"1.1"
);
assert_eq!(
serde_json::to_string(&Value::List(vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(3)
]))
.unwrap(),
"[1,2,3]"
);
assert_eq!(
serde_json::to_string(&Value::Object(vec![(
"name".to_string(),
Value::String("Bob".to_string())
)]))
.unwrap(),
r#"{"name":"Bob"}"#
);
assert_eq!(
serde_json::to_string(&Value::Nodeset(4)).unwrap(),
r#"{"type":"nodeset","size":4}"#
);
assert_eq!(
serde_json::to_string(&Value::Bytes(vec![65])).unwrap(),
r#""QQ==""#
);
assert_eq!(serde_json::to_string(&Value::Null {}).unwrap(), "null");
}
}

View File

@ -22,8 +22,14 @@ pub enum Value {
Number(String),
String(Template),
Boolean(bool),
List { space0: String, elements: Vec<ListElement> },
Object { space0: String, elements: Vec<ObjectElement> },
List {
space0: String,
elements: Vec<ListElement>,
},
Object {
space0: String,
elements: Vec<ObjectElement>,
},
Null {},
}
@ -57,35 +63,30 @@ pub struct ObjectElement {
pub space3: String,
}
#[cfg(test)]
pub mod tests {
use super::*;
use super::super::ast::{Expr, TemplateElement, Variable, Whitespace};
use super::super::common::SourceInfo;
use super::*;
pub fn person_value() -> Value {
Value::Object {
space0: "\n ".to_string(),
elements: vec![
ObjectElement {
space0: "".to_string(),
name: "firstName".to_string(),
space1: "".to_string(),
space2: " ".to_string(),
value: Value::String(Template {
quotes: false,
elements: vec![
TemplateElement::String {
value: "John".to_string(),
encoded: "John".to_string(),
}
],
source_info: SourceInfo::init(1, 1, 1, 1),
}),
space3: "\n".to_string(),
},
],
elements: vec![ObjectElement {
space0: "".to_string(),
name: "firstName".to_string(),
space1: "".to_string(),
space2: " ".to_string(),
value: Value::String(Template {
quotes: false,
elements: vec![TemplateElement::String {
value: "John".to_string(),
encoded: "John".to_string(),
}],
source_info: SourceInfo::init(1, 1, 1, 1),
}),
space3: "\n".to_string(),
}],
}
}
@ -99,9 +100,18 @@ pub mod tests {
encoded: "Hello\\u0020".to_string(),
},
TemplateElement::Expression(Expr {
space0: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 15, 1, 15) },
variable: Variable { name: "name".to_string(), source_info: SourceInfo::init(1, 15, 1, 19) },
space1: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 19, 1, 19) },
space0: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 15, 1, 15),
},
variable: Variable {
name: "name".to_string(),
source_info: SourceInfo::init(1, 15, 1, 19),
},
space1: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 19, 1, 19),
},
}),
TemplateElement::String {
value: "!".to_string(),

View File

@ -31,7 +31,6 @@ pub struct Error {
pub color: bool,
}
impl Error {
pub fn format(self) -> String {
let mut s = "".to_string();
@ -65,23 +64,27 @@ impl Error {
self.filename,
self.source_info.start.line,
self.source_info.start.column,
).as_str(),
)
.as_str(),
);
}
s.push_str(format!("{} |\n", " ".repeat(line_number_size)).as_str());
let line = self.lines.get(self.source_info.start.line - 1).unwrap();
let line = str::replace(line, "\t", " "); // replace all your tabs with 4 characters
let line = str::replace(line, "\t", " "); // replace all your tabs with 4 characters
s.push_str(
format!(
"{line_number:>width$} |{line}\n",
line_number = self.source_info.start.line,
width = line_number_size,
line = if line.is_empty() { line } else { format!(" {}", line) }
line = if line.is_empty() {
line
} else {
format!(" {}", line)
}
)
.as_str(),
.as_str(),
);
// TODO: to clean/Refacto
@ -99,7 +102,7 @@ impl Error {
" ".repeat(line_number_size).as_str(),
fixme = line,
)
.as_str(),
.as_str(),
);
}
} else {
@ -108,7 +111,9 @@ impl Error {
let mut tab_shift = 0;
for (i, c) in line.chars().enumerate() {
if i >= self.source_info.start.column - 1 { break; };
if i >= self.source_info.start.column - 1 {
break;
};
if c == '\t' {
tab_shift += 1;
}
@ -121,18 +126,16 @@ impl Error {
"^".repeat(if width > 1 { width } else { 1 }),
fixme = self.fixme.as_str(),
)
.as_str(),
.as_str(),
);
}
s.push_str(format!("{} |\n", " ".repeat(line_number_size)).as_str());
s
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -140,9 +143,7 @@ mod tests {
#[test]
fn test_basic() {
let filename = String::from("integration/hurl_error_lint/spaces.hurl");
let lines = vec![
String::from("GET\thttp://localhost:8000/hello")
];
let lines = vec![String::from("GET\thttp://localhost:8000/hello")];
let error = Error {
source_info: SourceInfo::init(1, 4, 1, 5),
description: String::from("One space"),
@ -152,23 +153,24 @@ mod tests {
warning: true,
color: false,
};
assert_eq!(error.format(),
String::from(r#"warning: One space
assert_eq!(
error.format(),
String::from(
r#"warning: One space
--> integration/hurl_error_lint/spaces.hurl:1:4
|
1 | GET http://localhost:8000/hello
| ^ Use only one space
|
"#)
"#
)
);
}
#[test]
fn test_with_tabs() {
let filename = String::from("integration/hurl_error_lint/spaces.hurl");
let lines = vec![
String::from("GET\thttp://localhost:8000/hello ")
];
let lines = vec![String::from("GET\thttp://localhost:8000/hello ")];
let error = Error {
source_info: SourceInfo::init(1, 32, 1, 32),
description: String::from("Unnecessary space"),
@ -178,28 +180,28 @@ mod tests {
warning: true,
color: false,
};
assert_eq!(error.format(),
concat!(
"warning: Unnecessary space\n",
" --> integration/hurl_error_lint/spaces.hurl:1:32\n",
" |\n",
" 1 | GET http://localhost:8000/hello \n",
" | ^ Remove space\n",
" |\n")
assert_eq!(
error.format(),
concat!(
"warning: Unnecessary space\n",
" --> integration/hurl_error_lint/spaces.hurl:1:32\n",
" |\n",
" 1 | GET http://localhost:8000/hello \n",
" | ^ Remove space\n",
" |\n"
)
);
}
#[test]
fn test_end_of_file() {
// todo: improve error location
let filename = String::from("hurl_error_parser/json_unexpected_eof.hurl");
let lines = vec![
String::from("POST http://localhost:8000/data\n"),
String::from("{ \"name\":\n"),
String::from("")
String::from(""),
];
let error = Error {
source_info: SourceInfo::init(3, 1, 3, 1),
@ -210,14 +212,17 @@ mod tests {
warning: true,
color: false,
};
assert_eq!(error.format(),
String::from(r#"warning: Parsing json
assert_eq!(
error.format(),
String::from(
r#"warning: Parsing json
--> hurl_error_parser/json_unexpected_eof.hurl:3:1
|
3 |
| ^ json error
|
"#)
"#
)
);
}
@ -227,7 +232,7 @@ mod tests {
let lines = vec![
String::from("...\n"),
String::from("[Asserts]\n"),
String::from("jsonpath \"$.message\" startsWith \"hello\"")
String::from("jsonpath \"$.message\" startsWith \"hello\""),
];
let _error = Error {
source_info: SourceInfo::init(3, 0, 3, 0),

View File

@ -41,7 +41,11 @@ pub fn format_standalone(hurl_file: HurlFile) -> String {
}
pub fn format(hurl_file: HurlFile, standalone: bool) -> String {
if standalone { format_standalone(hurl_file) } else { hurl_file.to_html() }
if standalone {
format_standalone(hurl_file)
} else {
hurl_file.to_html()
}
}
impl Htmlable for HurlFile {
@ -399,7 +403,9 @@ impl Htmlable for PredicateFuncValue {
PredicateFuncValue::EqualString { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">equals</span>");
buffer.push_str(space0.to_html().as_str());
buffer.push_str(format!("<span class=\"number\">{}</span>", value.to_html()).as_str());
buffer.push_str(
format!("<span class=\"number\">{}</span>", value.to_html()).as_str(),
);
}
PredicateFuncValue::EqualInt { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">equals</span>");
@ -409,7 +415,9 @@ impl Htmlable for PredicateFuncValue {
PredicateFuncValue::EqualFloat { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">equals</span>");
buffer.push_str(space0.to_html().as_str());
buffer.push_str(format!("<span class=\"number\">{}</span>", value.to_string()).as_str());
buffer.push_str(
format!("<span class=\"number\">{}</span>", value.to_string()).as_str(),
);
}
PredicateFuncValue::EqualExpression { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">equals</span>");
@ -419,17 +427,23 @@ impl Htmlable for PredicateFuncValue {
PredicateFuncValue::StartWith { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">startsWith</span>");
buffer.push_str(space0.to_html().as_str());
buffer.push_str(format!("<span class=\"string\">{}</span>", value.to_html()).as_str());
buffer.push_str(
format!("<span class=\"string\">{}</span>", value.to_html()).as_str(),
);
}
PredicateFuncValue::Contain { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">contains</span>");
buffer.push_str(space0.to_html().as_str());
buffer.push_str(format!("<span class=\"string\">{}</span>", value.to_html()).as_str());
buffer.push_str(
format!("<span class=\"string\">{}</span>", value.to_html()).as_str(),
);
}
PredicateFuncValue::IncludeString { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">includes</span>");
buffer.push_str(space0.to_html().as_str());
buffer.push_str(format!("<span class=\"string\">{}</span>", value.to_html()).as_str());
buffer.push_str(
format!("<span class=\"string\">{}</span>", value.to_html()).as_str(),
);
}
PredicateFuncValue::IncludeInt { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">includes</span>");
@ -449,7 +463,9 @@ impl Htmlable for PredicateFuncValue {
PredicateFuncValue::IncludeFloat { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">includes</span>");
buffer.push_str(space0.to_html().as_str());
buffer.push_str(format!("<span class=\"number\">{}</span>", value.to_string()).as_str());
buffer.push_str(
format!("<span class=\"number\">{}</span>", value.to_string()).as_str(),
);
}
PredicateFuncValue::IncludeExpression { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">includes</span>");
@ -459,7 +475,9 @@ impl Htmlable for PredicateFuncValue {
PredicateFuncValue::Match { space0, value } => {
buffer.push_str("<span class=\"predicate-type\">matches</span>");
buffer.push_str(space0.to_html().as_str());
buffer.push_str(format!("<span class=\"string\">{}</span>", value.to_html()).as_str());
buffer.push_str(
format!("<span class=\"string\">{}</span>", value.to_html()).as_str(),
);
}
PredicateFuncValue::EqualNull { space0 } => {
buffer.push_str("<span class=\"predicate-type\">equals</span>");

View File

@ -16,8 +16,8 @@
*
*/
use super::error::Error;
use super::color::TerminalColor;
use super::error::Error;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Logger {
@ -27,9 +27,7 @@ pub struct Logger {
pub color: bool,
}
impl Logger {
pub fn info(&self, s: &str) {
println!("{}", s);
}
@ -105,12 +103,16 @@ impl Logger {
eprintln!("{} |", " ".repeat(line_number_size));
let line = self.lines.get(err.source_info.start.line - 1).unwrap();
let line = str::replace(line, "\t", " "); // replace all your tabs with 4 characters
let line = str::replace(line, "\t", " "); // replace all your tabs with 4 characters
eprintln!(
"{line_number:>width$} |{line}",
line_number = err.source_info.start.line,
width = line_number_size,
line = if line.is_empty() { line } else { format!(" {}", line) }
line = if line.is_empty() {
line
} else {
format!(" {}", line)
}
);
// TODO: to clean/Refacto
@ -134,7 +136,9 @@ impl Logger {
let mut tab_shift = 0;
for (i, c) in line.chars().enumerate() {
if i >= err.source_info.start.column - 1 { break; };
if i >= err.source_info.start.column - 1 {
break;
};
if c == '\t' {
tab_shift += 1;
}
@ -150,4 +154,4 @@ impl Logger {
eprintln!("{} |\n", " ".repeat(line_number_size));
}
}
}

View File

@ -18,6 +18,6 @@
pub mod color;
pub mod error;
pub mod html;
pub mod logger;
pub mod text;
pub mod token;
pub mod logger;

View File

@ -15,8 +15,8 @@
* limitations under the License.
*
*/
use super::color::TerminalColor;
use super::super::core::ast::*;
use super::color::TerminalColor;
use super::token::*;
pub fn format(hurl_file: HurlFile, color: bool) -> String {

View File

@ -75,7 +75,9 @@ impl Tokenizable for Entry {
fn tokenize(&self) -> Vec<Token> {
let mut tokens: Vec<Token> = vec![];
add_tokens(&mut tokens, self.request.tokenize());
if let Some(response) = self.clone().response { add_tokens(&mut tokens, response.tokenize()) }
if let Some(response) = self.clone().response {
add_tokens(&mut tokens, response.tokenize())
}
tokens
}
}
@ -103,7 +105,9 @@ impl Tokenizable for Request {
&mut tokens,
self.sections.iter().flat_map(|e| e.tokenize()).collect(),
);
if let Some(body) = self.clone().body { add_tokens(&mut tokens, body.tokenize()) }
if let Some(body) = self.clone().body {
add_tokens(&mut tokens, body.tokenize())
}
tokens
}
}
@ -131,7 +135,9 @@ impl Tokenizable for Response {
&mut tokens,
self.sections.iter().flat_map(|e| e.tokenize()).collect(),
);
if let Some(body) = self.clone().body { add_tokens(&mut tokens, body.tokenize()) }
if let Some(body) = self.clone().body {
add_tokens(&mut tokens, body.tokenize())
}
tokens
}
}
@ -177,7 +183,7 @@ impl Tokenizable for Bytes {
Bytes::Xml { value } => {
tokens.push(Token::String(value.to_string()));
}
// Bytes::MultilineString { value: _ } => {}
// Bytes::MultilineString { value: _ } => {}
Bytes::RawString { newline0, value } => {
tokens.push(Token::Keyword(String::from("```")));
add_tokens(&mut tokens, newline0.tokenize());
@ -384,7 +390,9 @@ impl Tokenizable for Capture {
add_tokens(&mut tokens, self.space2.tokenize());
add_tokens(&mut tokens, self.query.tokenize());
add_tokens(&mut tokens, self.space3.tokenize());
if let Some(subquery) = self.clone().subquery { add_tokens(&mut tokens, subquery.tokenize()) }
if let Some(subquery) = self.clone().subquery {
add_tokens(&mut tokens, subquery.tokenize())
}
add_tokens(&mut tokens, self.line_terminator0.tokenize());
tokens
}
@ -605,12 +613,16 @@ impl Tokenizable for EncodedString {
fn tokenize(&self) -> Vec<Token> {
let mut tokens: Vec<Token> = vec![];
if self.quotes {
tokens.push(Token::Quote(if self.clone().quotes { "\"" } else { "" }.to_string()));
tokens.push(Token::Quote(
if self.clone().quotes { "\"" } else { "" }.to_string(),
));
}
tokens.push(Token::String(self.encoded.clone()));
if self.quotes {
tokens.push(Token::Quote(if self.clone().quotes { "\"" } else { "" }.to_string()));
tokens.push(Token::Quote(
if self.clone().quotes { "\"" } else { "" }.to_string(),
));
}
tokens
}
@ -620,14 +632,18 @@ impl Tokenizable for Template {
fn tokenize(&self) -> Vec<Token> {
let mut tokens: Vec<Token> = vec![];
if self.quotes {
tokens.push(Token::Quote(if self.clone().quotes { "\"" } else { "" }.to_string()));
tokens.push(Token::Quote(
if self.clone().quotes { "\"" } else { "" }.to_string(),
));
}
for element in self.elements.clone() {
add_tokens(&mut tokens, element.tokenize());
}
if self.quotes {
tokens.push(Token::Quote(if self.clone().quotes { "\"" } else { "" }.to_string()));
tokens.push(Token::Quote(
if self.clone().quotes { "\"" } else { "" }.to_string(),
));
}
tokens
}

View File

@ -28,10 +28,9 @@ pub struct Head {
pub stylesheet: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Body {
pub children: Vec<Element>
pub children: Vec<Element>,
}
#[derive(Clone, Debug, PartialEq, Eq)]

View File

@ -19,7 +19,11 @@ use super::ast::*;
impl Html {
pub fn render(self) -> String {
format!("<!DOCTYPE html>\n<html>{}{}</html>", self.head.render(), self.body.render())
format!(
"<!DOCTYPE html>\n<html>{}{}</html>",
self.head.render(),
self.body.render()
)
}
}
@ -28,7 +32,13 @@ impl Head {
let mut s = "".to_string();
s.push_str(format!("<title>{}</title>", self.title).as_str());
if let Some(filename) = self.stylesheet {
s.push_str(format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">", filename).as_str());
s.push_str(
format!(
"<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">",
filename
)
.as_str(),
);
}
format!("<head>{}</head>", s)
}
@ -44,16 +54,27 @@ impl Body {
impl Element {
fn render(self) -> String {
match self {
Element::NodeElement { name, children, attributes } => {
Element::NodeElement {
name,
children,
attributes,
} => {
let attributes = if attributes.is_empty() {
"".to_string()
} else {
format!(" {}", attributes.iter().map(|a| a.clone().render()).collect::<Vec<String>>().join(""))
format!(
" {}",
attributes
.iter()
.map(|a| a.clone().render())
.collect::<Vec<String>>()
.join("")
)
};
let children: Vec<String> = children.iter().map(|e| e.clone().render()).collect();
format!("<{}{}>{}</{}>", name, attributes, children.join(""), name)
}
Element::TextElement(s) => s
Element::TextElement(s) => s,
}
}
}
@ -67,24 +88,22 @@ impl Attribute {
}
}
#[cfg(test)]
mod tests {
use super::*;
pub fn sample_html() -> Html {
Html {
head: Head { title: "This is a title".to_string(), stylesheet: None },
head: Head {
title: "This is a title".to_string(),
stylesheet: None,
},
body: Body {
children: vec![
Element::NodeElement {
name: "p".to_string(),
attributes: vec![],
children: vec![
Element::TextElement("Hello world!".to_string())
],
}
]
children: vec![Element::NodeElement {
name: "p".to_string(),
attributes: vec![],
children: vec![Element::TextElement("Hello world!".to_string())],
}],
},
}
}
@ -97,15 +116,16 @@ mod tests {
pub fn sample_div() -> Element {
Element::NodeElement {
name: "div".to_string(),
attributes: vec![
Attribute::Class("request".to_string())
],
attributes: vec![Attribute::Class("request".to_string())],
children: vec![],
}
}
#[test]
fn test_render_div() {
assert_eq!(sample_div().render(), "<div class=\"request\"></div>".to_string());
assert_eq!(
sample_div().render(),
"<div class=\"request\"></div>".to_string()
);
}
}

View File

@ -19,10 +19,9 @@
// https://goessner.net/articles/JsonPath/
// https://jsonpath.com/
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Query {
pub selectors: Vec<Selector>
pub selectors: Vec<Selector>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -39,7 +38,6 @@ pub struct Predicate {
pub func: PredicateFunc,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PredicateFunc {
KeyExist {},
@ -51,7 +49,6 @@ pub enum PredicateFunc {
LessThanOrEqual(Number),
}
// Number
// - without rounding
// - Equalable

View File

@ -21,12 +21,14 @@ use super::ast::*;
pub type JsonpathResult = Vec<serde_json::Value>;
impl Query {
pub fn eval(self, value: serde_json::Value) -> JsonpathResult {
let mut results = vec![value];
for selector in self.selectors {
results = results.iter().flat_map(|value| selector.clone().eval(value.clone())).collect();
results = results
.iter()
.flat_map(|value| selector.clone().eval(value.clone()))
.collect();
}
results
}
@ -35,29 +37,22 @@ impl Query {
impl Selector {
pub fn eval(self, root: serde_json::Value) -> JsonpathResult {
match self {
Selector::NameChild(field) =>
match root.get(field) {
None => vec![],
Some(value) => vec![value.clone()],
}
,
Selector::ArrayIndex(index) =>
match root.get(index) {
None => vec![],
Some(value) => vec![value.clone()],
}
Selector::Filter(predicate) => {
match root {
serde_json::Value::Array(elements) => {
elements
.iter()
.filter(|&e| predicate.eval(e.clone()))
.cloned()
.collect()
}
_ => vec![],
}
}
Selector::NameChild(field) => match root.get(field) {
None => vec![],
Some(value) => vec![value.clone()],
},
Selector::ArrayIndex(index) => match root.get(index) {
None => vec![],
Some(value) => vec![value.clone()],
},
Selector::Filter(predicate) => match root {
serde_json::Value::Array(elements) => elements
.iter()
.filter(|&e| predicate.eval(e.clone()))
.cloned()
.collect(),
_ => vec![],
},
Selector::RecursiveKey(key) => {
let mut elements = vec![];
match root {
@ -68,14 +63,14 @@ impl Selector {
for value in obj.values() {
for element in Selector::RecursiveKey(key.clone()).eval(value.clone()) {
elements.push(element);
};
}
}
}
serde_json::Value::Array(values) => {
for value in values {
for element in Selector::RecursiveKey(key.clone()).eval(value.clone()) {
elements.push(element);
};
}
}
}
_ => {}
@ -89,22 +84,36 @@ impl Selector {
impl Predicate {
pub fn eval(&self, elem: serde_json::Value) -> bool {
match elem {
serde_json::Value::Object(ref obj) => match (obj.get(self.key.as_str()), self.func.clone()) {
(Some(serde_json::Value::Number(v)), PredicateFunc::Equal(ref num))
=> approx_eq!(f64, v.as_f64().unwrap(), num.to_f64(), ulps = 2), //v.as_f64().unwrap() == num.to_f64(),
(Some(serde_json::Value::Number(v)), PredicateFunc::GreaterThan(ref num)) => v.as_f64().unwrap() > num.to_f64(),
(Some(serde_json::Value::Number(v)), PredicateFunc::GreaterThanOrEqual(ref num)) => v.as_f64().unwrap() >= num.to_f64(),
(Some(serde_json::Value::Number(v)), PredicateFunc::LessThan(ref num)) => v.as_f64().unwrap() < num.to_f64(),
(Some(serde_json::Value::Number(v)), PredicateFunc::LessThanOrEqual(ref num)) => v.as_f64().unwrap() <= num.to_f64(),
(Some(serde_json::Value::String(v)), PredicateFunc::EqualString(ref s)) => v == s,
_ => false,
},
serde_json::Value::Object(ref obj) => {
match (obj.get(self.key.as_str()), self.func.clone()) {
(Some(serde_json::Value::Number(v)), PredicateFunc::Equal(ref num)) => {
approx_eq!(f64, v.as_f64().unwrap(), num.to_f64(), ulps = 2)
} //v.as_f64().unwrap() == num.to_f64(),
(Some(serde_json::Value::Number(v)), PredicateFunc::GreaterThan(ref num)) => {
v.as_f64().unwrap() > num.to_f64()
}
(
Some(serde_json::Value::Number(v)),
PredicateFunc::GreaterThanOrEqual(ref num),
) => v.as_f64().unwrap() >= num.to_f64(),
(Some(serde_json::Value::Number(v)), PredicateFunc::LessThan(ref num)) => {
v.as_f64().unwrap() < num.to_f64()
}
(
Some(serde_json::Value::Number(v)),
PredicateFunc::LessThanOrEqual(ref num),
) => v.as_f64().unwrap() <= num.to_f64(),
(Some(serde_json::Value::String(v)), PredicateFunc::EqualString(ref s)) => {
v == s
}
_ => false,
}
}
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
@ -112,23 +121,24 @@ mod tests {
use super::*;
pub fn json_root() -> serde_json::Value {
json!({
"store": json_store()
})
json!({ "store": json_store() })
}
pub fn json_store() -> serde_json::Value {
json!({
"book": json_books(),
"bicycle": [
"book": json_books(),
"bicycle": [
]
})
]
})
}
pub fn json_books() -> serde_json::Value {
json!([
json_first_book(), json_second_book(), json_third_book(), json_fourth_book()
json_first_book(),
json_second_book(),
json_third_book(),
json_fourth_book()
])
}
@ -143,36 +153,43 @@ mod tests {
pub fn json_second_book() -> serde_json::Value {
json!( { "category": "fiction",
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
})
"author": "Evelyn Waugh",
"title": "Sword of Honour",
"price": 12.99
})
}
pub fn json_third_book() -> serde_json::Value {
json!( { "category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
})
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
})
}
pub fn json_fourth_book() -> serde_json::Value {
json!({ "category": "fiction",
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
})
"author": "J. R. R. Tolkien",
"title": "The Lord of the Rings",
"isbn": "0-395-19395-8",
"price": 22.99
})
}
#[test]
pub fn test_query() {
assert_eq!(Query { selectors: vec![] }.eval(json_root()), vec![json_root()]);
assert_eq!(
Query { selectors: vec![] }.eval(json_root()),
vec![json_root()]
);
assert_eq!(Query { selectors: vec![Selector::NameChild("store".to_string())] }.eval(json_root()),
vec![json_store()]
assert_eq!(
Query {
selectors: vec![Selector::NameChild("store".to_string())]
}
.eval(json_root()),
vec![json_store()]
);
let query = Query {
@ -181,10 +198,11 @@ mod tests {
Selector::NameChild("book".to_string()),
Selector::ArrayIndex(0),
Selector::NameChild("title".to_string()),
]
],
};
assert_eq!(query.eval(json_root()),
vec![json!("Sayings of the Century")]
assert_eq!(
query.eval(json_root()),
vec![json!("Sayings of the Century")]
);
// $.store.book[?(@.price<10)].title
@ -194,60 +212,74 @@ mod tests {
Selector::NameChild("book".to_string()),
Selector::Filter(Predicate {
key: "price".to_string(),
func: PredicateFunc::LessThan(Number { int: 10, decimal: 0 }),
func: PredicateFunc::LessThan(Number {
int: 10,
decimal: 0,
}),
}),
Selector::NameChild("title".to_string()),
]
],
};
assert_eq!(query.eval(json_root()),
vec![json!("Sayings of the Century"), json!("Moby Dick")]
assert_eq!(
query.eval(json_root()),
vec![json!("Sayings of the Century"), json!("Moby Dick")]
);
// $..author
let query = Query {
selectors: vec![
Selector::RecursiveKey("author".to_string())
]
selectors: vec![Selector::RecursiveKey("author".to_string())],
};
assert_eq!(query.eval(json_root()),
vec![json!("Nigel Rees"), json!("Evelyn Waugh"), json!("Herman Melville"), json!("J. R. R. Tolkien")]
assert_eq!(
query.eval(json_root()),
vec![
json!("Nigel Rees"),
json!("Evelyn Waugh"),
json!("Herman Melville"),
json!("J. R. R. Tolkien")
]
);
}
#[test]
pub fn test_bookstore() {
// assert_eq!(Selector::NameChild("store".to_string()).eval(json_root()),
// vec![json_store()]
// );
// assert_eq!(Selector::NameChild("book".to_string()).eval(json_store()),
// vec![json_books()]
// );
//
// assert_eq!(Selector::ArrayIndex(0).eval(json_books()),
// vec![json_first_book()]
// );
//
// assert_eq!(Selector::NameChild("title".to_string()).eval(json_first_book()),
// vec![json!("Sayings of the Century")]
// );
//
// assert_eq!(Selector::ArrayIndex(0).eval(json_books()),
// vec![json_first_book()]
// );
// assert_eq!(Selector::Filter(Predicate::KeyEqualStringValue("category".to_string(), "reference".to_string())).eval(json_books()),
// vec![json_first_book()]
// );
//
// assert_eq!(Selector::Filter(Predicate::KeySmallerThanIntValue("price".to_string(), 10)).eval(json_books()),
// vec![json_first_book(), json_third_book()]
// );
//
// assert_eq!(Selector::RecursiveKey("book".to_string()).eval(json_root()),
// vec![json_books()]
// );
// assert_eq!(Selector::NameChild("store".to_string()).eval(json_root()),
// vec![json_store()]
// );
// assert_eq!(Selector::NameChild("book".to_string()).eval(json_store()),
// vec![json_books()]
// );
//
// assert_eq!(Selector::ArrayIndex(0).eval(json_books()),
// vec![json_first_book()]
// );
//
// assert_eq!(Selector::NameChild("title".to_string()).eval(json_first_book()),
// vec![json!("Sayings of the Century")]
// );
//
// assert_eq!(Selector::ArrayIndex(0).eval(json_books()),
// vec![json_first_book()]
// );
// assert_eq!(Selector::Filter(Predicate::KeyEqualStringValue("category".to_string(), "reference".to_string())).eval(json_books()),
// vec![json_first_book()]
// );
//
// assert_eq!(Selector::Filter(Predicate::KeySmallerThanIntValue("price".to_string(), 10)).eval(json_books()),
// vec![json_first_book(), json_third_book()]
// );
//
// assert_eq!(Selector::RecursiveKey("book".to_string()).eval(json_root()),
// vec![json_books()]
// );
assert_eq!(Selector::RecursiveKey("author".to_string()).eval(json_root()),
vec![json!("Nigel Rees"), json!("Evelyn Waugh"), json!("Herman Melville"), json!("J. R. R. Tolkien")]
assert_eq!(
Selector::RecursiveKey("author".to_string()).eval(json_root()),
vec![
json!("Nigel Rees"),
json!("Evelyn Waugh"),
json!("Herman Melville"),
json!("J. R. R. Tolkien")
]
);
}
@ -255,41 +287,66 @@ mod tests {
#[test]
pub fn test_array_index() {
let value = json!(["first", "second", "third", "forth", "fifth"]);
assert_eq!(Selector::ArrayIndex(2).eval(value),
vec![json!("third")]
);
assert_eq!(Selector::ArrayIndex(2).eval(value), vec![json!("third")]);
}
#[test]
pub fn test_predicate() {
assert_eq!(Predicate {
key: "key".to_string(),
func: PredicateFunc::EqualString("value".to_string()),
}.eval(json!({"key": "value"})), true);
assert_eq!(
Predicate {
key: "key".to_string(),
func: PredicateFunc::EqualString("value".to_string()),
}
.eval(json!({"key": "value"})),
true
);
assert_eq!(Predicate {
key: "key".to_string(),
func: PredicateFunc::EqualString("value".to_string()),
}.eval(json!({"key": "some"})), false);
assert_eq!(
Predicate {
key: "key".to_string(),
func: PredicateFunc::EqualString("value".to_string()),
}
.eval(json!({"key": "some"})),
false
);
assert_eq!(Predicate {
key: "key".to_string(),
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
}.eval(json!({"key": 1})), true);
assert_eq!(
Predicate {
key: "key".to_string(),
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
}
.eval(json!({"key": 1})),
true
);
assert_eq!(Predicate {
key: "key".to_string(),
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
}.eval(json!({"key": 2})), false);
assert_eq!(
Predicate {
key: "key".to_string(),
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
}
.eval(json!({"key": 2})),
false
);
assert_eq!(Predicate {
key: "key".to_string(),
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
}.eval(json!({"key": "1"})), false);
assert_eq!(
Predicate {
key: "key".to_string(),
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
}
.eval(json!({"key": "1"})),
false
);
assert_eq!(Predicate {
key: "key".to_string(),
func: PredicateFunc::LessThan(Number { int: 10, decimal: 0 }),
}.eval(json!({"key": 1})), true);
assert_eq!(
Predicate {
key: "key".to_string(),
func: PredicateFunc::LessThan(Number {
int: 10,
decimal: 0
}),
}
.eval(json!({"key": 1})),
true
);
}
}

View File

@ -15,16 +15,14 @@
* limitations under the License.
*
*/
use super::{ParseFunc, ParseResult};
use super::error::*;
use super::reader::Reader;
use super::{ParseFunc, ParseResult};
pub fn optional<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'a, Option<T>> {
let start = p.state.clone();
match f(p) {
Ok(r) => {
Ok(Some(r))
}
Ok(r) => Ok(Some(r)),
Err(e) => {
if e.recoverable {
p.state = start;
@ -41,33 +39,24 @@ pub fn optional<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'a, O
pub fn recover<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'a, T> {
// let start = p.state.clone();
match f(p) {
Ok(r) => {
Ok(r)
}
Err(e) => {
Err(Error {
pos: e.pos,
recoverable: true,
inner: e.inner,
})
}
Ok(r) => Ok(r),
Err(e) => Err(Error {
pos: e.pos,
recoverable: true,
inner: e.inner,
}),
}
}
pub fn nonrecover<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'a, T> {
//let start = p.state.clone();
match f(p) {
Ok(r) => {
Ok(r)
}
Err(e) => {
Err(Error {
pos: e.pos,
recoverable: false,
inner: e.inner,
})
}
Ok(r) => Ok(r),
Err(e) => Err(Error {
pos: e.pos,
recoverable: false,
inner: e.inner,
}),
}
}
@ -98,7 +87,6 @@ pub fn zero_or_more<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'
}
}
pub fn one_or_more<'a, T>(f: ParseFunc<'a, T>, reader: &mut Reader) -> ParseResult<'a, Vec<T>> {
let _initial_state = reader.state.clone();
match f(reader) {
@ -144,18 +132,19 @@ pub fn choice<'a, T>(fs: Vec<ParseFunc<'a, T>>, p: &mut Reader) -> ParseResult<'
f(p)
} else {
match f(p) {
Err(Error { recoverable: true, .. }) => {
Err(Error {
recoverable: true, ..
}) => {
p.state = start;
choice(fs.clone().into_iter().skip(1).collect(), p)
}
x => x
x => x,
}
}
}
}
}
pub fn peek<T>(f: ParseFunc<T>, p: Reader) -> ParseResult<T> {
let start = p.state.clone();
let mut p = p;
@ -174,4 +163,3 @@ pub fn peek<T>(f: ParseFunc<T>, p: Reader) -> ParseResult<T> {
}
}
}

View File

@ -19,10 +19,10 @@ use error::Error;
use reader::Reader;
pub mod combinators;
pub mod parse;
pub mod error;
pub mod reader;
pub mod parse;
pub mod primitives;
pub mod reader;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Pos {

View File

@ -15,12 +15,12 @@
* limitations under the License.
*
*/
use super::super::ast::*;
use super::combinators::*;
use super::error::{Error, ParseError};
use super::ParseResult;
use super::primitives::*;
use super::reader::Reader;
use super::super::ast::*;
use super::ParseResult;
pub fn parse(s: &str) -> Result<Query, Error> {
let mut reader = Reader::init(s);
@ -30,39 +30,43 @@ pub fn parse(s: &str) -> Result<Query, Error> {
fn query(reader: &mut Reader) -> ParseResult<Query> {
literal("$", reader)?;
let selectors = zero_or_more(selector, reader)?;
if !reader.is_eof() {
return Err(Error {
pos: reader.state.pos.clone(),
recoverable: false,
inner: ParseError::Expecting { value: "eof".to_string() },
inner: ParseError::Expecting {
value: "eof".to_string(),
},
});
}
Ok(Query {
selectors
})
Ok(Query { selectors })
}
fn selector(reader: &mut Reader) -> ParseResult<Selector> {
choice(vec![
selector_recursive_key,
selector_array_index,
selector_object_key_bracket,
selector_object_key,
selector_filter,
], reader)
choice(
vec![
selector_recursive_key,
selector_array_index,
selector_object_key_bracket,
selector_object_key,
selector_filter,
],
reader,
)
}
fn selector_array_index(reader: &mut Reader) -> Result<Selector, Error> {
try_literal("[", reader)?;
let i = match natural(reader) {
Err(e) => return Err(Error {
pos: e.pos,
recoverable: true,
inner: e.inner,
}),
Ok(v) => v
Err(e) => {
return Err(Error {
pos: e.pos,
recoverable: true,
inner: e.inner,
})
}
Ok(v) => v,
};
literal("]", reader)?;
Ok(Selector::ArrayIndex(i))
@ -81,7 +85,9 @@ fn selector_object_key_bracket(reader: &mut Reader) -> Result<Selector, Error> {
Err(_) => Err(Error {
pos: reader.state.pos.clone(),
recoverable: true,
inner: ParseError::Expecting { value: "value string".to_string() },
inner: ParseError::Expecting {
value: "value string".to_string(),
},
}),
Ok(v) => {
literal("]", reader)?;
@ -99,7 +105,9 @@ fn selector_object_key(reader: &mut Reader) -> Result<Selector, Error> {
return Err(Error {
pos: reader.state.pos.clone(),
recoverable: true,
inner: ParseError::Expecting { value: "[ or .".to_string() },
inner: ParseError::Expecting {
value: "[ or .".to_string(),
},
});
};
@ -108,7 +116,9 @@ fn selector_object_key(reader: &mut Reader) -> Result<Selector, Error> {
return Err(Error {
pos: reader.state.pos.clone(),
recoverable: false,
inner: ParseError::Expecting { value: "empty value".to_string() },
inner: ParseError::Expecting {
value: "empty value".to_string(),
},
});
}
if !end_delim.is_empty() {
@ -134,7 +144,7 @@ fn predicate(reader: &mut Reader) -> ParseResult<Predicate> {
// @.key Exist(Key)
// @.key==value Equal(Key,Value)
// @.key>=value GreaterThanOrEqual(Key, Value)
literal("@.", reader)?; // assume key value for the time being
literal("@.", reader)?; // assume key value for the time being
let key = key_name(reader)?;
let func = predicate_func(reader)?;
Ok(Predicate { key, func })
@ -148,7 +158,7 @@ fn predicate_func(reader: &mut Reader) -> ParseResult<PredicateFunc> {
greater_than_or_equal_predicate_func,
less_than_predicate_func,
less_than_or_equal_predicate_func,
equal_string_predicate_func
equal_string_predicate_func,
],
reader,
)
@ -196,29 +206,27 @@ fn equal_string_predicate_func(reader: &mut Reader) -> ParseResult<PredicateFunc
Ok(PredicateFunc::EqualString(s))
}
#[cfg(test)]
mod tests {
// tests from https://cburgmer.github.io/json-path-comparison
use super::*;
use super::super::Pos;
use super::*;
#[test]
pub fn test_query() {
let expected_query = Query {
selectors: vec![
Selector::ArrayIndex(2)
]
selectors: vec![Selector::ArrayIndex(2)],
};
assert_eq!(query(&mut Reader::init("$[2]")).unwrap(), expected_query);
let expected_query = Query {
selectors: vec![
Selector::NameChild("key".to_string())
]
selectors: vec![Selector::NameChild("key".to_string())],
};
assert_eq!(query(&mut Reader::init("$['key']")).unwrap(), expected_query);
assert_eq!(
query(&mut Reader::init("$['key']")).unwrap(),
expected_query
);
assert_eq!(query(&mut Reader::init("$.key")).unwrap(), expected_query);
let expected_query = Query {
@ -227,19 +235,27 @@ mod tests {
Selector::NameChild("book".to_string()),
Selector::ArrayIndex(0),
Selector::NameChild("title".to_string()),
]
],
};
assert_eq!(query(&mut Reader::init("$.store.book[0].title")).unwrap(), expected_query);
assert_eq!(query(&mut Reader::init("$['store']['book'][0]['title']")).unwrap(), expected_query);
assert_eq!(
query(&mut Reader::init("$.store.book[0].title")).unwrap(),
expected_query
);
assert_eq!(
query(&mut Reader::init("$['store']['book'][0]['title']")).unwrap(),
expected_query
);
let expected_query = Query {
selectors: vec![
Selector::RecursiveKey("book".to_string()),
Selector::ArrayIndex(2),
]
],
};
assert_eq!(query(&mut Reader::init("$..book[2]")).unwrap(), expected_query);
assert_eq!(
query(&mut Reader::init("$..book[2]")).unwrap(),
expected_query
);
}
#[test]
@ -253,7 +269,6 @@ mod tests {
#[test]
pub fn test_selector() {
// Array index
let mut reader = Reader::init("[2]");
assert_eq!(selector(&mut reader).unwrap(), Selector::ArrayIndex(2));
@ -261,85 +276,136 @@ mod tests {
// Key bracket notation
let mut reader = Reader::init("['key']");
assert_eq!(selector(&mut reader).unwrap(), Selector::NameChild("key".to_string()));
assert_eq!(
selector(&mut reader).unwrap(),
Selector::NameChild("key".to_string())
);
assert_eq!(reader.state.cursor, 7);
let mut reader = Reader::init("['key1']");
assert_eq!(selector(&mut reader).unwrap(), Selector::NameChild("key1".to_string()));
assert_eq!(
selector(&mut reader).unwrap(),
Selector::NameChild("key1".to_string())
);
assert_eq!(reader.state.cursor, 8);
// Key dot notation
let mut reader = Reader::init(".key");
assert_eq!(selector(&mut reader).unwrap(), Selector::NameChild("key".to_string()));
assert_eq!(
selector(&mut reader).unwrap(),
Selector::NameChild("key".to_string())
);
assert_eq!(reader.state.cursor, 4);
let mut reader = Reader::init(".key1");
assert_eq!(selector(&mut reader).unwrap(), Selector::NameChild("key1".to_string()));
assert_eq!(
selector(&mut reader).unwrap(),
Selector::NameChild("key1".to_string())
);
assert_eq!(reader.state.cursor, 5);
// Filter equal on string with single quotes
let mut reader = Reader::init("[?(@.key=='value')]");
assert_eq!(selector(&mut reader).unwrap(), Selector::Filter(Predicate {
key: "key".to_string(),
func: PredicateFunc::EqualString("value".to_string()),
}));
assert_eq!(
selector(&mut reader).unwrap(),
Selector::Filter(Predicate {
key: "key".to_string(),
func: PredicateFunc::EqualString("value".to_string()),
})
);
assert_eq!(reader.state.cursor, 19);
let mut reader = Reader::init("..book");
assert_eq!(selector(&mut reader).unwrap(), Selector::RecursiveKey("book".to_string()));
assert_eq!(
selector(&mut reader).unwrap(),
Selector::RecursiveKey("book".to_string())
);
assert_eq!(reader.state.cursor, 6);
}
#[test]
pub fn test_predicate() {
// Filter equal on string with single quotes
assert_eq!(predicate(&mut Reader::init("@.key=='value'")).unwrap(),
Predicate {
key: "key".to_string(),
func: PredicateFunc::EqualString("value".to_string()),
});
assert_eq!(
predicate(&mut Reader::init("@.key=='value'")).unwrap(),
Predicate {
key: "key".to_string(),
func: PredicateFunc::EqualString("value".to_string()),
}
);
// Filter equal on int
assert_eq!(predicate(&mut Reader::init("@.key==1")).unwrap(),
Predicate {
key: "key".to_string(),
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
});
assert_eq!(
predicate(&mut Reader::init("@.key==1")).unwrap(),
Predicate {
key: "key".to_string(),
func: PredicateFunc::Equal(Number { int: 1, decimal: 0 }),
}
);
}
#[test]
pub fn test_predicate_func() {
let mut reader = Reader::init("==2");
assert_eq!(predicate_func(&mut reader).unwrap(), PredicateFunc::Equal(Number { int: 2, decimal: 0 }));
assert_eq!(
predicate_func(&mut reader).unwrap(),
PredicateFunc::Equal(Number { int: 2, decimal: 0 })
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("==2.1");
assert_eq!(predicate_func(&mut reader).unwrap(), PredicateFunc::Equal(Number { int: 2, decimal: 100_000_000_000_000_000 }));
assert_eq!(
predicate_func(&mut reader).unwrap(),
PredicateFunc::Equal(Number {
int: 2,
decimal: 100_000_000_000_000_000
})
);
assert_eq!(reader.state.cursor, 5);
let mut reader = Reader::init("== 2.1 ");
assert_eq!(predicate_func(&mut reader).unwrap(), PredicateFunc::Equal(Number { int: 2, decimal: 100_000_000_000_000_000 }));
assert_eq!(
predicate_func(&mut reader).unwrap(),
PredicateFunc::Equal(Number {
int: 2,
decimal: 100_000_000_000_000_000
})
);
assert_eq!(reader.state.cursor, 7);
let mut reader = Reader::init("=='hello'");
assert_eq!(predicate_func(&mut reader).unwrap(), PredicateFunc::EqualString("hello".to_string()));
assert_eq!(
predicate_func(&mut reader).unwrap(),
PredicateFunc::EqualString("hello".to_string())
);
assert_eq!(reader.state.cursor, 9);
let mut reader = Reader::init(">5");
assert_eq!(predicate_func(&mut reader).unwrap(), PredicateFunc::GreaterThan(Number { int: 5, decimal: 0 }));
assert_eq!(
predicate_func(&mut reader).unwrap(),
PredicateFunc::GreaterThan(Number { int: 5, decimal: 0 })
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init(">=5");
assert_eq!(predicate_func(&mut reader).unwrap(), PredicateFunc::GreaterThanOrEqual(Number { int: 5, decimal: 0 }));
assert_eq!(
predicate_func(&mut reader).unwrap(),
PredicateFunc::GreaterThanOrEqual(Number { int: 5, decimal: 0 })
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("<5");
assert_eq!(predicate_func(&mut reader).unwrap(), PredicateFunc::LessThan(Number { int: 5, decimal: 0 }));
assert_eq!(
predicate_func(&mut reader).unwrap(),
PredicateFunc::LessThan(Number { int: 5, decimal: 0 })
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("<=5");
assert_eq!(predicate_func(&mut reader).unwrap(), PredicateFunc::LessThanOrEqual(Number { int: 5, decimal: 0 }));
assert_eq!(
predicate_func(&mut reader).unwrap(),
PredicateFunc::LessThanOrEqual(Number { int: 5, decimal: 0 })
);
assert_eq!(reader.state.cursor, 3);
}
}

View File

@ -15,10 +15,10 @@
* limitations under the License.
*
*/
use super::super::ast::*;
use super::error::{Error, ParseError};
use super::ParseResult;
use super::Reader;
use super::super::ast::*;
pub fn natural(reader: &mut Reader) -> ParseResult<'static, usize> {
let start = reader.state.clone();
@ -27,7 +27,9 @@ pub fn natural(reader: &mut Reader) -> ParseResult<'static, usize> {
return Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
let first_digit = reader.read().unwrap();
@ -35,7 +37,9 @@ pub fn natural(reader: &mut Reader) -> ParseResult<'static, usize> {
return Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
@ -47,7 +51,9 @@ pub fn natural(reader: &mut Reader) -> ParseResult<'static, usize> {
return Err(Error {
pos: save.pos,
recoverable: false,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
Ok(format!("{}{}", first_digit, s).parse().unwrap())
@ -67,7 +73,9 @@ pub fn number(reader: &mut Reader) -> ParseResult<'static, Number> {
return Err(Error {
pos: reader.clone().state.pos,
recoverable: false,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
@ -76,7 +84,9 @@ pub fn number(reader: &mut Reader) -> ParseResult<'static, Number> {
return Err(Error {
pos: reader.clone().state.pos,
recoverable: false,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
format!("{:0<18}", s).parse().unwrap()
@ -98,10 +108,7 @@ pub fn string_value(reader: &mut Reader) -> Result<String, Error> {
pub fn key_name(reader: &mut Reader) -> Result<String, Error> {
// // test python or javascript
//// subset that can used for dot notation
let s = reader.read_while(|c| c.is_alphabetic()
|| *c == '-'
|| *c == '_'
);
let s = reader.read_while(|c| c.is_alphabetic() || *c == '-' || *c == '_');
whitespace(reader);
Ok(s)
}
@ -115,7 +122,9 @@ pub fn literal(s: &str, reader: &mut Reader) -> ParseResult<'static, ()> {
return Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::Expecting { value: s.to_string() },
inner: ParseError::Expecting {
value: s.to_string(),
},
});
}
for c in s.chars() {
@ -125,7 +134,9 @@ pub fn literal(s: &str, reader: &mut Reader) -> ParseResult<'static, ()> {
return Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::Expecting { value: s.to_string() },
inner: ParseError::Expecting {
value: s.to_string(),
},
});
}
Some(x) => {
@ -133,7 +144,9 @@ pub fn literal(s: &str, reader: &mut Reader) -> ParseResult<'static, ()> {
return Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::Expecting { value: s.to_string() },
inner: ParseError::Expecting {
value: s.to_string(),
},
});
} else {
continue;
@ -145,7 +158,6 @@ pub fn literal(s: &str, reader: &mut Reader) -> ParseResult<'static, ()> {
Ok(())
}
pub fn try_literal(s: &str, p: &mut Reader) -> ParseResult<'static, ()> {
match literal(s, p) {
Ok(_) => Ok(()),
@ -153,7 +165,7 @@ pub fn try_literal(s: &str, p: &mut Reader) -> ParseResult<'static, ()> {
pos,
recoverable: true,
inner,
})
}),
}
}
@ -163,11 +175,10 @@ pub fn whitespace(reader: &mut Reader) {
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::Pos;
use super::*;
#[test]
fn test_natural() {
@ -189,19 +200,34 @@ mod tests {
let mut reader = Reader::init("");
let error = natural(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("01");
let error = natural(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 2 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.recoverable, false);
let mut reader = Reader::init("x");
let error = natural(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.recoverable, true);
}
@ -219,7 +245,12 @@ mod tests {
let mut reader = Reader::init("x");
let error = integer(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.recoverable, true);
}
@ -234,27 +265,63 @@ mod tests {
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("-1.0");
assert_eq!(number(&mut reader).unwrap(), Number { int: -1, decimal: 0 });
assert_eq!(
number(&mut reader).unwrap(),
Number {
int: -1,
decimal: 0
}
);
assert_eq!(reader.state.cursor, 4);
let mut reader = Reader::init("1.1");
assert_eq!(number(&mut reader).unwrap(), Number { int: 1, decimal: 100_000_000_000_000_000 });
assert_eq!(
number(&mut reader).unwrap(),
Number {
int: 1,
decimal: 100_000_000_000_000_000
}
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("1.100");
assert_eq!(number(&mut reader).unwrap(), Number { int: 1, decimal: 100_000_000_000_000_000 });
assert_eq!(
number(&mut reader).unwrap(),
Number {
int: 1,
decimal: 100_000_000_000_000_000
}
);
assert_eq!(reader.state.cursor, 5);
let mut reader = Reader::init("1.01");
assert_eq!(number(&mut reader).unwrap(), Number { int: 1, decimal: 10_000_000_000_000_000 });
assert_eq!(
number(&mut reader).unwrap(),
Number {
int: 1,
decimal: 10_000_000_000_000_000
}
);
assert_eq!(reader.state.cursor, 4);
let mut reader = Reader::init("1.010");
assert_eq!(number(&mut reader).unwrap(), Number { int: 1, decimal: 10_000_000_000_000_000 });
assert_eq!(
number(&mut reader).unwrap(),
Number {
int: 1,
decimal: 10_000_000_000_000_000
}
);
assert_eq!(reader.state.cursor, 5);
let mut reader = Reader::init("-0.333333333333333333");
assert_eq!(number(&mut reader).unwrap(), Number { int: 0, decimal: 333_333_333_333_333_333 });
assert_eq!(
number(&mut reader).unwrap(),
Number {
int: 0,
decimal: 333_333_333_333_333_333
}
);
assert_eq!(reader.state.cursor, 21);
}
@ -262,30 +329,49 @@ mod tests {
fn test_number_error() {
let mut reader = Reader::init("");
let error = number(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("-");
let error = number(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 2 });
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("1.");
let error = number(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 3 });
assert_eq!(error.recoverable, false);
let mut reader = Reader::init("1.x");
let error = number(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 3 });
assert_eq!(error.recoverable, false);
}
#[test]
fn test_string_value() {
let mut reader = Reader::init("'hello'");
@ -293,13 +379,23 @@ mod tests {
let mut reader = Reader::init("1");
let error = string_value(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("'") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("'")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("'hi");
let error = string_value(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("'") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("'")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 4 });
assert_eq!(error.recoverable, false);
}
@ -323,19 +419,34 @@ mod tests {
let mut reader = Reader::init("");
let error = literal("hello", &mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("hello") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("hello")
}
);
assert_eq!(reader.state.cursor, 0);
let mut reader = Reader::init("hi");
let error = literal("hello", &mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("hello") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("hello")
}
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("he");
let error = literal("hello", &mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("hello") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("hello")
}
);
assert_eq!(reader.state.cursor, 2);
}
}

View File

@ -44,7 +44,6 @@ impl Reader {
self.state.cursor == self.buffer.len()
}
pub fn read(&mut self) -> Option<char> {
match self.buffer.get(self.state.cursor) {
None => None,
@ -65,7 +64,7 @@ impl Reader {
pub fn peek(&mut self) -> Option<char> {
match self.buffer.get(self.state.cursor) {
None => None,
Some(c) => Some(*c)
Some(c) => Some(*c),
}
}
@ -74,12 +73,13 @@ impl Reader {
loop {
match self.peek() {
None => return s,
Some(c) =>
Some(c) => {
if predicate(&c) {
s.push(self.read().unwrap())
} else {
return s;
}
}
}
}
}
@ -116,7 +116,6 @@ fn is_combining_character(c: char) -> bool {
c > '\u{0300}' && c < '\u{036F}' // Combining Diacritical Marks (0300036F)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -20,12 +20,12 @@
#[macro_use]
extern crate float_cmp;
pub mod cli;
pub mod core;
pub mod format;
pub mod html;
pub mod http;
pub mod jsonpath;
pub mod linter;
pub mod parser;
pub mod runner;
pub mod http;
pub mod jsonpath;
pub mod html;
pub mod cli;

View File

@ -30,7 +30,6 @@ pub enum LinterError {
OneSpace {},
}
impl FormatError for Error {
fn source_info(&self) -> SourceInfo {
self.clone().source_info

View File

@ -270,8 +270,14 @@ impl Lintable<QueryValue> for QueryValue {
fn lint(&self) -> QueryValue {
match self {
QueryValue::Status {} => QueryValue::Status {},
QueryValue::Header { name, .. } => QueryValue::Header { name: name.clone(), space0: one_whitespace() },
QueryValue::Cookie { expr: CookiePath { name, attribute }, .. } => {
QueryValue::Header { name, .. } => QueryValue::Header {
name: name.clone(),
space0: one_whitespace(),
},
QueryValue::Cookie {
expr: CookiePath { name, attribute },
..
} => {
let attribute = if let Some(attribute) = attribute {
Some(attribute.lint())
} else {
@ -279,14 +285,29 @@ impl Lintable<QueryValue> for QueryValue {
};
QueryValue::Cookie {
space0: one_whitespace(),
expr: CookiePath { name: name.clone(), attribute },
expr: CookiePath {
name: name.clone(),
attribute,
},
}
}
QueryValue::Body {} => QueryValue::Body {},
QueryValue::Xpath { expr, .. } => QueryValue::Xpath { expr: expr.clone(), space0: one_whitespace() },
QueryValue::Jsonpath { expr, .. } => QueryValue::Jsonpath { expr: expr.clone(), space0: one_whitespace() },
QueryValue::Regex { expr, .. } => QueryValue::Regex { expr: expr.clone(), space0: one_whitespace() },
QueryValue::Variable { name, .. } => QueryValue::Variable { name: name.clone(), space0: one_whitespace() },
QueryValue::Xpath { expr, .. } => QueryValue::Xpath {
expr: expr.clone(),
space0: one_whitespace(),
},
QueryValue::Jsonpath { expr, .. } => QueryValue::Jsonpath {
expr: expr.clone(),
space0: one_whitespace(),
},
QueryValue::Regex { expr, .. } => QueryValue::Regex {
expr: expr.clone(),
space0: one_whitespace(),
},
QueryValue::Variable { name, .. } => QueryValue::Variable {
name: name.clone(),
space0: one_whitespace(),
},
}
}
}
@ -301,7 +322,11 @@ impl Lintable<CookieAttribute> for CookieAttribute {
let space0 = empty_whitespace();
let name = self.name.lint();
let space1 = empty_whitespace();
CookieAttribute { space0, name, space1 }
CookieAttribute {
space0,
name,
space1,
}
}
}
@ -319,8 +344,12 @@ impl Lintable<CookieAttributeName> for CookieAttributeName {
CookieAttributeName::Domain(_) => CookieAttributeName::Domain("Domain".to_string()),
CookieAttributeName::Path(_) => CookieAttributeName::Path("Path".to_string()),
CookieAttributeName::Secure(_) => CookieAttributeName::Secure("Secure".to_string()),
CookieAttributeName::HttpOnly(_) => CookieAttributeName::HttpOnly("HttpOnly".to_string()),
CookieAttributeName::SameSite(_) => CookieAttributeName::SameSite("SameSite".to_string())
CookieAttributeName::HttpOnly(_) => {
CookieAttributeName::HttpOnly("HttpOnly".to_string())
}
CookieAttributeName::SameSite(_) => {
CookieAttributeName::SameSite("SameSite".to_string())
}
}
}
}
@ -334,7 +363,11 @@ impl Lintable<Predicate> for Predicate {
fn lint(&self) -> Predicate {
Predicate {
not: self.clone().not,
space0: if self.not { one_whitespace() } else { empty_whitespace() },
space0: if self.not {
one_whitespace()
} else {
empty_whitespace()
},
predicate_func: self.predicate_func.lint(),
}
}
@ -363,25 +396,75 @@ impl Lintable<PredicateFuncValue> for PredicateFuncValue {
#[allow(clippy::clone_on_copy)]
fn lint(&self) -> PredicateFuncValue {
match self {
PredicateFuncValue::EqualString { value, .. } => PredicateFuncValue::EqualString { space0: one_whitespace(), value: value.clone().lint() },
PredicateFuncValue::EqualInt { value, .. } => PredicateFuncValue::EqualInt { space0: one_whitespace(), value: value.clone() },
PredicateFuncValue::EqualBool { value, .. } => PredicateFuncValue::EqualBool { space0: one_whitespace(), value: value.clone() },
PredicateFuncValue::EqualNull { .. } => PredicateFuncValue::EqualNull { space0: one_whitespace() },
PredicateFuncValue::EqualFloat { value, .. } => PredicateFuncValue::EqualFloat { space0: one_whitespace(), value: value.clone() },
PredicateFuncValue::EqualExpression { value, .. } => PredicateFuncValue::EqualExpression { space0: one_whitespace(), value: value.clone() },
PredicateFuncValue::Contain { value, .. } => PredicateFuncValue::Contain { space0: one_whitespace(), value: value.clone().lint() },
PredicateFuncValue::EqualString { value, .. } => PredicateFuncValue::EqualString {
space0: one_whitespace(),
value: value.clone().lint(),
},
PredicateFuncValue::EqualInt { value, .. } => PredicateFuncValue::EqualInt {
space0: one_whitespace(),
value: value.clone(),
},
PredicateFuncValue::EqualBool { value, .. } => PredicateFuncValue::EqualBool {
space0: one_whitespace(),
value: value.clone(),
},
PredicateFuncValue::EqualNull { .. } => PredicateFuncValue::EqualNull {
space0: one_whitespace(),
},
PredicateFuncValue::EqualFloat { value, .. } => PredicateFuncValue::EqualFloat {
space0: one_whitespace(),
value: value.clone(),
},
PredicateFuncValue::EqualExpression { value, .. } => {
PredicateFuncValue::EqualExpression {
space0: one_whitespace(),
value: value.clone(),
}
}
PredicateFuncValue::Contain { value, .. } => PredicateFuncValue::Contain {
space0: one_whitespace(),
value: value.clone().lint(),
},
PredicateFuncValue::IncludeString { value, .. } => PredicateFuncValue::IncludeString { space0: one_whitespace(), value: value.clone().lint() },
PredicateFuncValue::IncludeInt { value, .. } => PredicateFuncValue::IncludeInt { space0: one_whitespace(), value: value.clone() },
PredicateFuncValue::IncludeFloat { value, .. } => PredicateFuncValue::IncludeFloat { space0: one_whitespace(), value: value.clone() },
PredicateFuncValue::IncludeBool { value, .. } => PredicateFuncValue::IncludeBool { space0: one_whitespace(), value: value.clone() },
PredicateFuncValue::IncludeNull { .. } => PredicateFuncValue::IncludeNull { space0: one_whitespace() },
PredicateFuncValue::IncludeExpression { value, .. } => PredicateFuncValue::IncludeExpression { space0: one_whitespace(), value: value.clone() },
PredicateFuncValue::IncludeString { value, .. } => PredicateFuncValue::IncludeString {
space0: one_whitespace(),
value: value.clone().lint(),
},
PredicateFuncValue::IncludeInt { value, .. } => PredicateFuncValue::IncludeInt {
space0: one_whitespace(),
value: value.clone(),
},
PredicateFuncValue::IncludeFloat { value, .. } => PredicateFuncValue::IncludeFloat {
space0: one_whitespace(),
value: value.clone(),
},
PredicateFuncValue::IncludeBool { value, .. } => PredicateFuncValue::IncludeBool {
space0: one_whitespace(),
value: value.clone(),
},
PredicateFuncValue::IncludeNull { .. } => PredicateFuncValue::IncludeNull {
space0: one_whitespace(),
},
PredicateFuncValue::IncludeExpression { value, .. } => {
PredicateFuncValue::IncludeExpression {
space0: one_whitespace(),
value: value.clone(),
}
}
PredicateFuncValue::Match { value, .. } => PredicateFuncValue::Match { space0: one_whitespace(), value: value.clone().lint() },
PredicateFuncValue::StartWith { value, .. } => PredicateFuncValue::StartWith { space0: one_whitespace(), value: value.clone().lint() },
PredicateFuncValue::CountEqual { value, .. } => PredicateFuncValue::CountEqual { space0: one_whitespace(), value: value.clone() },
PredicateFuncValue::Exist {} => PredicateFuncValue::Exist {}
PredicateFuncValue::Match { value, .. } => PredicateFuncValue::Match {
space0: one_whitespace(),
value: value.clone().lint(),
},
PredicateFuncValue::StartWith { value, .. } => PredicateFuncValue::StartWith {
space0: one_whitespace(),
value: value.clone().lint(),
},
PredicateFuncValue::CountEqual { value, .. } => PredicateFuncValue::CountEqual {
space0: one_whitespace(),
value: value.clone(),
},
PredicateFuncValue::Exist {} => PredicateFuncValue::Exist {},
}
}
}
@ -450,9 +533,9 @@ impl Lintable<Bytes> for Bytes {
Bytes::Xml { value } => Bytes::Xml {
value: value.clone(),
},
// Bytes::MultilineString { value } => Bytes::MultilineString {
// value: value.clone(),
// },
// Bytes::MultilineString { value } => Bytes::MultilineString {
// value: value.clone(),
// },
}
}
}
@ -469,7 +552,11 @@ impl Lintable<KeyValue> for KeyValue {
space0: empty_whitespace(),
key: self.clone().key,
space1: empty_whitespace(),
space2: if self.value.clone().elements.is_empty() { empty_whitespace() } else { one_whitespace() },
space2: if self.value.clone().elements.is_empty() {
empty_whitespace()
} else {
one_whitespace()
},
value: self.clone().value,
line_terminator0: self.clone().line_terminator0,
}
@ -488,15 +575,15 @@ impl Lintable<MultipartParam> for MultipartParam {
MultipartParam::FileParam(file_param) => MultipartParam::FileParam(file_param.lint()),
}
}
// let line_terminators = self.line_terminators.clone();
// let space0 = empty_whitespace();
// let key = self.key.clone();
// let space1 = empty_whitespace();
// let space2 = self.space2.clone();
// let value = self.clone().value;
// let line_terminator0 = self.clone().line_terminator0;
// MultipartParam { line_terminators, space0, key,space1, space2, value, line_terminator0}
// }
// let line_terminators = self.line_terminators.clone();
// let space0 = empty_whitespace();
// let key = self.key.clone();
// let space1 = empty_whitespace();
// let space2 = self.space2.clone();
// let value = self.clone().value;
// let line_terminator0 = self.clone().line_terminator0;
// MultipartParam { line_terminators, space0, key,space1, space2, value, line_terminator0}
// }
}
impl Lintable<FileParam> for FileParam {
@ -525,7 +612,6 @@ impl Lintable<FileParam> for FileParam {
}
}
fn empty_whitespace() -> Whitespace {
Whitespace {
value: "".to_string(),
@ -618,7 +704,6 @@ impl Lintable<Template> for Template {
}
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -35,7 +35,6 @@ Encoded
YW55IGNhcm5hbCBwbGVhcw== any carnal pleas # [97, 110, 121, 32, 99, 97, 114, 110, 97, 108, 32, 112, 108, 101, 97, 115]
*/
pub fn parse(reader: &mut Reader) -> Vec<u8> {
let mut bytes = vec![];
let mut buf = vec![]; // base64 text
@ -46,31 +45,31 @@ pub fn parse(reader: &mut Reader) -> Vec<u8> {
}
let save = reader.state.clone();
match reader.read() {
None => { break; }
None => {
break;
}
Some(' ') | Some('\n') | Some('\t') => {}
Some(c) => {
match value(c) {
None => {
reader.state = save;
break;
}
Some(v) => {
buf.push(v);
if buf.len() == 4 {
let bs = decode_four_chars(
*buf.get(0).unwrap(),
*buf.get(1).unwrap(),
*buf.get(2).unwrap(),
*buf.get(3).unwrap(),
);
for b in bs {
bytes.push(b);
}
buf = vec![];
Some(c) => match value(c) {
None => {
reader.state = save;
break;
}
Some(v) => {
buf.push(v);
if buf.len() == 4 {
let bs = decode_four_chars(
*buf.get(0).unwrap(),
*buf.get(1).unwrap(),
*buf.get(2).unwrap(),
*buf.get(3).unwrap(),
);
for b in bs {
bytes.push(b);
}
buf = vec![];
}
}
}
},
}
}
match buf.as_slice() {
@ -81,7 +80,6 @@ pub fn parse(reader: &mut Reader) -> Vec<u8> {
bytes
}
fn value(c: char) -> Option<i32> {
match c {
'A' => Some(0),
@ -152,14 +150,15 @@ fn value(c: char) -> Option<i32> {
}
}
fn padding(reader: &mut Reader) -> String {
// consume padding can not fail
let mut buf = String::from("");
loop {
let save = reader.state.clone();
match reader.read() {
Some('=') => { buf.push('='); }
Some('=') => {
buf.push('=');
}
_ => {
reader.state = save;
break;
@ -169,7 +168,6 @@ fn padding(reader: &mut Reader) -> String {
buf
}
fn decode_two_chars(c1: i32, c2: i32) -> Vec<u8> {
return vec![((c1 << 2 & 255) + (c2 >> 4)) as u8];
}
@ -181,7 +179,6 @@ fn decode_three_chars(c1: i32, c2: i32, c3: i32) -> Vec<u8> {
];
}
fn decode_four_chars(c1: i32, c2: i32, c3: i32, c4: i32) -> Vec<u8> {
return vec![
((c1 << 2 & 255) + (c2 >> 4)) as u8,
@ -190,7 +187,6 @@ fn decode_four_chars(c1: i32, c2: i32, c3: i32, c4: i32) -> Vec<u8> {
];
}
#[cfg(test)]
mod tests {
use super::*;
@ -265,7 +261,6 @@ mod tests {
assert_eq!(reader.state.cursor, 5);
}
#[test]
fn test_decode_two_chars() {
assert_eq!(

View File

@ -20,14 +20,17 @@ use crate::core::ast::*;
use super::base64;
use super::combinators::*;
use super::json::parse as parse_json;
use super::ParseResult;
use super::primitives::*;
use super::reader::Reader;
use super::xml;
use super::ParseResult;
pub fn bytes(reader: &mut Reader) -> ParseResult<'static, Bytes> {
//let start = p.state.clone();
choice(vec![raw_string, json_bytes, xml_bytes, base64_bytes, file_bytes], reader)
choice(
vec![raw_string, json_bytes, xml_bytes, base64_bytes, file_bytes],
reader,
)
}
fn xml_bytes(reader: &mut Reader) -> ParseResult<'static, Bytes> {
@ -81,14 +84,13 @@ fn base64_bytes(reader: &mut Reader) -> ParseResult<'static, Bytes> {
})
}
#[cfg(test)]
mod tests {
use crate::core::common::{Pos, SourceInfo};
use crate::core::json;
use super::*;
use super::super::error::*;
use super::*;
#[test]
fn test_bytes_json() {
@ -99,9 +101,21 @@ mod tests {
value: json::Value::List {
space0: "".to_string(),
elements: vec![
json::ListElement { space0: "".to_string(), value: json::Value::Number("1".to_string()), space1: "".to_string() },
json::ListElement { space0: "".to_string(), value: json::Value::Number("2".to_string()), space1: "".to_string() },
json::ListElement { space0: "".to_string(), value: json::Value::Number("3".to_string()), space1: "".to_string() },
json::ListElement {
space0: "".to_string(),
value: json::Value::Number("1".to_string()),
space1: "".to_string()
},
json::ListElement {
space0: "".to_string(),
value: json::Value::Number("2".to_string()),
space1: "".to_string()
},
json::ListElement {
space0: "".to_string(),
value: json::Value::Number("3".to_string()),
space1: "".to_string()
},
],
}
}
@ -181,7 +195,12 @@ mod tests {
let mut reader = Reader::init("{ x ");
let error = bytes(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 3 });
assert_eq!(error.inner, ParseError::Expecting { value: "\"".to_string() });
assert_eq!(
error.inner,
ParseError::Expecting {
value: "\"".to_string()
}
);
}
#[test]
@ -189,7 +208,12 @@ mod tests {
let mut reader = Reader::init("```\nxxx ");
let error = bytes(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 2, column: 5 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("```") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("```")
}
);
}
#[test]
@ -197,7 +221,12 @@ mod tests {
let mut reader = Reader::init("");
let error = bytes(&mut reader).err().unwrap();
//println!("{:?}", error);
assert_eq!(error.inner, ParseError::Expecting { value: String::from("file") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("file")
}
);
assert_eq!(error.recoverable, true);
}
@ -270,7 +299,12 @@ mod tests {
}
);
assert_eq!(error.recoverable, false);
assert_eq!(error.inner, ParseError::Expecting { value: String::from(";") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from(";")
}
);
}
#[test]

View File

@ -15,23 +15,22 @@
* limitations under the License.
*
*/
use super::{ParseFunc, ParseResult};
use super::error::*;
use super::reader::Reader;
use super::{ParseFunc, ParseResult};
pub fn optional<'a, T>(f: ParseFunc<'a, T>, reader: &mut Reader) -> ParseResult<'a, Option<T>> {
let start = reader.state.clone();
match f(reader) {
Ok(r) => {
Ok(Some(r))
}
Err(e) =>
Ok(r) => Ok(Some(r)),
Err(e) => {
if e.recoverable {
reader.state = start;
Ok(None)
} else {
Err(e)
}
}
}
}
@ -39,36 +38,27 @@ pub fn recover<'a, T>(f: ParseFunc<'a, T>, reader: &mut Reader) -> ParseResult<'
// make an error recoverable
// but does not reset cursor
match f(reader) {
Ok(r) => {
Ok(r)
}
Err(e) => {
Err(Error {
pos: e.pos,
recoverable: true,
inner: e.inner,
})
}
Ok(r) => Ok(r),
Err(e) => Err(Error {
pos: e.pos,
recoverable: true,
inner: e.inner,
}),
}
}
pub fn nonrecover<'a, T>(f: ParseFunc<'a, T>, reader: &mut Reader) -> ParseResult<'a, T> {
//let start = p.state.clone();
match f(reader) {
Ok(r) => {
Ok(r)
}
Err(e) => {
Err(Error {
pos: e.pos,
recoverable: false,
inner: e.inner,
})
}
Ok(r) => Ok(r),
Err(e) => Err(Error {
pos: e.pos,
recoverable: false,
inner: e.inner,
}),
}
}
pub fn zero_or_more<'a, T>(f: ParseFunc<'a, T>, reader: &mut Reader) -> ParseResult<'a, Vec<T>> {
let _start = reader.state.clone();
@ -96,7 +86,6 @@ pub fn zero_or_more<'a, T>(f: ParseFunc<'a, T>, reader: &mut Reader) -> ParseRes
}
}
pub fn one_or_more<'a, T>(f: ParseFunc<'a, T>, reader: &mut Reader) -> ParseResult<'a, Vec<T>> {
let _initial_state = reader.state.clone();
match f(reader) {
@ -142,11 +131,13 @@ pub fn choice<'a, T>(fs: Vec<ParseFunc<'a, T>>, reader: &mut Reader) -> ParseRes
f(reader)
} else {
match f(reader) {
Err(Error { recoverable: true, .. }) => {
Err(Error {
recoverable: true, ..
}) => {
reader.state = start;
choice(fs.clone().into_iter().skip(1).collect(), reader)
}
x => x
x => x,
}
}
}

View File

@ -2,10 +2,10 @@ use crate::core::ast::*;
use super::combinators::*;
use super::error::*;
use super::ParseResult;
use super::primitives::*;
use super::reader::Reader;
use super::string::*;
use super::ParseResult;
/*

View File

@ -57,10 +57,8 @@ pub enum ParseError {
EscapeChar,
InvalidCookieAttribute,
}
impl FormatError for Error {
fn source_info(&self) -> SourceInfo {
SourceInfo {
@ -111,12 +109,17 @@ impl FormatError for Error {
ParseError::PredicateValue { .. } => "invalid predicate value".to_string(),
ParseError::RegexExpr { .. } => "Invalid Regex expression".to_string(),
ParseError::DuplicateSection { .. } => "The section is already defined".to_string(),
ParseError::RequestSection { .. } => "This is not a valid section for a request".to_string(),
ParseError::ResponseSection { .. } => "This is not a valid section for a response".to_string(),
ParseError::RequestSection { .. } => {
"This is not a valid section for a request".to_string()
}
ParseError::ResponseSection { .. } => {
"This is not a valid section for a response".to_string()
}
ParseError::EscapeChar { .. } => "The escaping sequence is not valid".to_string(),
ParseError::InvalidCookieAttribute { .. } => "The cookie attribute is not valid".to_string(),
ParseError::InvalidCookieAttribute { .. } => {
"The cookie attribute is not valid".to_string()
}
_ => format!("{:?}", self),
}
}
}

View File

@ -19,9 +19,9 @@ use crate::core::ast::*;
use crate::core::common::SourceInfo;
use super::error::*;
use super::ParseResult;
use super::primitives::*;
use super::reader::Reader;
use super::ParseResult;
pub fn parse(reader: &mut Reader) -> ParseResult<'static, Expr> {
// let start = p.state.clone();
@ -43,7 +43,6 @@ pub fn parse(reader: &mut Reader) -> ParseResult<'static, Expr> {
})
}
pub fn parse2(reader: &mut Reader) -> ParseResult<'static, Expr> {
// let start = p.state.clone();
@ -79,7 +78,6 @@ fn variable_name(reader: &mut Reader) -> ParseResult<'static, Variable> {
})
}
#[cfg(test)]
mod tests {
use crate::core::common::Pos;
@ -113,7 +111,12 @@ mod tests {
let mut reader = Reader::init("{{host>}}");
let error = parse(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 7 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("}}") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("}}")
}
);
assert_eq!(error.recoverable, false);
}
@ -122,11 +125,15 @@ mod tests {
let mut reader = Reader::init("{{host");
let error = parse(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 7 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("}}") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("}}")
}
);
assert_eq!(error.recoverable, false);
}
#[test]
fn test_variable() {
let mut reader = Reader::init("name");

View File

@ -22,10 +22,10 @@ use crate::core::json;
use super::combinators::*;
use super::error;
use super::ParseResult;
use super::primitives::*;
use super::reader::*;
use super::template::*;
use super::ParseResult;
pub fn parse(reader: &mut Reader) -> ParseResult<'static, json::Value> {
choice(
@ -35,7 +35,7 @@ pub fn parse(reader: &mut Reader) -> ParseResult<'static, json::Value> {
string_value,
number_value,
list_value,
object_value
object_value,
],
reader,
)
@ -66,7 +66,10 @@ fn string_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
let end = reader.state.pos.clone();
let encoded_string = EncodedString {
source_info: SourceInfo { start: start.clone(), end: end.clone() },
source_info: SourceInfo {
start: start.clone(),
end: end.clone(),
},
chars,
};
literal("\"", reader)?;
@ -84,21 +87,25 @@ fn any_char(reader: &mut Reader) -> ParseResult<'static, (char, String, Pos)> {
let start = reader.state.clone();
match escape_char(reader) {
Ok(c) => Ok((c, reader.from(start.cursor), start.pos)),
Err(e) =>
Err(e) => {
if e.recoverable {
reader.state = start.clone();
match reader.read() {
None => Err(error::Error {
pos: start.pos,
recoverable: true,
inner: error::ParseError::Expecting { value: "char".to_string() },
inner: error::ParseError::Expecting {
value: "char".to_string(),
},
}),
Some(c) => {
if vec!['\\', '\x08', '\n', '\x0c', '\r', '\t'].contains(&c) {
Err(error::Error {
pos: start.pos,
recoverable: true,
inner: error::ParseError::Expecting { value: "char".to_string() },
inner: error::ParseError::Expecting {
value: "char".to_string(),
},
})
} else {
Ok((c, reader.from(start.cursor), start.pos))
@ -108,6 +115,7 @@ fn any_char(reader: &mut Reader) -> ParseResult<'static, (char, String, Pos)> {
} else {
Err(e)
}
}
}
}
@ -135,11 +143,13 @@ fn escape_char(reader: &mut Reader) -> ParseResult<'static, char> {
fn unicode(reader: &mut Reader) -> ParseResult<'static, char> {
let v = hex_value(reader)?;
let c = match std::char::from_u32(v) {
None => return Err(error::Error {
pos: reader.clone().state.pos,
recoverable: false,
inner: error::ParseError::Unicode {},
}),
None => {
return Err(error::Error {
pos: reader.clone().state.pos,
recoverable: false,
inner: error::ParseError::Unicode {},
})
}
Some(c) => c,
};
Ok(c)
@ -159,7 +169,7 @@ fn number_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
let sign = match try_literal("-", reader) {
Err(_) => "".to_string(),
Ok(_) => "-".to_string()
Ok(_) => "-".to_string(),
};
let integer = match try_literal("0", reader) {
@ -169,13 +179,15 @@ fn number_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
return Err(error::Error {
pos: start,
recoverable: true,
inner: error::ParseError::Expecting { value: "number".to_string() },
inner: error::ParseError::Expecting {
value: "number".to_string(),
},
});
} else {
digits
}
}
Ok(_) => "0".to_string()
Ok(_) => "0".to_string(),
};
let fraction = match try_literal(".", reader) {
@ -185,13 +197,15 @@ fn number_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
return Err(error::Error {
pos: reader.state.pos.clone(),
recoverable: false,
inner: error::ParseError::Expecting { value: "digits".to_string() },
inner: error::ParseError::Expecting {
value: "digits".to_string(),
},
});
} else {
format!(".{}", digits)
}
}
Err(_) => "".to_string()
Err(_) => "".to_string(),
};
let exponent = if reader.remaining().starts_with('e') || reader.remaining().starts_with('E') {
@ -201,7 +215,7 @@ fn number_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
Err(_) => match try_literal("+", reader) {
Ok(_) => "+".to_string(),
Err(_) => "".to_string(),
}
},
};
let exponent_digits = reader.read_while(|c| c.is_ascii_digit());
format!("e{}{}", exponent_sign, exponent_digits)
@ -209,7 +223,10 @@ fn number_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
"".to_string()
};
Ok(json::Value::Number(format!("{}{}{}{}", sign, integer, fraction, exponent)))
Ok(json::Value::Number(format!(
"{}{}{}{}",
sign, integer, fraction, exponent
)))
}
fn list_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
@ -217,7 +234,6 @@ fn list_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
let space0 = whitespace(reader);
let mut elements = vec![];
// at least one element
if !reader.remaining().starts_with(']') {
let first_element = list_element(None, reader)?;
@ -240,17 +256,22 @@ fn list_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
Ok(json::Value::List { space0, elements })
}
fn list_element(_type: Option<String>, reader: &mut Reader) -> ParseResult<'static, json::ListElement> {
fn list_element(
_type: Option<String>,
reader: &mut Reader,
) -> ParseResult<'static, json::ListElement> {
let save = reader.state.pos.clone();
let space0 = whitespace(reader);
let pos = reader.state.pos.clone();
let value = match parse(reader) {
Ok(r) => r,
Err(_) => return Err(error::Error {
pos: save,
recoverable: false,
inner: error::ParseError::Json {},
}),
Err(_) => {
return Err(error::Error {
pos: save,
recoverable: false,
inner: error::ParseError::Json {},
})
}
};
if let Some(t) = _type {
if t != value._type() {
@ -262,7 +283,11 @@ fn list_element(_type: Option<String>, reader: &mut Reader) -> ParseResult<'stat
}
}
let space1 = whitespace(reader);
Ok(json::ListElement { space0, value, space1 })
Ok(json::ListElement {
space0,
value,
space1,
})
}
fn object_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
@ -304,24 +329,36 @@ fn object_element(reader: &mut Reader) -> ParseResult<'static, json::ObjectEleme
let space2 = whitespace(reader);
let value = match parse(reader) {
Ok(r) => r,
Err(_) => return Err(error::Error {
pos: save,
recoverable: false,
inner: error::ParseError::Json {},
}),
Err(_) => {
return Err(error::Error {
pos: save,
recoverable: false,
inner: error::ParseError::Json {},
})
}
};
let space3 = whitespace(reader);
Ok(json::ObjectElement { space0, name, space1, space2, value, space3 })
Ok(json::ObjectElement {
space0,
name,
space1,
space2,
value,
space3,
})
}
fn key(reader: &mut Reader) -> ParseResult<'static, String> {
let s = reader.read_while(|c| c.is_alphanumeric() || *c == '@' || *c == '.' || *c == '_' || *c == '-');
let s = reader
.read_while(|c| c.is_alphanumeric() || *c == '@' || *c == '.' || *c == '_' || *c == '-');
if s.is_empty() {
let pos = reader.state.pos.clone();
Err(error::Error {
pos,
recoverable: false,
inner: error::ParseError::Expecting { value: "key".to_string() },
inner: error::ParseError::Expecting {
value: "key".to_string(),
},
})
} else {
Ok(s)
@ -362,48 +399,68 @@ mod tests {
let mut reader = Reader::init("true");
let error = null_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "null".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "null".to_string()
}
);
assert_eq!(error.recoverable, true);
}
#[test]
fn test_boolean_value() {
let mut reader = Reader::init("true");
assert_eq!(boolean_value(&mut reader).unwrap(), json::Value::Boolean(true));
assert_eq!(
boolean_value(&mut reader).unwrap(),
json::Value::Boolean(true)
);
assert_eq!(reader.state.cursor, 4);
let mut reader = Reader::init("1");
let error = boolean_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "true|false".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "true|false".to_string()
}
);
assert_eq!(error.recoverable, true);
}
#[test]
fn test_string_value() {
let mut reader = Reader::init("\"\"");
assert_eq!(string_value(&mut reader).unwrap(), json::Value::String(Template {
quotes: true,
elements: vec![],
source_info: SourceInfo::init(1, 2, 1, 2),
}));
assert_eq!(
string_value(&mut reader).unwrap(),
json::Value::String(Template {
quotes: true,
elements: vec![],
source_info: SourceInfo::init(1, 2, 1, 2),
})
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("\"Hello\\u0020{{name}}!\"");
assert_eq!(string_value(&mut reader).unwrap(), json::tests::hello_world_value());
assert_eq!(
string_value(&mut reader).unwrap(),
json::tests::hello_world_value()
);
assert_eq!(reader.state.cursor, 22);
let mut reader = Reader::init("\"{}\"");
assert_eq!(string_value(&mut reader).unwrap(), json::Value::String(Template {
quotes: true,
elements: vec![
TemplateElement::String {
assert_eq!(
string_value(&mut reader).unwrap(),
json::Value::String(Template {
quotes: true,
elements: vec![TemplateElement::String {
value: "{}".to_string(),
encoded: "{}".to_string(),
}
],
source_info: SourceInfo::init(1, 2, 1, 4),
}));
}],
source_info: SourceInfo::init(1, 2, 1, 4),
})
);
assert_eq!(reader.state.cursor, 4);
}
@ -412,42 +469,72 @@ mod tests {
let mut reader = Reader::init("1");
let error = string_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "\"".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "\"".to_string()
}
);
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("\"1");
let error = string_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 3 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "\"".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "\"".to_string()
}
);
assert_eq!(error.recoverable, false);
let mut reader = Reader::init("\"{{x\"");
let error = string_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 5 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "}}".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "}}".to_string()
}
);
assert_eq!(error.recoverable, false);
}
#[test]
fn test_any_char() {
let mut reader = Reader::init("a");
assert_eq!(any_char(&mut reader).unwrap(), ('a', "a".to_string(), Pos { line: 1, column: 1 }));
assert_eq!(
any_char(&mut reader).unwrap(),
('a', "a".to_string(), Pos { line: 1, column: 1 })
);
assert_eq!(reader.state.cursor, 1);
let mut reader = Reader::init(" ");
assert_eq!(any_char(&mut reader).unwrap(), (' ', " ".to_string(), Pos { line: 1, column: 1 }));
assert_eq!(
any_char(&mut reader).unwrap(),
(' ', " ".to_string(), Pos { line: 1, column: 1 })
);
assert_eq!(reader.state.cursor, 1);
let mut reader = Reader::init("\\u0020 ");
assert_eq!(any_char(&mut reader).unwrap(), (' ', "\\u0020".to_string(), Pos { line: 1, column: 1 }));
assert_eq!(
any_char(&mut reader).unwrap(),
(' ', "\\u0020".to_string(), Pos { line: 1, column: 1 })
);
assert_eq!(reader.state.cursor, 6);
let mut reader = Reader::init("\\t");
assert_eq!(any_char(&mut reader).unwrap(), ('\t', "\\t".to_string(), Pos { line: 1, column: 1 }));
assert_eq!(
any_char(&mut reader).unwrap(),
('\t', "\\t".to_string(), Pos { line: 1, column: 1 })
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("#");
assert_eq!(any_char(&mut reader).unwrap(), ('#', "#".to_string(), Pos { line: 1, column: 1 }));
assert_eq!(
any_char(&mut reader).unwrap(),
('#', "#".to_string(), Pos { line: 1, column: 1 })
);
assert_eq!(reader.state.cursor, 1);
}
@ -477,7 +564,12 @@ mod tests {
let mut reader = Reader::init("x");
let error = escape_char(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "\\".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "\\".to_string()
}
);
assert_eq!(error.recoverable, true);
assert_eq!(reader.state.cursor, 0);
}
@ -504,32 +596,52 @@ mod tests {
#[test]
fn test_number_value() {
let mut reader = Reader::init("100");
assert_eq!(number_value(&mut reader).unwrap(), json::Value::Number("100".to_string()));
assert_eq!(
number_value(&mut reader).unwrap(),
json::Value::Number("100".to_string())
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("1.333");
assert_eq!(number_value(&mut reader).unwrap(), json::Value::Number("1.333".to_string()));
assert_eq!(
number_value(&mut reader).unwrap(),
json::Value::Number("1.333".to_string())
);
assert_eq!(reader.state.cursor, 5);
let mut reader = Reader::init("-1");
assert_eq!(number_value(&mut reader).unwrap(), json::Value::Number("-1".to_string()));
assert_eq!(
number_value(&mut reader).unwrap(),
json::Value::Number("-1".to_string())
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("00");
assert_eq!(number_value(&mut reader).unwrap(), json::Value::Number("0".to_string()));
assert_eq!(
number_value(&mut reader).unwrap(),
json::Value::Number("0".to_string())
);
assert_eq!(reader.state.cursor, 1);
let mut reader = Reader::init("1e0");
assert_eq!(number_value(&mut reader).unwrap(), json::Value::Number("1e0".to_string()));
assert_eq!(
number_value(&mut reader).unwrap(),
json::Value::Number("1e0".to_string())
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("1e005");
assert_eq!(number_value(&mut reader).unwrap(), json::Value::Number("1e005".to_string()));
assert_eq!(
number_value(&mut reader).unwrap(),
json::Value::Number("1e005".to_string())
);
assert_eq!(reader.state.cursor, 5);
let mut reader = Reader::init("1e-005");
assert_eq!(number_value(&mut reader).unwrap(), json::Value::Number("1e-005".to_string()));
assert_eq!(
number_value(&mut reader).unwrap(),
json::Value::Number("1e-005".to_string())
);
assert_eq!(reader.state.cursor, 6);
}
@ -538,37 +650,60 @@ mod tests {
let mut reader = Reader::init("true");
let error = number_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "number".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "number".to_string()
}
);
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("1.x");
let error = number_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 3 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "digits".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "digits".to_string()
}
);
assert_eq!(error.recoverable, false);
}
#[test]
fn test_list_value() {
let mut reader = Reader::init("[]");
assert_eq!(list_value(&mut reader).unwrap(), json::Value::List { space0: "".to_string(), elements: vec![] });
assert_eq!(
list_value(&mut reader).unwrap(),
json::Value::List {
space0: "".to_string(),
elements: vec![]
}
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("[ ]");
assert_eq!(list_value(&mut reader).unwrap(), json::Value::List { space0: " ".to_string(), elements: vec![] });
assert_eq!(
list_value(&mut reader).unwrap(),
json::Value::List {
space0: " ".to_string(),
elements: vec![]
}
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("[true]");
assert_eq!(list_value(&mut reader).unwrap(), json::Value::List {
space0: "".to_string(),
elements: vec![
json::ListElement {
assert_eq!(
list_value(&mut reader).unwrap(),
json::Value::List {
space0: "".to_string(),
elements: vec![json::ListElement {
space0: "".to_string(),
value: json::Value::Boolean(true),
space1: "".to_string(),
}
],
});
}],
}
);
assert_eq!(reader.state.cursor, 6);
}
@ -577,13 +712,23 @@ mod tests {
let mut reader = Reader::init("true");
let error = list_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "[".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "[".to_string()
}
);
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("[1, true]");
let error = list_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 5 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "number".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "number".to_string()
}
);
assert_eq!(error.recoverable, false);
let mut reader = Reader::init("[1, 2,]");
@ -596,24 +741,36 @@ mod tests {
#[test]
fn test_list_element() {
let mut reader = Reader::init("true");
assert_eq!(list_element(None, &mut reader).unwrap(), json::ListElement {
space0: "".to_string(),
value: json::Value::Boolean(true),
space1: "".to_string(),
});
assert_eq!(
list_element(None, &mut reader).unwrap(),
json::ListElement {
space0: "".to_string(),
value: json::Value::Boolean(true),
space1: "".to_string(),
}
);
assert_eq!(reader.state.cursor, 4);
}
#[test]
fn test_list_element_error() {
let mut reader = Reader::init("true");
let error = list_element(Some("number".to_string()), &mut reader).err().unwrap();
let error = list_element(Some("number".to_string()), &mut reader)
.err()
.unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "number".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "number".to_string()
}
);
assert_eq!(error.recoverable, false);
let mut reader = Reader::init("\n]");
let error = list_element(Some("number".to_string()), &mut reader).err().unwrap();
let error = list_element(Some("number".to_string()), &mut reader)
.err()
.unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Json {});
assert_eq!(error.recoverable, false);
@ -622,33 +779,51 @@ mod tests {
#[test]
fn test_object_value() {
let mut reader = Reader::init("{}");
assert_eq!(object_value(&mut reader).unwrap(), json::Value::Object { space0: "".to_string(), elements: vec![] });
assert_eq!(
object_value(&mut reader).unwrap(),
json::Value::Object {
space0: "".to_string(),
elements: vec![]
}
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("{ }");
assert_eq!(object_value(&mut reader).unwrap(), json::Value::Object { space0: " ".to_string(), elements: vec![] });
assert_eq!(
object_value(&mut reader).unwrap(),
json::Value::Object {
space0: " ".to_string(),
elements: vec![]
}
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("{\n \"a\": true\n}");
assert_eq!(object_value(&mut reader).unwrap(), json::Value::Object {
space0: "\n ".to_string(),
elements: vec![
json::ObjectElement {
assert_eq!(
object_value(&mut reader).unwrap(),
json::Value::Object {
space0: "\n ".to_string(),
elements: vec![json::ObjectElement {
space0: "".to_string(),
name: "a".to_string(),
space1: "".to_string(),
space2: " ".to_string(),
value: json::Value::Boolean(true),
space3: "\n".to_string(),
}
],
});
}],
}
);
assert_eq!(reader.state.cursor, 15);
let mut reader = Reader::init("true");
let error = object_value(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "{".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "{".to_string()
}
);
assert_eq!(error.recoverable, true);
}
@ -664,14 +839,17 @@ mod tests {
#[test]
fn test_object_element() {
let mut reader = Reader::init("\"a\": true");
assert_eq!(object_element(&mut reader).unwrap(), json::ObjectElement {
space0: "".to_string(),
name: "a".to_string(),
space1: "".to_string(),
space2: " ".to_string(),
value: json::Value::Boolean(true),
space3: "".to_string(),
});
assert_eq!(
object_element(&mut reader).unwrap(),
json::ObjectElement {
space0: "".to_string(),
name: "a".to_string(),
space1: "".to_string(),
space2: " ".to_string(),
value: json::Value::Boolean(true),
space3: "".to_string(),
}
);
assert_eq!(reader.state.cursor, 9);
}
@ -680,7 +858,12 @@ mod tests {
let mut reader = Reader::init(":");
let error = object_element(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "\"".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "\"".to_string()
}
);
assert_eq!(error.recoverable, false);
let mut reader = Reader::init("\"name\":\n");

View File

@ -15,34 +15,30 @@
* limitations under the License.
*
*/
use error::Error;
use crate::core::ast::HurlFile;
use error::Error;
mod base64;
mod bytes;
mod combinators;
mod cookiepath;
pub mod error;
mod expr;
pub mod json;
mod parsers;
mod predicate;
mod primitives;
mod sections;
mod bytes;
mod xml;
mod query;
pub mod reader;
mod sections;
mod string;
mod template;
mod query;
mod predicate;
mod cookiepath;
mod xml;
pub type ParseResult<'a, T> = std::result::Result<T, Error>;
pub type ParseFunc<'a, T> = fn(&mut reader::Reader) -> ParseResult<'a, T>;
pub fn parse_hurl_file(s: &str) -> ParseResult<'static, HurlFile> {
let mut reader = reader::Reader::init(s);
parsers::hurl_file(&mut reader)
}

View File

@ -22,10 +22,10 @@ use super::bytes::*;
use super::combinators::*;
use super::error::*;
use super::expr;
use super::ParseResult;
use super::primitives::*;
use super::reader::Reader;
use super::sections::*;
use super::ParseResult;
pub fn hurl_file(reader: &mut Reader) -> ParseResult<'static, HurlFile> {
let entries = zero_or_more(|p1| entry(p1), reader)?;
@ -150,7 +150,6 @@ fn method(reader: &mut Reader) -> ParseResult<'static, Method> {
}
fn url(reader: &mut Reader) -> ParseResult<'static, Template> {
// can not be json-encoded
// can not be empty
// but more restrictive: whitelist characters, not empty
@ -198,7 +197,7 @@ fn url(reader: &mut Reader) -> ParseResult<'static, Template> {
Some(c) => {
if c.is_alphanumeric()
|| vec![':', '/', '.', '-', '?', '=', '&', '_', '%', '*', ',']
.contains(&c)
.contains(&c)
{
buffer.push(c);
} else {
@ -299,7 +298,6 @@ fn body(reader: &mut Reader) -> ParseResult<'static, Body> {
})
}
#[cfg(test)]
mod tests {
use crate::core::common::Pos;
@ -314,7 +312,6 @@ mod tests {
assert_eq!(hurl_file.entries.len(), 1);
}
#[test]
fn test_entry() {
let mut reader = Reader::init("GET http://google.fr");
@ -612,7 +609,9 @@ mod tests {
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"),
encoded: String::from(
"http://localhost:8000/cookies/set-session-cookie2-valueA"
),
}],
quotes: false,
source_info: SourceInfo::init(1, 1, 1, 57),

View File

@ -4,10 +4,10 @@ use crate::core::common::SourceInfo;
use super::combinators::*;
use super::error::*;
use super::expr;
use super::ParseResult;
use super::primitives::*;
use super::reader::Reader;
use super::string::*;
use super::ParseResult;
pub fn predicate(reader: &mut Reader) -> ParseResult<'static, Predicate> {
let (not, space0) = match try_literal("not", reader) {
@ -55,7 +55,9 @@ fn predicate_func_value(reader: &mut Reader) -> ParseResult<'static, PredicateFu
],
reader,
) {
Err(Error { recoverable: true, .. }) => Err(Error {
Err(Error {
recoverable: true, ..
}) => Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::Predicate,
@ -75,17 +77,20 @@ fn equal_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncVal
Ok(PredicateValue::Bool { value }) => Ok(PredicateFuncValue::EqualBool { space0, value }),
Ok(PredicateValue::Int { value }) => Ok(PredicateFuncValue::EqualInt { space0, value }),
Ok(PredicateValue::Float { value }) => Ok(PredicateFuncValue::EqualFloat { space0, value }),
Ok(PredicateValue::Expression { value }) => Ok(PredicateFuncValue::EqualExpression { space0, value }),
Ok(PredicateValue::Template { value }) => Ok(PredicateFuncValue::EqualString { space0, value }),
Err(e) =>
match e.inner {
ParseError::EscapeChar {} => Err(e),
_ => Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::PredicateValue {},
})
}
Ok(PredicateValue::Expression { value }) => {
Ok(PredicateFuncValue::EqualExpression { space0, value })
}
Ok(PredicateValue::Template { value }) => {
Ok(PredicateFuncValue::EqualString { space0, value })
}
Err(e) => match e.inner {
ParseError::EscapeChar {} => Err(e),
_ => Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::PredicateValue {},
}),
},
}
}
@ -94,12 +99,14 @@ fn count_equal_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateF
let space0 = one_or_more_spaces(reader)?;
let save = reader.state.clone();
let value = match natural(reader) {
Err(_) => return Err(Error {
pos: save.pos,
recoverable: false,
inner: ParseError::PredicateValue {},
}),
Ok(value) => value
Err(_) => {
return Err(Error {
pos: save.pos,
recoverable: false,
inner: ParseError::PredicateValue {},
})
}
Ok(value) => value,
};
Ok(PredicateFuncValue::CountEqual { space0, value })
}
@ -126,18 +133,23 @@ fn include_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncV
Ok(PredicateValue::Null {}) => Ok(PredicateFuncValue::IncludeNull { space0 }),
Ok(PredicateValue::Bool { value }) => Ok(PredicateFuncValue::IncludeBool { space0, value }),
Ok(PredicateValue::Int { value }) => Ok(PredicateFuncValue::IncludeInt { space0, value }),
Ok(PredicateValue::Float { value }) => Ok(PredicateFuncValue::IncludeFloat { space0, value }),
Ok(PredicateValue::Template { value }) => Ok(PredicateFuncValue::IncludeString { space0, value }),
Ok(PredicateValue::Expression { value }) => Ok(PredicateFuncValue::IncludeExpression { space0, value }),
Err(e) =>
match e.inner {
ParseError::EscapeChar {} => Err(e),
_ => Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::PredicateValue {},
})
}
Ok(PredicateValue::Float { value }) => {
Ok(PredicateFuncValue::IncludeFloat { space0, value })
}
Ok(PredicateValue::Template { value }) => {
Ok(PredicateFuncValue::IncludeString { space0, value })
}
Ok(PredicateValue::Expression { value }) => {
Ok(PredicateFuncValue::IncludeExpression { space0, value })
}
Err(e) => match e.inner {
ParseError::EscapeChar {} => Err(e),
_ => Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::PredicateValue {},
}),
},
}
}
@ -153,7 +165,6 @@ fn exist_predicate(reader: &mut Reader) -> ParseResult<'static, PredicateFuncVal
Ok(PredicateFuncValue::Exist {})
}
/* internal to the parser */
#[derive(Clone, Debug, PartialEq, Eq)]
enum PredicateValue {
@ -192,13 +203,11 @@ fn predicate_value(reader: &mut Reader) -> ParseResult<'static, PredicateValue>
Ok(value) => Ok(PredicateValue::Template { value }),
Err(e) => Err(e),
},
],
reader,
)
}
#[cfg(test)]
mod tests {
use crate::core::common::Pos;
@ -234,7 +243,13 @@ mod tests {
fn test_predicate_error() {
let mut reader = Reader::init("countEquals true");
let error = predicate(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 13 });
assert_eq!(
error.pos,
Pos {
line: 1,
column: 13
}
);
assert_eq!(error.recoverable, false);
assert_eq!(error.inner, ParseError::PredicateValue {});
}
@ -266,7 +281,11 @@ mod tests {
assert_eq!(
equal_predicate(&mut reader).unwrap(),
PredicateFuncValue::EqualFloat {
value: Float { int: 1, decimal: 100_000_000_000_000_000, decimal_digits: 1 },
value: Float {
int: 1,
decimal: 100_000_000_000_000_000,
decimal_digits: 1
},
space0: Whitespace {
value: String::from(" "),
source_info: SourceInfo::init(1, 7, 1, 8),
@ -292,12 +311,10 @@ mod tests {
PredicateFuncValue::EqualString {
value: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "Bob".to_string(),
encoded: "Bob".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "Bob".to_string(),
encoded: "Bob".to_string(),
}],
source_info: SourceInfo::init(1, 8, 1, 13),
},
space0: Whitespace {
@ -352,7 +369,13 @@ mod tests {
let mut reader = Reader::init("countEquals true");
let error = count_equal_predicate(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 13 });
assert_eq!(
error.pos,
Pos {
line: 1,
column: 13
}
);
assert_eq!(error.recoverable, false);
assert_eq!(error.inner, ParseError::PredicateValue {});
}
@ -361,9 +384,20 @@ mod tests {
fn test_start_with_predicate() {
let mut reader = Reader::init("startsWith 2");
let error = start_with_predicate(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 12 });
assert_eq!(
error.pos,
Pos {
line: 1,
column: 12
}
);
assert_eq!(error.recoverable, false);
assert_eq!(error.inner, ParseError::Expecting { value: "\"".to_string() });
assert_eq!(
error.inner,
ParseError::Expecting {
value: "\"".to_string()
}
);
}
#[test]
@ -384,8 +418,12 @@ mod tests {
assert_eq!(
predicate_value(&mut reader).unwrap(),
PredicateValue::Float {
value: Float { int: 1, decimal: 100_000_000_000_000_000, decimal_digits: 1 }
value: Float {
int: 1,
decimal: 100_000_000_000_000_000,
decimal_digits: 1
}
}
);
}
}
}

View File

@ -20,21 +20,19 @@ use crate::core::common::SourceInfo;
use super::combinators::*;
use super::error::*;
use super::ParseResult;
use super::reader::Reader;
use super::string::*;
use super::template;
use super::ParseResult;
pub fn space(reader: &mut Reader) -> ParseResult<'static, Whitespace> {
let start = reader.state.clone();
match reader.read() {
None => {
Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Space {},
})
}
None => Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Space {},
}),
Some(c) => {
if c == ' ' || c == '\t' {
Ok(Whitespace {
@ -117,7 +115,9 @@ pub fn line_terminator(reader: &mut Reader) -> ParseResult<'static, LineTerminat
return Err(Error {
pos: e.pos,
recoverable: false,
inner: ParseError::Expecting { value: String::from("line_terminator") },
inner: ParseError::Expecting {
value: String::from("line_terminator"),
},
});
}
}
@ -167,7 +167,9 @@ pub fn literal(s: &str, reader: &mut Reader) -> ParseResult<'static, ()> {
return Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::Expecting { value: s.to_string() },
inner: ParseError::Expecting {
value: s.to_string(),
},
});
}
for c in s.chars() {
@ -177,7 +179,9 @@ pub fn literal(s: &str, reader: &mut Reader) -> ParseResult<'static, ()> {
return Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::Expecting { value: s.to_string() },
inner: ParseError::Expecting {
value: s.to_string(),
},
});
}
Some(x) => {
@ -185,7 +189,9 @@ pub fn literal(s: &str, reader: &mut Reader) -> ParseResult<'static, ()> {
return Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::Expecting { value: s.to_string() },
inner: ParseError::Expecting {
value: s.to_string(),
},
});
} else {
continue;
@ -238,13 +244,14 @@ pub fn newline(reader: &mut Reader) -> ParseResult<'static, Whitespace> {
Err(_) => Err(Error {
pos: start.pos,
recoverable: false,
inner: ParseError::Expecting { value: String::from("newline") },
inner: ParseError::Expecting {
value: String::from("newline"),
},
}),
},
}
}
pub fn key_value(reader: &mut Reader) -> ParseResult<'static, KeyValue> {
let line_terminators = optional_line_terminators(reader)?;
let space0 = zero_or_more_spaces(reader)?;
@ -270,7 +277,8 @@ pub fn filename(reader: &mut Reader) -> ParseResult<'static, Filename> {
// that you have to write with a relative name
// default root_dir is the hurl directory
let start = reader.state.clone();
let s = reader.read_while(|c| c.is_alphanumeric() || *c == '.' || *c == '/' || *c == '_' || *c == '-');
let s = reader
.read_while(|c| c.is_alphanumeric() || *c == '.' || *c == '/' || *c == '_' || *c == '-');
if s.is_empty() {
return Err(Error {
pos: start.pos,
@ -297,7 +305,6 @@ pub fn filename(reader: &mut Reader) -> ParseResult<'static, Filename> {
pub fn null(reader: &mut Reader) -> ParseResult<'static, ()> {
try_literal("null", reader)
}
pub fn boolean(reader: &mut Reader) -> ParseResult<'static, bool> {
@ -309,14 +316,14 @@ pub fn boolean(reader: &mut Reader) -> ParseResult<'static, bool> {
Err(_) => Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Expecting { value: String::from("true|false") },
inner: ParseError::Expecting {
value: String::from("true|false"),
},
}),
},
}
}
pub fn natural(reader: &mut Reader) -> ParseResult<'static, u64> {
let start = reader.state.clone();
@ -324,7 +331,9 @@ pub fn natural(reader: &mut Reader) -> ParseResult<'static, u64> {
return Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
let first_digit = reader.read().unwrap();
@ -332,7 +341,9 @@ pub fn natural(reader: &mut Reader) -> ParseResult<'static, u64> {
return Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
@ -344,7 +355,9 @@ pub fn natural(reader: &mut Reader) -> ParseResult<'static, u64> {
return Err(Error {
pos: save.pos,
recoverable: false,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
Ok(format!("{}{}", first_digit, s).parse().unwrap())
@ -370,7 +383,9 @@ pub fn float(reader: &mut Reader) -> ParseResult<'static, Float> {
return Err(Error {
pos: reader.clone().state.pos,
recoverable: false,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
@ -379,12 +394,18 @@ pub fn float(reader: &mut Reader) -> ParseResult<'static, Float> {
return Err(Error {
pos: reader.clone().state.pos,
recoverable: false,
inner: ParseError::Expecting { value: String::from("natural") },
inner: ParseError::Expecting {
value: String::from("natural"),
},
});
}
let decimal = format!("{:0<18}", s).parse().unwrap();
let decimal_digits = s.len();
Ok(Float { int, decimal, decimal_digits })
Ok(Float {
int,
decimal,
decimal_digits,
})
}
pub fn raw_string(reader: &mut Reader) -> ParseResult<'static, Bytes> {
@ -415,25 +436,25 @@ pub fn raw_string(reader: &mut Reader) -> ParseResult<'static, Bytes> {
pub fn raw_string_value(reader: &mut Reader) -> ParseResult<'static, Template> {
let mut chars = vec![];
let start = reader.state.pos.clone();
while !reader.remaining().starts_with("```") && !reader.is_eof() {
let pos = reader.state.pos.clone();
let c = reader.read().unwrap();
chars.push((c, c.to_string(), pos));
};
}
let end = reader.state.pos.clone();
literal("```", reader)?;
let encoded_string = template::EncodedString {
source_info: SourceInfo { start: start.clone(), end: end.clone() },
source_info: SourceInfo {
start: start.clone(),
end: end.clone(),
},
chars,
};
let elements = template::templatize(encoded_string)?;
Ok(Template {
quotes: false,
elements,
@ -448,7 +469,9 @@ pub fn eof(reader: &mut Reader) -> ParseResult<'static, ()> {
Err(Error {
pos: reader.state.clone().pos,
recoverable: false,
inner: ParseError::Expecting { value: String::from("eof") },
inner: ParseError::Expecting {
value: String::from("eof"),
},
})
}
}
@ -478,25 +501,22 @@ pub fn hex_digit_value(c: char) -> Option<u32> {
pub fn hex_digit(reader: &mut Reader) -> ParseResult<'static, u32> {
let start = reader.clone().state;
match reader.read() {
Some(c) => {
match hex_digit_value(c) {
Some(v) => Ok(v),
None => Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::HexDigit {},
})
}
}
Some(c) => match hex_digit_value(c) {
Some(v) => Ok(v),
None => Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::HexDigit {},
}),
},
None => Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::HexDigit {},
})
}),
}
}
#[cfg(test)]
mod tests {
use crate::core::common::Pos;
@ -613,19 +633,34 @@ mod tests {
let mut reader = Reader::init("");
let error = literal("hello", &mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("hello") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("hello")
}
);
assert_eq!(reader.state.cursor, 0);
let mut reader = Reader::init("hi");
let error = literal("hello", &mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("hello") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("hello")
}
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("he");
let error = literal("hello", &mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("hello") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("hello")
}
);
assert_eq!(reader.state.cursor, 2);
}
@ -641,7 +676,6 @@ mod tests {
);
}
#[test]
fn test_key_value() {
let mut reader = Reader::init("message: hello {{name}}! # comment");
@ -715,20 +749,22 @@ mod tests {
#[test]
fn test_filename() {
let mut reader = Reader::init("data/data.bin");
assert_eq!(filename(&mut reader).unwrap(),
Filename {
value: String::from("data/data.bin"),
source_info: SourceInfo::init(1, 1, 1, 14),
}
assert_eq!(
filename(&mut reader).unwrap(),
Filename {
value: String::from("data/data.bin"),
source_info: SourceInfo::init(1, 1, 1, 14),
}
);
assert_eq!(reader.state.cursor, 13);
let mut reader = Reader::init("data.bin");
assert_eq!(filename(&mut reader).unwrap(),
Filename {
value: String::from("data.bin"),
source_info: SourceInfo::init(1, 1, 1, 9),
}
assert_eq!(
filename(&mut reader).unwrap(),
Filename {
value: String::from("data.bin"),
source_info: SourceInfo::init(1, 1, 1, 9),
}
);
assert_eq!(reader.state.cursor, 8);
}
@ -754,12 +790,22 @@ mod tests {
let mut reader = Reader::init("xxx");
let error = boolean(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("true|false") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("true|false")
}
);
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("trux");
let error = boolean(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("true|false") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("true|false")
}
);
assert_eq!(error.recoverable, true);
}
@ -783,19 +829,34 @@ mod tests {
let mut reader = Reader::init("");
let error = natural(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("01");
let error = natural(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 2 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.recoverable, false);
let mut reader = Reader::init("x");
let error = natural(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.recoverable, true);
}
@ -813,38 +874,92 @@ mod tests {
let mut reader = Reader::init("x");
let error = integer(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.recoverable, true);
}
#[test]
fn test_float() {
let mut reader = Reader::init("1.0");
assert_eq!(float(&mut reader).unwrap(), Float { int: 1, decimal: 0, decimal_digits: 1 });
assert_eq!(
float(&mut reader).unwrap(),
Float {
int: 1,
decimal: 0,
decimal_digits: 1
}
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("-1.0");
assert_eq!(float(&mut reader).unwrap(), Float { int: -1, decimal: 0, decimal_digits: 1 });
assert_eq!(
float(&mut reader).unwrap(),
Float {
int: -1,
decimal: 0,
decimal_digits: 1
}
);
assert_eq!(reader.state.cursor, 4);
let mut reader = Reader::init("1.1");
assert_eq!(float(&mut reader).unwrap(), Float { int: 1, decimal: 100_000_000_000_000_000, decimal_digits: 1 });
assert_eq!(
float(&mut reader).unwrap(),
Float {
int: 1,
decimal: 100_000_000_000_000_000,
decimal_digits: 1
}
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("1.100");
assert_eq!(float(&mut reader).unwrap(), Float { int: 1, decimal: 100_000_000_000_000_000, decimal_digits: 3 });
assert_eq!(
float(&mut reader).unwrap(),
Float {
int: 1,
decimal: 100_000_000_000_000_000,
decimal_digits: 3
}
);
assert_eq!(reader.state.cursor, 5);
let mut reader = Reader::init("1.01");
assert_eq!(float(&mut reader).unwrap(), Float { int: 1, decimal: 10_000_000_000_000_000, decimal_digits: 2 });
assert_eq!(
float(&mut reader).unwrap(),
Float {
int: 1,
decimal: 10_000_000_000_000_000,
decimal_digits: 2
}
);
assert_eq!(reader.state.cursor, 4);
let mut reader = Reader::init("1.010");
assert_eq!(float(&mut reader).unwrap(), Float { int: 1, decimal: 10_000_000_000_000_000, decimal_digits: 3 });
assert_eq!(
float(&mut reader).unwrap(),
Float {
int: 1,
decimal: 10_000_000_000_000_000,
decimal_digits: 3
}
);
assert_eq!(reader.state.cursor, 5);
let mut reader = Reader::init("-0.333333333333333333");
assert_eq!(float(&mut reader).unwrap(), Float { int: 0, decimal: 333_333_333_333_333_333, decimal_digits: 18 });
assert_eq!(
float(&mut reader).unwrap(),
Float {
int: 0,
decimal: 333_333_333_333_333_333,
decimal_digits: 18
}
);
assert_eq!(reader.state.cursor, 21);
}
@ -852,37 +967,67 @@ mod tests {
fn test_float_error() {
let mut reader = Reader::init("");
let error = float(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("-");
let error = float(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 2 });
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("1");
let error = float(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from(".") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from(".")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 2 });
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("1x");
let error = float(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from(".") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from(".")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 2 });
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("1.");
let error = float(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 3 });
assert_eq!(error.recoverable, false);
let mut reader = Reader::init("1.x");
let error = float(&mut reader).err().unwrap();
assert_eq!(error.inner, ParseError::Expecting { value: String::from("natural") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("natural")
}
);
assert_eq!(error.pos, Pos { line: 1, column: 3 });
assert_eq!(error.recoverable, false);
}
@ -890,108 +1035,117 @@ mod tests {
#[test]
fn test_raw_string_empty() {
let mut reader = Reader::init("``````");
assert_eq!(raw_string(&mut reader).unwrap(), Bytes::RawString {
newline0: Whitespace {
value: String::from(""),
source_info: SourceInfo::init(1, 4, 1, 4),
},
value: Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(1, 4, 1, 4),
},
});
assert_eq!(
raw_string(&mut reader).unwrap(),
Bytes::RawString {
newline0: Whitespace {
value: String::from(""),
source_info: SourceInfo::init(1, 4, 1, 4),
},
value: Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(1, 4, 1, 4),
},
}
);
let mut reader = Reader::init("```\n```");
assert_eq!(raw_string(&mut reader).unwrap(), Bytes::RawString {
newline0: Whitespace {
value: String::from("\n"),
source_info: SourceInfo::init(1, 4, 2, 1),
},
value: Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(2, 1, 2, 1),
},
});
assert_eq!(
raw_string(&mut reader).unwrap(),
Bytes::RawString {
newline0: Whitespace {
value: String::from("\n"),
source_info: SourceInfo::init(1, 4, 2, 1),
},
value: Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(2, 1, 2, 1),
},
}
);
let mut reader = Reader::init("```\r\n```");
assert_eq!(raw_string(&mut reader).unwrap(), Bytes::RawString {
newline0: Whitespace {
value: String::from("\r\n"),
source_info: SourceInfo::init(1, 4, 2, 1),
},
value: Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(2, 1, 2, 1),
},
});
assert_eq!(
raw_string(&mut reader).unwrap(),
Bytes::RawString {
newline0: Whitespace {
value: String::from("\r\n"),
source_info: SourceInfo::init(1, 4, 2, 1),
},
value: Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(2, 1, 2, 1),
},
}
);
}
#[test]
fn test_raw_string_hello() {
let mut reader = Reader::init("```Hello World!```");
assert_eq!(raw_string(&mut reader).unwrap(), Bytes::RawString {
newline0: Whitespace {
value: String::from(""),
source_info: SourceInfo::init(1, 4, 1, 4),
},
value: Template {
quotes: false,
elements: vec![
TemplateElement::String {
assert_eq!(
raw_string(&mut reader).unwrap(),
Bytes::RawString {
newline0: Whitespace {
value: String::from(""),
source_info: SourceInfo::init(1, 4, 1, 4),
},
value: Template {
quotes: false,
elements: vec![TemplateElement::String {
value: "Hello World!".to_string(),
encoded: "Hello World!".to_string(),
}
],
source_info: SourceInfo::init(1, 4, 1, 16),
},
});
}],
source_info: SourceInfo::init(1, 4, 1, 16),
},
}
);
let mut reader = Reader::init("```Hello\nWorld!\n```");
assert_eq!(raw_string(&mut reader).unwrap(), Bytes::RawString {
newline0: Whitespace {
value: String::from(""),
source_info: SourceInfo::init(1, 4, 1, 4),
},
value: Template {
quotes: false,
elements: vec![
TemplateElement::String {
assert_eq!(
raw_string(&mut reader).unwrap(),
Bytes::RawString {
newline0: Whitespace {
value: String::from(""),
source_info: SourceInfo::init(1, 4, 1, 4),
},
value: Template {
quotes: false,
elements: vec![TemplateElement::String {
value: "Hello\nWorld!\n".to_string(),
encoded: "Hello\nWorld!\n".to_string(),
}
],
source_info: SourceInfo::init(1, 4, 3, 1),
},
});
}],
source_info: SourceInfo::init(1, 4, 3, 1),
},
}
);
}
#[test]
fn test_raw_string_csv() {
let mut reader = Reader::init("```\nline1\nline2\nline3\n```");
assert_eq!(raw_string(&mut reader).unwrap(), Bytes::RawString {
newline0: Whitespace {
value: String::from("\n"),
source_info: SourceInfo::init(1, 4, 2, 1),
},
value: Template {
quotes: false,
elements: vec![
TemplateElement::String {
assert_eq!(
raw_string(&mut reader).unwrap(),
Bytes::RawString {
newline0: Whitespace {
value: String::from("\n"),
source_info: SourceInfo::init(1, 4, 2, 1),
},
value: Template {
quotes: false,
elements: vec![TemplateElement::String {
value: "line1\nline2\nline3\n".to_string(),
encoded: "line1\nline2\nline3\n".to_string(),
}
],
source_info: SourceInfo::init(2, 1, 5, 1),
},
});
}],
source_info: SourceInfo::init(2, 1, 5, 1),
},
}
);
}
#[test]
fn test_raw_string_one_emptyline() {
// one newline
// the value takes the value of the newline??
let mut reader = Reader::init("```\n\n```");
@ -1004,12 +1158,10 @@ mod tests {
},
value: Template {
quotes: false,
elements: vec![
TemplateElement::String {
value: "\n".to_string(),
encoded: "\n".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "\n".to_string(),
encoded: "\n".to_string(),
}],
source_info: SourceInfo::init(2, 1, 3, 1),
},
}
@ -1026,12 +1178,10 @@ mod tests {
},
value: Template {
quotes: false,
elements: vec![
TemplateElement::String {
value: "\r\n".to_string(),
encoded: "\r\n".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "\r\n".to_string(),
encoded: "\r\n".to_string(),
}],
source_info: SourceInfo::init(2, 1, 3, 1),
},
}
@ -1043,44 +1193,59 @@ mod tests {
let mut reader = Reader::init("xxx");
let error = raw_string(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("```") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("```")
}
);
assert_eq!(error.recoverable, true);
let mut reader = Reader::init("```\nxxx");
let error = raw_string(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 2, column: 4 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("```") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("```")
}
);
assert_eq!(error.recoverable, false);
let mut reader = Reader::init("```xxx");
let error = raw_string(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 7 });
assert_eq!(error.inner, ParseError::Expecting { value: String::from("```") });
assert_eq!(
error.inner,
ParseError::Expecting {
value: String::from("```")
}
);
assert_eq!(error.recoverable, false);
}
#[test]
fn test_raw_string_value() {
let mut reader = Reader::init("```");
assert_eq!(raw_string_value(&mut reader).unwrap(), Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(1, 1, 1, 1),
});
assert_eq!(
raw_string_value(&mut reader).unwrap(),
Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(1, 1, 1, 1),
}
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("hello```");
assert_eq!(
raw_string_value(&mut reader).unwrap(),
Template {
quotes: false,
elements: vec![
TemplateElement::String {
value: "hello".to_string(),
encoded: "hello".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "hello".to_string(),
encoded: "hello".to_string(),
}],
source_info: SourceInfo::init(1, 1, 1, 6),
}
);

View File

@ -4,10 +4,10 @@ use crate::core::common::SourceInfo;
use super::combinators::*;
use super::cookiepath::cookiepath;
use super::ParseResult;
use super::primitives::*;
use super::reader::Reader;
use super::string::*;
use super::ParseResult;
pub fn query(reader: &mut Reader) -> ParseResult<'static, Query> {
let start = reader.state.pos.clone();
@ -19,7 +19,6 @@ pub fn query(reader: &mut Reader) -> ParseResult<'static, Query> {
})
}
pub fn subquery(reader: &mut Reader) -> ParseResult<'static, Subquery> {
let start = reader.state.pos.clone();
let value = subquery_value(reader)?;
@ -30,7 +29,6 @@ pub fn subquery(reader: &mut Reader) -> ParseResult<'static, Subquery> {
})
}
fn query_value(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
choice(
vec![
@ -47,13 +45,11 @@ fn query_value(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
)
}
fn status_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
try_literal("status", reader)?;
Ok(QueryValue::Status {})
}
fn header_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
try_literal("header", reader)?;
let space0 = one_or_more_spaces(reader)?;
@ -61,7 +57,6 @@ fn header_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
Ok(QueryValue::Header { space0, name })
}
fn cookie_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
try_literal("cookie", reader)?;
let space0 = one_or_more_spaces(reader)?;
@ -71,19 +66,20 @@ fn cookie_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
// or decode escape sequence with the cookiepath parser
let mut cookiepath_reader = Reader::init(s.as_str());
cookiepath_reader.state.pos = Pos { line: start.line, column: start.column + 1 };
cookiepath_reader.state.pos = Pos {
line: start.line,
column: start.column + 1,
};
let expr = cookiepath(&mut cookiepath_reader)?;
Ok(QueryValue::Cookie { space0, expr })
}
fn body_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
try_literal("body", reader)?;
Ok(QueryValue::Body {})
}
fn xpath_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
try_literal("xpath", reader)?;
let space0 = one_or_more_spaces(reader)?;
@ -91,29 +87,27 @@ fn xpath_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
Ok(QueryValue::Xpath { space0, expr })
}
fn jsonpath_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
try_literal("jsonpath", reader)?;
let space0 = one_or_more_spaces(reader)?;
//let expr = jsonpath_expr(reader)?;
// let start = reader.state.pos.clone();
let expr = quoted_template(reader)?;
// let end = reader.state.pos.clone();
// let expr = Template {
// elements: template.elements.iter().map(|e| match e {
// TemplateElement::String { value, encoded } => HurlTemplateElement::Literal {
// value: HurlString2 { value: value.clone(), encoded: Some(encoded.clone()) }
// },
// TemplateElement::Expression(value) => HurlTemplateElement::Expression { value: value.clone() }
// }).collect(),
// quotes: true,
// source_info: SourceInfo { start, end },
// };
// let end = reader.state.pos.clone();
// let expr = Template {
// elements: template.elements.iter().map(|e| match e {
// TemplateElement::String { value, encoded } => HurlTemplateElement::Literal {
// value: HurlString2 { value: value.clone(), encoded: Some(encoded.clone()) }
// },
// TemplateElement::Expression(value) => HurlTemplateElement::Expression { value: value.clone() }
// }).collect(),
// quotes: true,
// source_info: SourceInfo { start, end },
// };
Ok(QueryValue::Jsonpath { space0, expr })
}
fn regex_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
try_literal("regex", reader)?;
let space0 = one_or_more_spaces(reader)?;
@ -121,7 +115,6 @@ fn regex_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
Ok(QueryValue::Regex { space0, expr })
}
fn variable_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
try_literal("variable", reader)?;
let space0 = one_or_more_spaces(reader)?;
@ -129,17 +122,10 @@ fn variable_query(reader: &mut Reader) -> ParseResult<'static, QueryValue> {
Ok(QueryValue::Variable { space0, name })
}
fn subquery_value(reader: &mut Reader) -> ParseResult<'static, SubqueryValue> {
choice(
vec![
regex_subquery,
],
reader,
)
choice(vec![regex_subquery], reader)
}
fn regex_subquery(reader: &mut Reader) -> ParseResult<'static, SubqueryValue> {
try_literal("regex", reader)?;
let space0 = one_or_more_spaces(reader)?;
@ -147,7 +133,6 @@ fn regex_subquery(reader: &mut Reader) -> ParseResult<'static, SubqueryValue> {
Ok(SubqueryValue::Regex { space0, expr })
}
#[cfg(test)]
mod tests {
use super::*;
@ -155,19 +140,25 @@ mod tests {
#[test]
fn test_query() {
let mut reader = Reader::init("status");
assert_eq!(query(&mut reader).unwrap(), Query {
source_info: SourceInfo::init(1, 1, 1, 7),
value: QueryValue::Status {},
});
assert_eq!(
query(&mut reader).unwrap(),
Query {
source_info: SourceInfo::init(1, 1, 1, 7),
value: QueryValue::Status {},
}
);
}
#[test]
fn test_status_query() {
let mut reader = Reader::init("status");
assert_eq!(query(&mut reader).unwrap(), Query {
source_info: SourceInfo::init(1, 1, 1, 7),
value: QueryValue::Status {},
});
assert_eq!(
query(&mut reader).unwrap(),
Query {
source_info: SourceInfo::init(1, 1, 1, 7),
value: QueryValue::Status {},
}
);
}
#[test]
@ -182,19 +173,16 @@ mod tests {
},
name: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "Foo".to_string(),
encoded: "Foo".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "Foo".to_string(),
encoded: "Foo".to_string(),
}],
source_info: SourceInfo::init(1, 8, 1, 13),
},
}
);
}
#[test]
fn test_cookie_query() {
let mut reader = Reader::init("cookie \"Foo[Domain]\"");
@ -208,12 +196,10 @@ mod tests {
expr: CookiePath {
name: Template {
quotes: false,
elements: vec![
TemplateElement::String {
value: "Foo".to_string(),
encoded: "Foo".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "Foo".to_string(),
encoded: "Foo".to_string(),
}],
source_info: SourceInfo::init(1, 9, 1, 12),
},
attribute: Some(CookieAttribute {
@ -227,9 +213,9 @@ mod tests {
source_info: SourceInfo::init(1, 19, 1, 19),
},
}),
},
});
}
);
assert_eq!(reader.state.cursor, 20);
// todo test with escape sequence
@ -248,12 +234,10 @@ mod tests {
},
expr: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: String::from("normalize-space(//head/title)"),
encoded: String::from("normalize-space(//head/title)"),
}
],
elements: vec![TemplateElement::String {
value: String::from("normalize-space(//head/title)"),
encoded: String::from("normalize-space(//head/title)"),
}],
source_info: SourceInfo::init(1, 7, 1, 38),
},
},
@ -287,12 +271,10 @@ mod tests {
source_info: SourceInfo::init(1, 9, 1, 10),
},
expr: Template {
elements: vec![
TemplateElement::String {
value: "$['statusCode']".to_string(),
encoded: "$['statusCode']".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "$['statusCode']".to_string(),
encoded: "$['statusCode']".to_string(),
}],
quotes: true,
//delimiter: "\"".to_string(),
source_info: SourceInfo::init(1, 10, 1, 27),
@ -308,13 +290,10 @@ mod tests {
source_info: SourceInfo::init(1, 9, 1, 10),
},
expr: Template {
elements: vec![
TemplateElement::String {
value: "$.success".to_string(),
encoded: "$.success".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "$.success".to_string(),
encoded: "$.success".to_string(),
}],
quotes: true,
//delimiter: "\"".to_string(),
source_info: SourceInfo::init(1, 10, 1, 21),
@ -322,4 +301,4 @@ mod tests {
},
);
}
}
}

View File

@ -44,7 +44,6 @@ impl Reader {
self.state.cursor == self.buffer.len()
}
pub fn read(&mut self) -> Option<char> {
match self.buffer.get(self.state.cursor) {
None => None,
@ -65,7 +64,7 @@ impl Reader {
pub fn peek(&mut self) -> Option<char> {
match self.buffer.get(self.state.cursor) {
None => None,
Some(c) => Some(*c)
Some(c) => Some(*c),
}
}
@ -74,12 +73,13 @@ impl Reader {
loop {
match self.peek() {
None => return s,
Some(c) =>
Some(c) => {
if predicate(&c) {
s.push(self.read().unwrap())
} else {
return s;
}
}
}
}
}
@ -111,28 +111,20 @@ impl Reader {
.collect()
}
pub fn slice(&self, start: usize, end: usize) -> String {
self.buffer.as_slice()[start..end]
.iter()
.collect()
self.buffer.as_slice()[start..end].iter().collect()
}
pub fn from(&self, start: usize) -> String {
let end = self.state.cursor;
self.buffer.as_slice()[start..end]
.iter()
.collect()
self.buffer.as_slice()[start..end].iter().collect()
}
}
fn is_combining_character(c: char) -> bool {
c > '\u{0300}' && c < '\u{036F}' // Combining Diacritical Marks (0300036F)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -21,12 +21,12 @@ use crate::core::common::SourceInfo;
use super::combinators::*;
use super::error::*;
use super::ParseResult;
use super::predicate::predicate;
use super::primitives::*;
use super::query::{query, subquery};
use super::reader::Reader;
use super::string::*;
use super::ParseResult;
pub fn request_sections(reader: &mut Reader) -> ParseResult<'static, Vec<Section>> {
let sections = zero_or_more(|p1| section(p1), reader)?;
@ -37,7 +37,7 @@ pub fn request_sections(reader: &mut Reader) -> ParseResult<'static, Vec<Section
"MultipartFormData",
"Cookies",
]
.contains(&section.name())
.contains(&section.name())
{
return Err(Error {
pos: section.source_info.start,
@ -118,9 +118,7 @@ fn section_value_form_params(reader: &mut Reader) -> ParseResult<'static, Sectio
Ok(SectionValue::FormParams(items))
}
fn section_value_multipart_form_data(
reader: &mut Reader,
) -> ParseResult<'static, SectionValue> {
fn section_value_multipart_form_data(reader: &mut Reader) -> ParseResult<'static, SectionValue> {
let items = zero_or_more(|p1| multipart_param(p1), reader)?;
Ok(SectionValue::MultipartFormData(items))
}
@ -167,8 +165,15 @@ fn cookie(reader: &mut Reader) -> ParseResult<'static, Cookie> {
///
fn cookie_value(reader: &mut Reader) -> ParseResult<'static, CookieValue> {
//let start = reader.state.clone();
let value = reader.read_while(|c| c.is_ascii_alphanumeric()
|| *c == '_' || *c == '-' || *c == '/' || *c == '%' || *c == '[' || *c == ']');
let value = reader.read_while(|c| {
c.is_ascii_alphanumeric()
|| *c == '_'
|| *c == '-'
|| *c == '/'
|| *c == '%'
|| *c == '['
|| *c == ']'
});
Ok(CookieValue { value })
}
@ -360,7 +365,6 @@ fn assert(reader: &mut Reader) -> ParseResult<'static, Assert> {
})
}
#[cfg(test)]
mod tests {
use super::*;
@ -374,7 +378,8 @@ mod tests {
#[test]
fn test_asserts_section() {
let mut reader = Reader::init("[Asserts]\nheader \"Location\" equals \"https://google.fr\"\n");
let mut reader =
Reader::init("[Asserts]\nheader \"Location\" equals \"https://google.fr\"\n");
assert_eq!(
section(&mut reader).unwrap(),
@ -465,7 +470,8 @@ mod tests {
#[test]
fn test_asserts_section_error() {
let mut reader = Reader::init("x[Assertsx]\nheader Location equals \"https://google.fr\"\n");
let mut reader =
Reader::init("x[Assertsx]\nheader Location equals \"https://google.fr\"\n");
let error = section(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(
@ -500,9 +506,14 @@ mod tests {
fn test_cookie_error() {
let mut reader = Reader::init("Foo: \"Bar\"");
let error = cookie(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 6});
assert_eq!(error.pos, Pos { line: 1, column: 6 });
assert_eq!(error.recoverable, false);
assert_eq!(error.inner, ParseError::Expecting { value: "line_terminator".to_string()});
assert_eq!(
error.inner,
ParseError::Expecting {
value: "line_terminator".to_string()
}
);
}
#[test]
@ -524,7 +535,6 @@ mod tests {
);
}
#[test]
fn test_file_value() {
let mut reader = Reader::init("file,hello.txt;");

View File

@ -22,22 +22,26 @@ use crate::core::common::SourceInfo;
use super::combinators::*;
use super::error::*;
use super::expr;
use super::ParseResult;
use super::primitives::*;
use super::reader::{Reader, ReaderState};
use super::ParseResult;
pub type CharParser = fn(&mut Reader) -> ParseResult<(char, String)>;
pub fn unquoted_template(reader: &mut Reader) -> ParseResult<'static, Template> {
let start = reader.state.pos.clone();
match reader.peek() {
Some(' ') | Some('\t') | Some('\n') | Some('#') | None => return Ok(Template {
quotes: false,
elements: vec![],
source_info: SourceInfo { start: start.clone(), end: start },
}),
Some(' ') | Some('\t') | Some('\n') | Some('#') | None => {
return Ok(Template {
quotes: false,
elements: vec![],
source_info: SourceInfo {
start: start.clone(),
end: start,
},
})
}
_ => {}
}
@ -45,11 +49,14 @@ pub fn unquoted_template(reader: &mut Reader) -> ParseResult<'static, Template>
// TODO pas escape vector => expected fn pointer, found closure
let mut elements = zero_or_more(
|reader1| template_element(|reader2| any_char(vec!['#'], reader2), reader1),
reader)?;
reader,
)?;
// check trailing space
if let Some(TemplateElement::String { value, encoded }) = elements.last() {
let keep = encoded.trim_end_matches(|c: char| c == ' ' || c == '\t').len();
let keep = encoded
.trim_end_matches(|c: char| c == ' ' || c == '\t')
.len();
let trailing_space = encoded.len() - keep;
if trailing_space > 0 {
let value = value.as_str()[..value.len() - trailing_space].to_string();
@ -60,22 +67,26 @@ pub fn unquoted_template(reader: &mut Reader) -> ParseResult<'static, Template>
let cursor = reader.state.cursor - trailing_space;
let line = reader.state.pos.line;
let column = reader.state.pos.column - trailing_space;
reader.state = ReaderState { cursor, pos: Pos { line, column } };
reader.state = ReaderState {
cursor,
pos: Pos { line, column },
};
}
}
Ok(Template {
quotes,
elements,
source_info: SourceInfo { start, end: reader.state.pos.clone() },
source_info: SourceInfo {
start,
end: reader.state.pos.clone(),
},
})
}
pub fn unquoted_string_key(reader: &mut Reader) -> ParseResult<'static, EncodedString> {
let start = reader.state.pos.clone();
let quotes = false;
let mut value = "".to_string();
let mut encoded = "".to_string();
@ -86,7 +97,7 @@ pub fn unquoted_string_key(reader: &mut Reader) -> ParseResult<'static, EncodedS
value.push(c);
encoded.push_str(reader.from(save.cursor).as_str())
}
Err(e) =>
Err(e) => {
if e.recoverable {
reader.state = save.clone();
match reader.read() {
@ -104,6 +115,7 @@ pub fn unquoted_string_key(reader: &mut Reader) -> ParseResult<'static, EncodedS
} else {
return Err(e);
}
}
}
}
@ -112,13 +124,20 @@ pub fn unquoted_string_key(reader: &mut Reader) -> ParseResult<'static, EncodedS
return Err(Error {
pos: start,
recoverable: true,
inner: ParseError::Expecting { value: "key string".to_string() },
inner: ParseError::Expecting {
value: "key string".to_string(),
},
});
}
let end = reader.state.pos.clone();
let source_info = SourceInfo { start, end };
Ok(EncodedString { quotes, encoded, value, source_info })
Ok(EncodedString {
quotes,
encoded,
value,
source_info,
})
}
// todo should return an EncodedString
@ -146,19 +165,23 @@ pub fn quoted_template(reader: &mut Reader) -> ParseResult<'static, Template> {
let mut elements = vec![];
loop {
match template_element_expression(reader) {
Err(e) => if e.recoverable {
match template_element_string(|reader1| any_char(vec!['"'], reader1), reader) {
Err(e) => if e.recoverable {
break;
} else {
return Err(e);
Err(e) => {
if e.recoverable {
match template_element_string(|reader1| any_char(vec!['"'], reader1), reader) {
Err(e) => {
if e.recoverable {
break;
} else {
return Err(e);
}
}
Ok(element) => elements.push(element),
}
Ok(element) => elements.push(element)
} else {
return Err(e);
}
} else {
return Err(e);
},
Ok(element) => elements.push(element)
}
Ok(element) => elements.push(element),
}
}
@ -171,16 +194,26 @@ pub fn quoted_template(reader: &mut Reader) -> ParseResult<'static, Template> {
})
}
fn template_element(char_parser: CharParser, reader: &mut Reader) -> ParseResult<'static, TemplateElement> {
fn template_element(
char_parser: CharParser,
reader: &mut Reader,
) -> ParseResult<'static, TemplateElement> {
match template_element_expression(reader) {
Err(e) => if e.recoverable {
template_element_string(char_parser, reader)
} else { Err(e) },
Err(e) => {
if e.recoverable {
template_element_string(char_parser, reader)
} else {
Err(e)
}
}
r => r,
}
}
fn template_element_string(char_parser: CharParser, reader: &mut Reader) -> ParseResult<'static, TemplateElement> {
fn template_element_string(
char_parser: CharParser,
reader: &mut Reader,
) -> ParseResult<'static, TemplateElement> {
let start = reader.state.clone();
let mut value = "".to_string();
let mut encoded = "".to_string();
@ -221,7 +254,9 @@ fn template_element_string(char_parser: CharParser, reader: &mut Reader) -> Pars
Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Expecting { value: "string".to_string() },
inner: ParseError::Expecting {
value: "string".to_string(),
},
})
} else {
Ok(TemplateElement::String { value, encoded })
@ -237,21 +272,27 @@ fn any_char(except: Vec<char>, reader: &mut Reader) -> ParseResult<'static, (cha
let start = reader.state.clone();
match escape_char(reader) {
Ok(c) => Ok((c, reader.from(start.cursor))),
Err(e) =>
Err(e) => {
if e.recoverable {
reader.state = start.clone();
match reader.read() {
None => Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Expecting { value: "char".to_string() },
inner: ParseError::Expecting {
value: "char".to_string(),
},
}),
Some(c) => {
if except.contains(&c) || vec!['\\', '\x08', '\n', '\x0c', '\r', '\t'].contains(&c) {
if except.contains(&c)
|| vec!['\\', '\x08', '\n', '\x0c', '\r', '\t'].contains(&c)
{
Err(Error {
pos: start.pos,
recoverable: true,
inner: ParseError::Expecting { value: "char".to_string() },
inner: ParseError::Expecting {
value: "char".to_string(),
},
})
} else {
Ok((c, reader.from(start.cursor)))
@ -261,6 +302,7 @@ fn any_char(except: Vec<char>, reader: &mut Reader) -> ParseResult<'static, (cha
} else {
Err(e)
}
}
}
}
@ -290,11 +332,13 @@ fn unicode(reader: &mut Reader) -> ParseResult<'static, char> {
literal("{", reader)?;
let v = hex_value(reader)?;
let c = match std::char::from_u32(v) {
None => return Err(Error {
pos: reader.clone().state.pos,
recoverable: false,
inner: ParseError::Unicode {},
}),
None => {
return Err(Error {
pos: reader.clone().state.pos,
recoverable: false,
inner: ParseError::Unicode {},
})
}
Some(c) => c,
};
literal("}", reader)?;
@ -309,11 +353,10 @@ fn hex_value(reader: &mut Reader) -> ParseResult<'static, u32> {
for d in digits.iter() {
v += weight * d;
weight *= 16;
};
}
Ok(v)
}
#[cfg(test)]
mod tests {
use super::*;
@ -321,60 +364,93 @@ mod tests {
#[test]
fn test_unquoted_template() {
let mut reader = Reader::init("");
assert_eq!(unquoted_template(&mut reader).unwrap(), Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(1, 1, 1, 1),
});
assert_eq!(
unquoted_template(&mut reader).unwrap(),
Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(1, 1, 1, 1),
}
);
assert_eq!(reader.state.cursor, 0);
let mut reader = Reader::init("a\\u{23}");
assert_eq!(unquoted_template(&mut reader).unwrap(), Template {
quotes: false,
elements: vec![
TemplateElement::String { value: "a#".to_string(), encoded: "a\\u{23}".to_string() }
],
source_info: SourceInfo::init(1, 1, 1, 8),
});
assert_eq!(
unquoted_template(&mut reader).unwrap(),
Template {
quotes: false,
elements: vec![TemplateElement::String {
value: "a#".to_string(),
encoded: "a\\u{23}".to_string()
}],
source_info: SourceInfo::init(1, 1, 1, 8),
}
);
assert_eq!(reader.state.cursor, 7);
let mut reader = Reader::init("hello\\u{20}{{name}}!");
assert_eq!(unquoted_template(&mut reader).unwrap(), Template {
quotes: false,
elements: vec![
TemplateElement::String { value: "hello ".to_string(), encoded: "hello\\u{20}".to_string() },
TemplateElement::Expression(Expr {
space0: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 14, 1, 14) },
variable: Variable { name: "name".to_string(), source_info: SourceInfo::init(1, 14, 1, 18) },
space1: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 18, 1, 18) },
}),
TemplateElement::String { value: "!".to_string(), encoded: "!".to_string() },
],
source_info: SourceInfo::init(1, 1, 1, 21),
});
assert_eq!(
unquoted_template(&mut reader).unwrap(),
Template {
quotes: false,
elements: vec![
TemplateElement::String {
value: "hello ".to_string(),
encoded: "hello\\u{20}".to_string()
},
TemplateElement::Expression(Expr {
space0: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 14, 1, 14)
},
variable: Variable {
name: "name".to_string(),
source_info: SourceInfo::init(1, 14, 1, 18)
},
space1: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 18, 1, 18)
},
}),
TemplateElement::String {
value: "!".to_string(),
encoded: "!".to_string()
},
],
source_info: SourceInfo::init(1, 1, 1, 21),
}
);
assert_eq!(reader.state.cursor, 20);
let mut reader = Reader::init("hello\n");
assert_eq!(unquoted_template(&mut reader).unwrap(), Template {
quotes: false,
elements: vec![
TemplateElement::String { value: "hello".to_string(), encoded: "hello".to_string() },
],
source_info: SourceInfo::init(1, 1, 1, 6),
});
assert_eq!(
unquoted_template(&mut reader).unwrap(),
Template {
quotes: false,
elements: vec![TemplateElement::String {
value: "hello".to_string(),
encoded: "hello".to_string()
},],
source_info: SourceInfo::init(1, 1, 1, 6),
}
);
assert_eq!(reader.state.cursor, 5);
}
#[test]
fn test_unquoted_template_trailing_space() {
let mut reader = Reader::init("hello # comment");
assert_eq!(unquoted_template(&mut reader).unwrap(), Template {
quotes: false,
elements: vec![
TemplateElement::String { value: "hello".to_string(), encoded: "hello".to_string() },
],
source_info: SourceInfo::init(1, 1, 1, 6),
});
assert_eq!(
unquoted_template(&mut reader).unwrap(),
Template {
quotes: false,
elements: vec![TemplateElement::String {
value: "hello".to_string(),
encoded: "hello".to_string()
},],
source_info: SourceInfo::init(1, 1, 1, 6),
}
);
assert_eq!(reader.state.cursor, 5);
assert_eq!(reader.state.pos, Pos { line: 1, column: 6 });
}
@ -382,12 +458,14 @@ mod tests {
#[test]
fn test_unquoted_template_empty() {
let mut reader = Reader::init(" hi");
assert_eq!(unquoted_template(&mut reader).unwrap(),
Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(1, 1, 1, 1),
});
assert_eq!(
unquoted_template(&mut reader).unwrap(),
Template {
quotes: false,
elements: vec![],
source_info: SourceInfo::init(1, 1, 1, 1),
}
);
assert_eq!(reader.state.cursor, 0);
}
@ -395,21 +473,27 @@ mod tests {
#[test]
fn test_unquoted_key() {
let mut reader = Reader::init("key");
assert_eq!(unquoted_string_key(&mut reader).unwrap(), EncodedString {
value: "key".to_string(),
encoded: "key".to_string(),
quotes: false,
source_info: SourceInfo::init(1, 1, 1, 4),
});
assert_eq!(
unquoted_string_key(&mut reader).unwrap(),
EncodedString {
value: "key".to_string(),
encoded: "key".to_string(),
quotes: false,
source_info: SourceInfo::init(1, 1, 1, 4),
}
);
assert_eq!(reader.state.cursor, 3);
let mut reader = Reader::init("key\\u{20}\\u{3a} :");
assert_eq!(unquoted_string_key(&mut reader).unwrap(), EncodedString {
value: "key :".to_string(),
encoded: "key\\u{20}\\u{3a}".to_string(),
quotes: false,
source_info: SourceInfo::init(1, 1, 1, 16),
});
assert_eq!(
unquoted_string_key(&mut reader).unwrap(),
EncodedString {
value: "key :".to_string(),
encoded: "key\\u{20}\\u{3a}".to_string(),
quotes: false,
source_info: SourceInfo::init(1, 1, 1, 16),
}
);
assert_eq!(reader.state.cursor, 15);
}
@ -418,7 +502,12 @@ mod tests {
let mut reader = Reader::init("");
let error = unquoted_string_key(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: "key string".to_string() });
assert_eq!(
error.inner,
ParseError::Expecting {
value: "key string".to_string()
}
);
let mut reader = Reader::init("\\l");
let error = unquoted_string_key(&mut reader).err().unwrap();
@ -429,31 +518,42 @@ mod tests {
#[test]
fn test_quoted_template() {
let mut reader = Reader::init("\"\"");
assert_eq!(quoted_template(&mut reader).unwrap(), Template {
quotes: true,
elements: vec![],
source_info: SourceInfo::init(1, 1, 1, 3),
});
assert_eq!(
quoted_template(&mut reader).unwrap(),
Template {
quotes: true,
elements: vec![],
source_info: SourceInfo::init(1, 1, 1, 3),
}
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("\"a#\"");
assert_eq!(quoted_template(&mut reader).unwrap(), Template {
quotes: true,
elements: vec![
TemplateElement::String { value: "a#".to_string(), encoded: "a#".to_string() }
],
source_info: SourceInfo::init(1, 1, 1, 5),
});
assert_eq!(
quoted_template(&mut reader).unwrap(),
Template {
quotes: true,
elements: vec![TemplateElement::String {
value: "a#".to_string(),
encoded: "a#".to_string()
}],
source_info: SourceInfo::init(1, 1, 1, 5),
}
);
assert_eq!(reader.state.cursor, 4);
let mut reader = Reader::init("\"{0}\"");
assert_eq!(quoted_template(&mut reader).unwrap(), Template {
quotes: true,
elements: vec![
TemplateElement::String { value: "{0}".to_string(), encoded: "{0}".to_string() }
],
source_info: SourceInfo::init(1, 1, 1, 6),
});
assert_eq!(
quoted_template(&mut reader).unwrap(),
Template {
quotes: true,
elements: vec![TemplateElement::String {
value: "{0}".to_string(),
encoded: "{0}".to_string()
}],
source_info: SourceInfo::init(1, 1, 1, 6),
}
);
assert_eq!(reader.state.cursor, 5);
}
@ -510,31 +610,53 @@ mod tests {
#[test]
fn test_template_element_expression() {
let mut reader = Reader::init("{{name}}");
assert_eq!(template_element_expression(&mut reader).unwrap(),
TemplateElement::Expression(Expr {
space0: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 3, 1, 3) },
variable: Variable { name: "name".to_string(), source_info: SourceInfo::init(1, 3, 1, 7) },
space1: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 7, 1, 7) },
})
assert_eq!(
template_element_expression(&mut reader).unwrap(),
TemplateElement::Expression(Expr {
space0: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 3, 1, 3)
},
variable: Variable {
name: "name".to_string(),
source_info: SourceInfo::init(1, 3, 1, 7)
},
space1: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 7, 1, 7)
},
})
);
}
#[test]
fn test_any_char() {
let mut reader = Reader::init("a");
assert_eq!(any_char(vec![], &mut reader).unwrap(), ('a', "a".to_string()));
assert_eq!(
any_char(vec![], &mut reader).unwrap(),
('a', "a".to_string())
);
assert_eq!(reader.state.cursor, 1);
let mut reader = Reader::init(" ");
assert_eq!(any_char(vec![], &mut reader).unwrap(), (' ', " ".to_string()));
assert_eq!(
any_char(vec![], &mut reader).unwrap(),
(' ', " ".to_string())
);
assert_eq!(reader.state.cursor, 1);
let mut reader = Reader::init("\\t");
assert_eq!(any_char(vec![], &mut reader).unwrap(), ('\t', "\\t".to_string()));
assert_eq!(
any_char(vec![], &mut reader).unwrap(),
('\t', "\\t".to_string())
);
assert_eq!(reader.state.cursor, 2);
let mut reader = Reader::init("#");
assert_eq!(any_char(vec![], &mut reader).unwrap(), ('#', "#".to_string()));
assert_eq!(
any_char(vec![], &mut reader).unwrap(),
('#', "#".to_string())
);
assert_eq!(reader.state.cursor, 1);
}
@ -569,7 +691,12 @@ mod tests {
let mut reader = Reader::init("x");
let error = escape_char(&mut reader).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 1 });
assert_eq!(error.inner, ParseError::Expecting { value: "\\".to_string() });
assert_eq!(
error.inner,
ParseError::Expecting {
value: "\\".to_string()
}
);
assert_eq!(error.recoverable, true);
assert_eq!(reader.state.cursor, 0);
}

View File

@ -22,16 +22,14 @@ use crate::core::common::SourceInfo;
use super::error;
use super::expr;
use super::ParseResult;
use super::reader::*;
use super::ParseResult;
pub struct EncodedString {
pub source_info: SourceInfo,
pub chars: Vec<(char, String, Pos)>,
}
pub fn templatize(encoded_string: EncodedString) -> ParseResult<'static, Vec<TemplateElement>> {
enum State {
String {},
@ -60,10 +58,7 @@ pub fn templatize(encoded_string: EncodedString) -> ParseResult<'static, Vec<Tem
State::FirstOpenBracket {} => {
if s.as_str() == "{" {
elements.push(TemplateElement::String {
value,
encoded,
});
elements.push(TemplateElement::String { value, encoded });
value = "".to_string();
encoded = "".to_string();
state = State::Template {};
@ -92,7 +87,10 @@ pub fn templatize(encoded_string: EncodedString) -> ParseResult<'static, Vec<Tem
State::FirstCloseBracket {} => {
if s.as_str() == "}" {
let mut reader = Reader::init(encoded.as_str());
reader.state = ReaderState { cursor: 0, pos: expression_start.unwrap().clone() };
reader.state = ReaderState {
cursor: 0,
pos: expression_start.unwrap().clone(),
};
let expression = expr::parse2(&mut reader)?;
elements.push(TemplateElement::Expression(expression));
value = "".to_string();
@ -119,26 +117,23 @@ pub fn templatize(encoded_string: EncodedString) -> ParseResult<'static, Vec<Tem
return Err(error::Error {
pos: encoded_string.source_info.end,
recoverable: false,
inner: error::ParseError::Expecting { value: "}}".to_string() },
inner: error::ParseError::Expecting {
value: "}}".to_string(),
},
});
}
}
if !value.is_empty() {
elements.push(TemplateElement::String {
value,
encoded,
})
elements.push(TemplateElement::String { value, encoded })
}
Ok(elements)
}
#[cfg(test)]
mod tests {
use crate::core::ast::{Variable, Whitespace};
use crate::core::ast::Expr;
use crate::core::ast::{Variable, Whitespace};
use super::*;
@ -158,42 +153,103 @@ mod tests {
('i', "i".to_string(), Pos { line: 1, column: 2 }),
(' ', "\\u0020".to_string(), Pos { line: 1, column: 3 }),
('{', "{".to_string(), Pos { line: 1, column: 9 }),
('{', "{".to_string(), Pos { line: 1, column: 10 }),
('n', "n".to_string(), Pos { line: 1, column: 11 }),
('a', "a".to_string(), Pos { line: 1, column: 12 }),
('m', "m".to_string(), Pos { line: 1, column: 13 }),
('e', "e".to_string(), Pos { line: 1, column: 14 }),
('}', "}".to_string(), Pos { line: 1, column: 15 }),
('}', "}".to_string(), Pos { line: 1, column: 16 }),
('!', "!".to_string(), Pos { line: 1, column: 17 }),
(
'{',
"{".to_string(),
Pos {
line: 1,
column: 10,
},
),
(
'n',
"n".to_string(),
Pos {
line: 1,
column: 11,
},
),
(
'a',
"a".to_string(),
Pos {
line: 1,
column: 12,
},
),
(
'm',
"m".to_string(),
Pos {
line: 1,
column: 13,
},
),
(
'e',
"e".to_string(),
Pos {
line: 1,
column: 14,
},
),
(
'}',
"}".to_string(),
Pos {
line: 1,
column: 15,
},
),
(
'}',
"}".to_string(),
Pos {
line: 1,
column: 16,
},
),
(
'!',
"!".to_string(),
Pos {
line: 1,
column: 17,
},
),
],
};
assert_eq!(templatize(encoded_string).unwrap(), vec![
TemplateElement::String {
value: "Hi ".to_string(),
encoded: "Hi\\u0020".to_string(),
},
TemplateElement::Expression(Expr {
space0: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 11, 1, 11),
assert_eq!(
templatize(encoded_string).unwrap(),
vec![
TemplateElement::String {
value: "Hi ".to_string(),
encoded: "Hi\\u0020".to_string(),
},
variable: Variable { name: "name".to_string(), source_info: SourceInfo::init(1, 11, 1, 15) },
space1: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 15, 1, 15),
TemplateElement::Expression(Expr {
space0: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 11, 1, 11),
},
variable: Variable {
name: "name".to_string(),
source_info: SourceInfo::init(1, 11, 1, 15)
},
space1: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 15, 1, 15),
},
}),
TemplateElement::String {
value: "!".to_string(),
encoded: "!".to_string(),
},
}),
TemplateElement::String {
value: "!".to_string(),
encoded: "!".to_string(),
},
]);
]
);
}
#[test]
fn test_templatize_error() {
// missing closing
// {{x
let encoded_string = EncodedString {
@ -201,12 +257,17 @@ mod tests {
chars: vec![
('{', "{".to_string(), Pos { line: 1, column: 1 }),
('{', "{".to_string(), Pos { line: 1, column: 2 }),
('x', "x".to_string(), Pos { line: 1, column: 3 })
('x', "x".to_string(), Pos { line: 1, column: 3 }),
],
};
let error = templatize(encoded_string).err().unwrap();
assert_eq!(error.pos, Pos { line: 1, column: 4 });
assert_eq!(error.inner, error::ParseError::Expecting { value: "}}".to_string() });
assert_eq!(
error.inner,
error::ParseError::Expecting {
value: "}}".to_string()
}
);
assert_eq!(error.recoverable, false);
}
}

View File

@ -20,24 +20,28 @@ use sxd_document::parser;
use crate::core::common::Pos;
use super::error::*;
use super::ParseResult;
use super::reader::Reader;
use super::ParseResult;
pub fn parse(reader: &mut Reader) -> ParseResult<'static, String> {
let mut buf = String::from("");
let start = reader.state.clone();
match reader.read() {
Some('<') => { buf.push('<') }
_ => return Err(Error {
pos: Pos { line: 1, column: 1 },
recoverable: true,
inner: ParseError::Xml {},
})
Some('<') => buf.push('<'),
_ => {
return Err(Error {
pos: Pos { line: 1, column: 1 },
recoverable: true,
inner: ParseError::Xml {},
})
}
}
loop {
match reader.read() {
None => { break; }
None => {
break;
}
Some(c) => {
buf.push(c);
if c == '>' && is_valid(buf.as_str()) {
@ -55,12 +59,11 @@ pub fn parse(reader: &mut Reader) -> ParseResult<'static, String> {
fn is_valid(s: &str) -> bool {
match parser::parse(s) {
Ok(_) => { true }
_ => false
Ok(_) => true,
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -99,16 +102,25 @@ mod tests {
#[test]
fn test_parsing_xml_brute_force() {
let mut reader = Reader::init("<users><user /></users>");
assert_eq!(parse(&mut reader).unwrap(), String::from("<users><user /></users>"));
assert_eq!(
parse(&mut reader).unwrap(),
String::from("<users><user /></users>")
);
assert_eq!(reader.state.cursor, 23);
let mut reader = Reader::init("<users><user /></users>xx");
assert_eq!(parse(&mut reader).unwrap(), String::from("<users><user /></users>"));
assert_eq!(
parse(&mut reader).unwrap(),
String::from("<users><user /></users>")
);
assert_eq!(reader.state.cursor, 23);
assert_eq!(reader.remaining(), String::from("xx"));
let mut reader = Reader::init("<?xml version=\"1.0\"?><users/>xxx");
assert_eq!(parse(&mut reader).unwrap(), String::from("<?xml version=\"1.0\"?><users/>"));
assert_eq!(
parse(&mut reader).unwrap(),
String::from("<?xml version=\"1.0\"?><users/>")
);
assert_eq!(reader.state.cursor, 29);
}
}

View File

@ -21,111 +21,138 @@ use std::collections::HashMap;
use crate::core::common::Value;
use crate::http;
use super::core::{Error, RunnerError};
use super::core::*;
use super::super::core::ast::*;
use super::core::*;
use super::core::{Error, RunnerError};
impl AssertResult {
pub fn fail(self) -> bool {
match self {
AssertResult::Version { actual, expected, .. } => actual != expected,
AssertResult::Status { actual, expected, .. } => actual != expected,
AssertResult::Version {
actual, expected, ..
} => actual != expected,
AssertResult::Status {
actual, expected, ..
} => actual != expected,
AssertResult::Header { .. } => false,
AssertResult::Explicit { .. } => true,
AssertResult::Body { .. } => true
AssertResult::Body { .. } => true,
}
}
pub fn error(self) -> Option<Error> {
match self {
AssertResult::Version { actual, expected, source_info } => {
AssertResult::Version {
actual,
expected,
source_info,
} => {
if expected.as_str() == "*" || actual == expected {
None
} else {
Some(Error {
source_info,
inner: RunnerError::AssertVersion { actual }
,
inner: RunnerError::AssertVersion { actual },
assert: false,
})
}
}
AssertResult::Status { actual, expected, source_info } => {
AssertResult::Status {
actual,
expected,
source_info,
} => {
if actual == expected {
None
} else {
Some(Error {
source_info,
inner: RunnerError::AssertStatus { actual: actual.to_string() },
inner: RunnerError::AssertStatus {
actual: actual.to_string(),
},
assert: false,
})
}
}
AssertResult::Header { actual, expected, source_info } => {
match actual {
AssertResult::Header {
actual,
expected,
source_info,
} => match actual {
Err(e) => Some(e),
Ok(s) => {
if s == expected {
None
} else {
Some(Error {
source_info,
inner: RunnerError::AssertHeaderValueError { actual: s },
assert: false,
})
}
}
},
AssertResult::Body {
actual,
expected,
source_info,
} => match expected {
Err(e) => Some(e),
Ok(expected) => match actual {
Err(e) => Some(e),
Ok(s) => {
if s == expected {
Ok(actual) => {
if actual == expected {
None
} else {
let actual = actual.to_string();
let expected = expected.to_string();
Some(Error {
source_info,
inner: RunnerError::AssertHeaderValueError { actual: s },
inner: RunnerError::AssertBodyValueError { actual, expected },
assert: false,
})
}
}
}
}
AssertResult::Body { actual, expected, source_info } => {
match expected {
Err(e) => Some(e),
Ok(expected) => {
match actual {
Err(e) => Some(e),
Ok(actual) => if actual == expected {
None
} else {
let actual = actual.to_string();
let expected = expected.to_string();
Some(Error {
source_info,
inner: RunnerError::AssertBodyValueError { actual, expected },
assert: false,
})
}
}
}
}
}
},
},
AssertResult::Explicit { actual: Err(e), .. } => { Some(e) }
AssertResult::Explicit { predicate_result: Some(Err(e)), .. } => { Some(e) }
AssertResult::Explicit { actual: Err(e), .. } => Some(e),
AssertResult::Explicit {
predicate_result: Some(Err(e)),
..
} => Some(e),
_ => None,
}
}
}
impl Assert {
pub fn eval(self, http_response: http::Response, variables: &HashMap<String, Value>) -> AssertResult {
pub fn eval(
self,
http_response: http::Response,
variables: &HashMap<String, Value>,
) -> AssertResult {
let actual = self.query.eval(variables, http_response);
let source_info = self.predicate.clone().predicate_func.source_info;
let predicate_result = match actual.clone() {
Err(_) => None,
Ok(actual) => Some(self.predicate.eval(variables, actual))
Ok(actual) => Some(self.predicate.eval(variables, actual)),
};
AssertResult::Explicit { actual, source_info, predicate_result }
AssertResult::Explicit {
actual,
source_info,
predicate_result,
}
}
}
#[cfg(test)]
pub mod tests {
use crate::core::common::SourceInfo;
use super::*;
use super::super::query;
use super::*;
// xpath //user countEquals 3
pub fn assert_count_user() -> Assert {
@ -138,7 +165,10 @@ pub mod tests {
space0: whitespace.clone(),
predicate_func: PredicateFunc {
source_info: SourceInfo::init(1, 14, 1, 27),
value: PredicateFuncValue::CountEqual { space0: whitespace.clone(), value: 3 },
value: PredicateFuncValue::CountEqual {
space0: whitespace.clone(),
value: 3,
},
},
};
Assert {

View File

@ -23,17 +23,25 @@ use std::path::Path;
use crate::core::common::Value;
use super::core::{Error, RunnerError};
use super::super::core::ast::*;
use super::core::{Error, RunnerError};
impl Body {
pub fn eval(self, variables: &HashMap<String, Value>, context_dir: String) -> Result<Vec<u8>, Error> {
pub fn eval(
self,
variables: &HashMap<String, Value>,
context_dir: String,
) -> Result<Vec<u8>, Error> {
self.value.eval(variables, context_dir)
}
}
impl Bytes {
pub fn eval(self, variables: &HashMap<String, Value>, context_dir: String) -> Result<Vec<u8>, Error> {
pub fn eval(
self,
variables: &HashMap<String, Value>,
context_dir: String,
) -> Result<Vec<u8>, Error> {
match self {
Bytes::RawString { value, .. } => {
let value = value.eval(variables)?;
@ -50,7 +58,11 @@ impl Bytes {
let absolute_filename = if path.is_absolute() {
filename.clone().value
} else {
Path::new(context_dir.as_str()).join(filename.value).to_str().unwrap().to_string()
Path::new(context_dir.as_str())
.join(filename.value)
.to_str()
.unwrap()
.to_string()
};
match File::open(absolute_filename.clone()) {
Ok(f) => {
@ -62,16 +74,17 @@ impl Bytes {
}
Err(_) => Err(Error {
source_info: filename.source_info,
inner: RunnerError::FileReadAccess { value: absolute_filename },
inner: RunnerError::FileReadAccess {
value: absolute_filename,
},
assert: false,
})
}),
}
}
}
}
}
#[cfg(test)]
mod tests {
use crate::core::common::SourceInfo;
@ -88,7 +101,7 @@ mod tests {
Ok(mut file) => match file.write_all(b"Hello World!") {
Err(why) => panic!("couldn't write to {}: {:?}", display, why),
Ok(_) => println!("successfully wrote to {}", display),
}
},
}
}
}
@ -105,12 +118,18 @@ mod tests {
let bytes = Bytes::File {
space0: whitespace.clone(),
filename: Filename { value: String::from("/tmp/data.bin"), source_info: SourceInfo::init(1, 7, 1, 15) },
filename: Filename {
value: String::from("/tmp/data.bin"),
source_info: SourceInfo::init(1, 7, 1, 15),
},
space1: whitespace.clone(),
};
let variables = HashMap::new();
assert_eq!(bytes.eval(&variables, "current_dir".to_string()).unwrap(), b"Hello World!");
assert_eq!(
bytes.eval(&variables, "current_dir".to_string()).unwrap(),
b"Hello World!"
);
}
#[test]
@ -123,14 +142,25 @@ mod tests {
let bytes = Bytes::File {
space0: whitespace.clone(),
filename: Filename { value: String::from("data.bin"), source_info: SourceInfo::init(1, 7, 1, 15) },
filename: Filename {
value: String::from("data.bin"),
source_info: SourceInfo::init(1, 7, 1, 15),
},
space1: whitespace.clone(),
};
let variables = HashMap::new();
let error = bytes.eval(&variables, "current_dir".to_string()).err().unwrap();
assert_eq!(error.inner, RunnerError::FileReadAccess { value: String::from("current_dir/data.bin") });
let error = bytes
.eval(&variables, "current_dir".to_string())
.err()
.unwrap();
assert_eq!(
error.inner,
RunnerError::FileReadAccess {
value: String::from("current_dir/data.bin")
}
);
assert_eq!(error.source_info, SourceInfo::init(1, 7, 1, 15));
}
}

View File

@ -22,78 +22,83 @@ use regex::Regex;
use crate::core::common::Value;
use crate::http;
use super::core::{CaptureResult, Error};
use super::core::RunnerError;
use super::super::core::ast::*;
use super::core::RunnerError;
use super::core::{CaptureResult, Error};
impl Capture {
pub fn eval(self, variables: &HashMap<String, Value>, http_response: http::Response) -> Result<CaptureResult, Error> {
pub fn eval(
self,
variables: &HashMap<String, Value>,
http_response: http::Response,
) -> Result<CaptureResult, Error> {
let name = self.name.value;
let value = self.query.clone().eval(variables, http_response)?;
let value = match value {
None => return Err(Error {
source_info: self.query.source_info,
inner: RunnerError::NoQueryResult {},
assert: false,
}),
None => {
return Err(Error {
source_info: self.query.source_info,
inner: RunnerError::NoQueryResult {},
assert: false,
})
}
Some(value) => match self.subquery {
None => value,
Some(subquery) => {
let value = subquery.clone().eval(variables, value)?;
match value {
None => return Err(Error {
source_info: subquery.source_info,
inner: RunnerError::NoQueryResult {},
assert: false,
}),
Some(value) => value
None => {
return Err(Error {
source_info: subquery.source_info,
inner: RunnerError::NoQueryResult {},
assert: false,
})
}
Some(value) => value,
}
}
}
},
};
Ok(CaptureResult { name, value })
}
}
impl Subquery {
pub fn eval(self, variables: &HashMap<String, Value>, value: Value) -> Result<Option<Value>, Error> {
pub fn eval(
self,
variables: &HashMap<String, Value>,
value: Value,
) -> Result<Option<Value>, Error> {
match self.value {
SubqueryValue::Regex { expr, .. } => {
let source_info = expr.source_info.clone();
let expr = expr.eval(variables)?;
match value {
Value::String(s) => {
match Regex::new(expr.as_str()) {
Ok(re) => {
match re.captures(s.as_str()) {
Some(captures) => match captures.get(1) {
Some(v) => Ok(Some(Value::String(v.as_str().to_string()))),
None => Ok(None),
}
None => Ok(None),
}
}
Err(_) => Err(Error {
source_info,
inner: RunnerError::InvalidRegex(),
assert: false,
})
}
}
Value::String(s) => match Regex::new(expr.as_str()) {
Ok(re) => match re.captures(s.as_str()) {
Some(captures) => match captures.get(1) {
Some(v) => Ok(Some(Value::String(v.as_str().to_string()))),
None => Ok(None),
},
None => Ok(None),
},
Err(_) => Err(Error {
source_info,
inner: RunnerError::InvalidRegex(),
assert: false,
}),
},
_ => Err(Error {
source_info: self.source_info,
inner: RunnerError::SubqueryInvalidInput,
assert: false,
})
}),
}
}
}
}
}
#[cfg(test)]
pub mod tests {
use crate::core::common::{Pos, SourceInfo};
@ -103,9 +108,11 @@ pub mod tests {
use self::super::super::query;
pub fn user_count_capture() -> Capture {
// non scalar value
let whitespace = Whitespace { value: String::from(""), source_info: SourceInfo::init(0, 0, 0, 0) };
let whitespace = Whitespace {
value: String::from(""),
source_info: SourceInfo::init(0, 0, 0, 0),
};
Capture {
line_terminators: vec![],
space0: whitespace.clone(),
@ -131,9 +138,11 @@ pub mod tests {
}
pub fn duration_capture() -> Capture {
// non scalar value
let whitespace = Whitespace { value: String::from(""), source_info: SourceInfo::init(0, 0, 0, 0) };
let whitespace = Whitespace {
value: String::from(""),
source_info: SourceInfo::init(0, 0, 0, 0),
};
Capture {
line_terminators: vec![],
space0: whitespace.clone(),
@ -161,7 +170,10 @@ pub mod tests {
#[test]
fn test_invalid_xpath() {
let variables = HashMap::new();
let whitespace = Whitespace { value: String::from(""), source_info: SourceInfo::init(0, 0, 0, 0) };
let whitespace = Whitespace {
value: String::from(""),
source_info: SourceInfo::init(0, 0, 0, 0),
};
let capture = Capture {
line_terminators: vec![],
space0: whitespace.clone(),
@ -184,16 +196,21 @@ pub mod tests {
},
};
let error = capture.eval(&variables, http::xml_three_users_http_response()).err().unwrap();
let error = capture
.eval(&variables, http::xml_three_users_http_response())
.err()
.unwrap();
assert_eq!(error.source_info.start, Pos { line: 1, column: 7 });
assert_eq!(error.inner, RunnerError::QueryInvalidXpathEval)
}
#[test]
fn test_capture_unsupported() {
// non scalar value
let whitespace = Whitespace { value: String::from(""), source_info: SourceInfo::init(0, 0, 0, 0) };
let whitespace = Whitespace {
value: String::from(""),
source_info: SourceInfo::init(0, 0, 0, 0),
};
let _capture = Capture {
line_terminators: vec![],
space0: whitespace.clone(),
@ -213,12 +230,10 @@ pub mod tests {
space0: whitespace.clone(),
expr: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "//user".to_string(),
encoded: "//user".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "//user".to_string(),
encoded: "//user".to_string(),
}],
source_info: SourceInfo::init(1, 7, 1, 13),
},
},
@ -231,47 +246,61 @@ pub mod tests {
newline: whitespace,
},
};
}
#[test]
fn test_capture() {
let variables = HashMap::new();
assert_eq!(user_count_capture().eval(&variables, http::xml_three_users_http_response()).unwrap(),
CaptureResult {
name: "UserCount".to_string(),
value: Value::from_f64(3.0),
});
assert_eq!(
user_count_capture()
.eval(&variables, http::xml_three_users_http_response())
.unwrap(),
CaptureResult {
name: "UserCount".to_string(),
value: Value::from_f64(3.0),
}
);
assert_eq!(duration_capture().eval(&variables, http::json_http_response()).unwrap(),
CaptureResult {
name: "duration".to_string(),
value: Value::from_f64(1.5),
});
assert_eq!(
duration_capture()
.eval(&variables, http::json_http_response())
.unwrap(),
CaptureResult {
name: "duration".to_string(),
value: Value::from_f64(1.5),
}
);
}
#[test]
fn test_subquery_value_regex() {
// regex "Hello (.*)!"
let variables = HashMap::new();
let whitespace = Whitespace { value: String::from(""), source_info: SourceInfo::init(0, 0, 0, 0) };
let whitespace = Whitespace {
value: String::from(""),
source_info: SourceInfo::init(0, 0, 0, 0),
};
let subquery = Subquery {
source_info: SourceInfo::init(1, 1, 1, 20),
value: SubqueryValue::Regex {
space0: whitespace,
expr: Template {
quotes: false,
elements: vec![
TemplateElement::String { value: "Hello (.*)!".to_string(), encoded: "Hello (.*)!".to_string() }
],
elements: vec![TemplateElement::String {
value: "Hello (.*)!".to_string(),
encoded: "Hello (.*)!".to_string(),
}],
source_info: SourceInfo::init(1, 7, 1, 20),
},
},
};
assert_eq!(subquery.clone().eval(&variables, Value::String("Hello Bob!".to_string())).unwrap().unwrap(),
Value::String("Bob".to_string())
assert_eq!(
subquery
.clone()
.eval(&variables, Value::String("Hello Bob!".to_string()))
.unwrap()
.unwrap(),
Value::String("Bob".to_string())
);
let error = subquery.eval(&variables, Value::Bool(true)).err().unwrap();
assert_eq!(error.source_info, SourceInfo::init(1, 1, 1, 20));
@ -281,21 +310,28 @@ pub mod tests {
#[test]
fn test_subquery_value_error() {
let variables = HashMap::new();
let whitespace = Whitespace { value: String::from(""), source_info: SourceInfo::init(0, 0, 0, 0) };
let whitespace = Whitespace {
value: String::from(""),
source_info: SourceInfo::init(0, 0, 0, 0),
};
let subquery = Subquery {
source_info: SourceInfo::init(1, 1, 1, 20),
value: SubqueryValue::Regex {
space0: whitespace,
expr: Template {
quotes: false,
elements: vec![
TemplateElement::String { value: "???".to_string(), encoded: "???".to_string() }
],
elements: vec![TemplateElement::String {
value: "???".to_string(),
encoded: "???".to_string(),
}],
source_info: SourceInfo::init(1, 7, 1, 20),
},
},
};
let error = subquery.eval(&variables, Value::String("Hello Bob!".to_string())).err().unwrap();
let error = subquery
.eval(&variables, Value::String("Hello Bob!".to_string()))
.err()
.unwrap();
assert_eq!(error.source_info, SourceInfo::init(1, 7, 1, 20));
assert_eq!(error.inner, RunnerError::InvalidRegex {});
}

View File

@ -16,7 +16,6 @@
*
*/
///
/// This module defines a HTTP ResponseCookie,
/// namely the cookie returned from the response Set-Cookie header
@ -25,7 +24,6 @@
/// and not by the http client.
///
///
/// Cookie return from HTTP Response
/// It contains arbitrary attributes.
@ -45,7 +43,6 @@ pub struct CookieAttribute {
#[allow(dead_code)]
impl ResponseCookie {
///
/// parse value from Set-Cookie Header into a Cookie
///
@ -158,10 +155,8 @@ impl ResponseCookie {
}
None
}
}
impl CookieAttribute {
fn parse(s: String) -> Option<CookieAttribute> {
if s.is_empty() {
@ -176,7 +171,6 @@ impl CookieAttribute {
}
}
#[cfg(test)]
pub mod tests {
use super::*;
@ -185,24 +179,26 @@ pub mod tests {
fn test_parse_cookie_attribute() {
assert_eq!(
CookieAttribute::parse("Expires=Wed, 21 Oct 2015 07:28:00 GMT".to_string()).unwrap(),
CookieAttribute { name: "Expires".to_string(), value: Some("Wed, 21 Oct 2015 07:28:00 GMT".to_string()) }
CookieAttribute {
name: "Expires".to_string(),
value: Some("Wed, 21 Oct 2015 07:28:00 GMT".to_string())
}
);
assert_eq!(
CookieAttribute::parse("HttpOnly".to_string()).unwrap(),
CookieAttribute { name: "HttpOnly".to_string(), value:None }
CookieAttribute {
name: "HttpOnly".to_string(),
value: None
}
);
assert_eq!(
CookieAttribute::parse("".to_string()),
None
);
assert_eq!(CookieAttribute::parse("".to_string()), None);
}
#[test]
fn test_session_cookie() {
let cookie = ResponseCookie {
let cookie = ResponseCookie {
name: "sessionId".to_string(),
value: "38afes7a8".to_string(),
attributes: vec![],
@ -222,21 +218,23 @@ pub mod tests {
#[test]
fn test_permanent_cookie() {
let cookie = ResponseCookie {
let cookie = ResponseCookie {
name: "id".to_string(),
value: "a3fWa".to_string(),
attributes: vec![
CookieAttribute {
name: "Expires".to_string(),
value: Some("Wed, 21 Oct 2015 07:28:00 GMT".to_string()),
}
],
attributes: vec![CookieAttribute {
name: "Expires".to_string(),
value: Some("Wed, 21 Oct 2015 07:28:00 GMT".to_string()),
}],
};
assert_eq!(
ResponseCookie::parse("id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT".to_string()).unwrap(),
ResponseCookie::parse("id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT".to_string())
.unwrap(),
cookie
);
assert_eq!(cookie.expires(), Some("Wed, 21 Oct 2015 07:28:00 GMT".to_string()));
assert_eq!(
cookie.expires(),
Some("Wed, 21 Oct 2015 07:28:00 GMT".to_string())
);
assert_eq!(cookie.max_age(), None);
assert_eq!(cookie.domain(), None);
assert_eq!(cookie.path(), None);
@ -247,15 +245,13 @@ pub mod tests {
#[test]
fn test_permanent2_cookie() {
let cookie = ResponseCookie {
let cookie = ResponseCookie {
name: "id".to_string(),
value: "a3fWa".to_string(),
attributes: vec![
CookieAttribute {
name: "Max-Age".to_string(),
value: Some("2592000".to_string()),
}
],
attributes: vec![CookieAttribute {
name: "Max-Age".to_string(),
value: Some("2592000".to_string()),
}],
};
assert_eq!(
ResponseCookie::parse("id=a3fWa; Max-Age=2592000".to_string()).unwrap(),
@ -272,24 +268,38 @@ pub mod tests {
#[test]
fn test_lsid_cookie() {
let cookie = ResponseCookie {
let cookie = ResponseCookie {
name: "LSID".to_string(),
value: "DQAAAK…Eaem_vYg".to_string(),
attributes: vec![
CookieAttribute { name: "Path".to_string(), value: Some("/accounts".to_string()) },
CookieAttribute { name: "Expires".to_string(), value: Some("Wed, 13 Jan 2021 22:23:01 GMT".to_string()) },
CookieAttribute { name: "Secure".to_string(), value: None },
CookieAttribute { name: "HttpOnly".to_string(), value: None },
CookieAttribute {
name: "Path".to_string(),
value: Some("/accounts".to_string()),
},
CookieAttribute {
name: "Expires".to_string(),
value: Some("Wed, 13 Jan 2021 22:23:01 GMT".to_string()),
},
CookieAttribute {
name: "Secure".to_string(),
value: None,
},
CookieAttribute {
name: "HttpOnly".to_string(),
value: None,
},
],
};
assert_eq!(
ResponseCookie::parse("LSID=DQAAAK…Eaem_vYg; Path=/accounts; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly".to_string()).unwrap(),
cookie
);
assert_eq!(cookie.expires(), Some("Wed, 13 Jan 2021 22:23:01 GMT".to_string()));
assert_eq!(
cookie.expires(),
Some("Wed, 13 Jan 2021 22:23:01 GMT".to_string())
);
assert_eq!(cookie.max_age(), None);
assert_eq!(cookie.domain(), None)
;
assert_eq!(cookie.domain(), None);
assert_eq!(cookie.path(), Some("/accounts".to_string()));
assert_eq!(cookie.has_secure(), true);
assert_eq!(cookie.has_httponly(), true);
@ -298,21 +308,36 @@ pub mod tests {
#[test]
fn test_hsid_cookie() {
let cookie = ResponseCookie {
let cookie = ResponseCookie {
name: "HSID".to_string(),
value: "AYQEVn…DKrdst".to_string(),
attributes: vec![
CookieAttribute { name: "Domain".to_string(), value: Some(".foo.com".to_string()) },
CookieAttribute { name: "Path".to_string(), value: Some("/".to_string()) },
CookieAttribute { name: "Expires".to_string(), value: Some("Wed, 13 Jan 2021 22:23:01 GMT".to_string()) },
CookieAttribute { name: "HttpOnly".to_string(), value: None },
CookieAttribute {
name: "Domain".to_string(),
value: Some(".foo.com".to_string()),
},
CookieAttribute {
name: "Path".to_string(),
value: Some("/".to_string()),
},
CookieAttribute {
name: "Expires".to_string(),
value: Some("Wed, 13 Jan 2021 22:23:01 GMT".to_string()),
},
CookieAttribute {
name: "HttpOnly".to_string(),
value: None,
},
],
};
assert_eq!(
ResponseCookie::parse("HSID=AYQEVn…DKrdst; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; HttpOnly".to_string()).unwrap(),
cookie
);
assert_eq!(cookie.expires(), Some("Wed, 13 Jan 2021 22:23:01 GMT".to_string()));
assert_eq!(
cookie.expires(),
Some("Wed, 13 Jan 2021 22:23:01 GMT".to_string())
);
assert_eq!(cookie.max_age(), None);
assert_eq!(cookie.domain(), Some(".foo.com".to_string()));
assert_eq!(cookie.path(), Some("/".to_string()));
@ -321,7 +346,6 @@ pub mod tests {
assert_eq!(cookie.samesite(), None);
}
#[test]
fn test_trailing_semicolon() {
assert_eq!(
@ -334,18 +358,14 @@ pub mod tests {
);
}
#[test]
fn test_invalid_cookie() {
assert_eq!(
ResponseCookie::parse("xx".to_string()),
None
);
assert_eq!(ResponseCookie::parse("xx".to_string()), None);
}
#[test]
fn test_cookie_with_invalid_attributes() {
let cookie = ResponseCookie {
let cookie = ResponseCookie {
name: "id".to_string(),
value: "a3fWa".to_string(),
attributes: vec![
@ -356,7 +376,7 @@ pub mod tests {
CookieAttribute {
name: "Max-Age".to_string(),
value: Some("".to_string()),
}
},
],
};
assert_eq!(
@ -371,7 +391,4 @@ pub mod tests {
assert_eq!(cookie.has_httponly(), false);
assert_eq!(cookie.samesite(), None);
}
}

View File

@ -17,7 +17,6 @@
*/
use std::collections::HashMap;
use crate::core::common::{FormatError, SourceInfo, Value};
use crate::http;
@ -28,7 +27,6 @@ pub struct RunnerOptions {
pub to_entry: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HurlResult {
pub filename: String,
@ -61,11 +59,31 @@ pub struct EntryResult {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AssertResult {
Version { actual: String, expected: String, source_info: SourceInfo },
Status { actual: u64, expected: u64, source_info: SourceInfo },
Header { actual: Result<String, Error>, expected: String, source_info: SourceInfo },
Body { actual: Result<Value, Error>, expected: Result<Value, Error>, source_info: SourceInfo },
Explicit { actual: Result<Option<Value>, Error>, source_info: SourceInfo, predicate_result: Option<PredicateResult> },
Version {
actual: String,
expected: String,
source_info: SourceInfo,
},
Status {
actual: u64,
expected: u64,
source_info: SourceInfo,
},
Header {
actual: Result<String, Error>,
expected: String,
source_info: SourceInfo,
},
Body {
actual: Result<Value, Error>,
expected: Result<Value, Error>,
source_info: SourceInfo,
},
Explicit {
actual: Result<Option<Value>, Error>,
source_info: SourceInfo,
predicate_result: Option<PredicateResult>,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -78,7 +96,6 @@ pub type PredicateResult = Result<(), Error>;
// endregion
// region error
#[derive(Clone, Debug, PartialEq, Eq)]
@ -90,18 +107,33 @@ pub struct Error {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RunnerError {
TemplateVariableNotDefined { name: String },
VariableNotDefined { name: String },
TemplateVariableNotDefined {
name: String,
},
VariableNotDefined {
name: String,
},
InvalidURL(String),
HttpConnection { url: String, message: String },
FileReadAccess { value: String },
InvalidDecoding { charset: String },
InvalidCharset { charset: String },
HttpConnection {
url: String,
message: String,
},
FileReadAccess {
value: String,
},
InvalidDecoding {
charset: String,
},
InvalidCharset {
charset: String,
},
// Query
QueryHeaderNotFound,
QueryCookieNotFound,
QueryInvalidJsonpathExpression { value: String },
QueryInvalidJsonpathExpression {
value: String,
},
QueryInvalidXpathEval,
QueryInvalidXml,
QueryInvalidJson,
@ -112,19 +144,32 @@ pub enum RunnerError {
// Predicate
PredicateType,
PredicateValue(Value),
AssertFailure { actual: String, expected: String, type_mismatch: bool },
AssertFailure {
actual: String,
expected: String,
type_mismatch: bool,
},
InvalidRegex(),
AssertHeaderValueError { actual: String },
AssertBodyValueError { actual: String, expected: String },
AssertVersion { actual: String },
AssertStatus { actual: String },
UnrenderableVariable { value: String },
AssertHeaderValueError {
actual: String,
},
AssertBodyValueError {
actual: String,
expected: String,
},
AssertVersion {
actual: String,
},
AssertStatus {
actual: String,
},
UnrenderableVariable {
value: String,
},
}
impl FormatError for Error {
fn source_info(&self) -> SourceInfo {
self.clone().source_info
@ -151,44 +196,76 @@ impl FormatError for Error {
RunnerError::QueryInvalidJsonpathExpression { .. } => "Invalid jsonpath".to_string(),
RunnerError::PredicateType { .. } => "Assert - Inconsistent predicate type".to_string(),
RunnerError::SubqueryInvalidInput { .. } => "Subquery error".to_string(),
RunnerError::InvalidDecoding { ..} => "Invalid Decoding".to_string(),
RunnerError::InvalidCharset { ..} => "Invalid Charset".to_string(),
RunnerError::InvalidDecoding { .. } => "Invalid Decoding".to_string(),
RunnerError::InvalidCharset { .. } => "Invalid Charset".to_string(),
RunnerError::AssertFailure { .. } => "Assert Failure".to_string(),
RunnerError::UnrenderableVariable { .. } => "Unrenderable Variable".to_string(),
RunnerError::NoQueryResult { .. } => "No query result".to_string(),
}
}
fn fixme(&self) -> String {
match &self.inner {
RunnerError::InvalidURL(url) => format!("Invalid url <{}>", url),
RunnerError::TemplateVariableNotDefined { name } => format!("You must set the variable {}", name),
RunnerError::TemplateVariableNotDefined { name } => {
format!("You must set the variable {}", name)
}
RunnerError::HttpConnection { url, .. } => format!("can not connect to {}", url),
RunnerError::AssertVersion { actual, .. } => format!("actual value is <{}>", actual),
RunnerError::AssertStatus { actual, .. } => format!("actual value is <{}>", actual),
RunnerError::PredicateValue(value) => format!("actual value is <{}>", value.to_string()),
RunnerError::PredicateValue(value) => {
format!("actual value is <{}>", value.to_string())
}
RunnerError::InvalidRegex {} => "Regex expression is not valid".to_string(),
RunnerError::FileReadAccess { value } => format!("File {} can not be read", value),
RunnerError::QueryInvalidXml { .. } => "The Http response is not a valid XML".to_string(),
RunnerError::QueryHeaderNotFound {} => "This header has not been found in the response".to_string(),
RunnerError::QueryCookieNotFound {} => "This cookie has not been found in the response".to_string(),
RunnerError::QueryInvalidXpathEval {} => "The xpath expression is not valid".to_string(),
RunnerError::AssertHeaderValueError { actual } => format!("actual value is <{}>", actual),
RunnerError::AssertBodyValueError { actual, .. } => format!("actual value is <{}>", actual),
RunnerError::QueryInvalidJson { .. } => "The http response is not a valid json".to_string(),
RunnerError::QueryInvalidJsonpathExpression { value } => format!("the jsonpath expression '{}' is not valid", value),
RunnerError::PredicateType { .. } => "predicate type inconsistent with value return by query".to_string(),
RunnerError::SubqueryInvalidInput => "Type from query result and subquery do not match".to_string(),
RunnerError::InvalidDecoding { charset } => format!("The body can not be decoded with charset '{}'", charset),
RunnerError::InvalidCharset { charset } => format!("The charset '{}' is not valid", charset),
RunnerError::AssertFailure { actual, expected, .. } => format!("actual: {}\nexpected: {}", actual, expected),
RunnerError::VariableNotDefined { name } => format!("You must set the variable {}", name),
RunnerError::UnrenderableVariable { value } => format!("value {} can not be rendered", value),
RunnerError::QueryInvalidXml { .. } => {
"The Http response is not a valid XML".to_string()
}
RunnerError::QueryHeaderNotFound {} => {
"This header has not been found in the response".to_string()
}
RunnerError::QueryCookieNotFound {} => {
"This cookie has not been found in the response".to_string()
}
RunnerError::QueryInvalidXpathEval {} => {
"The xpath expression is not valid".to_string()
}
RunnerError::AssertHeaderValueError { actual } => {
format!("actual value is <{}>", actual)
}
RunnerError::AssertBodyValueError { actual, .. } => {
format!("actual value is <{}>", actual)
}
RunnerError::QueryInvalidJson { .. } => {
"The http response is not a valid json".to_string()
}
RunnerError::QueryInvalidJsonpathExpression { value } => {
format!("the jsonpath expression '{}' is not valid", value)
}
RunnerError::PredicateType { .. } => {
"predicate type inconsistent with value return by query".to_string()
}
RunnerError::SubqueryInvalidInput => {
"Type from query result and subquery do not match".to_string()
}
RunnerError::InvalidDecoding { charset } => {
format!("The body can not be decoded with charset '{}'", charset)
}
RunnerError::InvalidCharset { charset } => {
format!("The charset '{}' is not valid", charset)
}
RunnerError::AssertFailure {
actual, expected, ..
} => format!("actual: {}\nexpected: {}", actual, expected),
RunnerError::VariableNotDefined { name } => {
format!("You must set the variable {}", name)
}
RunnerError::UnrenderableVariable { value } => {
format!("value {} can not be rendered", value)
}
RunnerError::NoQueryResult { .. } => "The query didn't return any result".to_string(),
}
}
}
// endregion

View File

@ -18,18 +18,15 @@
use std::collections::HashMap;
use std::time::Instant;
use crate::core::ast::*;
use crate::core::common::SourceInfo;
use crate::core::common::Value;
use crate::http;
use super::core::*;
use super::core::{Error, RunnerError};
use crate::format::logger::Logger;
/// Run an entry with the hurl http client
///
/// # Examples
@ -48,12 +45,13 @@ use crate::format::logger::Logger;
//// all_proxy: None
//// });
/// ```
pub fn run(entry: Entry,
http_client: &mut http::Client,
entry_index: usize,
variables: &mut HashMap<String, Value>,
context_dir: String,
logger: &Logger,
pub fn run(
entry: Entry,
http_client: &mut http::Client,
entry_index: usize,
variables: &mut HashMap<String, Value>,
context_dir: String,
logger: &Logger,
) -> EntryResult {
let http_request = match entry.clone().request.eval(variables, context_dir.clone()) {
Ok(r) => r,
@ -70,7 +68,8 @@ pub fn run(entry: Entry,
}
};
logger.verbose("------------------------------------------------------------------------------");
logger
.verbose("------------------------------------------------------------------------------");
logger.verbose(format!("executing entry {}", entry_index + 1).as_str());
// Temporary - add cookie from request to the cookie store
@ -110,18 +109,17 @@ pub fn run(entry: Entry,
response: None,
captures: vec![],
asserts: vec![],
errors: vec![
Error {
source_info: SourceInfo {
start: entry.clone().request.url.source_info.start,
end: entry.clone().request.url.source_info.end,
},
inner: RunnerError::HttpConnection {
message: "".to_string(),
url: http_request.url,
},
assert: false,
}],
errors: vec![Error {
source_info: SourceInfo {
start: entry.clone().request.url.source_info.start,
end: entry.clone().request.url.source_info.end,
},
inner: RunnerError::HttpConnection {
message: "".to_string(),
url: http_request.url,
},
assert: false,
}],
time_in_ms: 0,
};
}
@ -144,7 +142,7 @@ pub fn run(entry: Entry,
time_in_ms,
};
}
}
},
};
// update variables now!
@ -154,12 +152,20 @@ pub fn run(entry: Entry,
let asserts = match entry.response {
None => vec![],
Some(response) => response.eval_asserts(variables, http_response.clone(), context_dir)
Some(response) => response.eval_asserts(variables, http_response.clone(), context_dir),
};
let errors = asserts
.iter()
.filter_map(|assert| assert.clone().error())
.map(|Error { source_info, inner, .. }| Error { source_info, inner, assert: true })
.map(
|Error {
source_info, inner, ..
}| Error {
source_info,
inner,
assert: true,
},
)
.collect();
if !captures.is_empty() {
@ -181,7 +187,6 @@ pub fn run(entry: Entry,
}
}
pub fn log_request(logger: &Logger, request: &http::Request) {
logger.verbose("Request");
logger.verbose(format!("{} {}", request.method, request.url).as_str());
@ -217,4 +222,4 @@ pub fn log_request(logger: &Logger, request: &http::Request) {
logger.verbose(format!("implicit content-type={}", s).as_str());
}
logger.verbose("");
}
}

View File

@ -20,7 +20,7 @@ use std::collections::HashMap;
use crate::core::ast::Expr;
use crate::core::common::Value;
use super::core::{RunnerError, Error};
use super::core::{Error, RunnerError};
impl Expr {
pub fn eval(self, variables: &HashMap<String, Value>) -> Result<Value, Error> {
@ -29,8 +29,10 @@ impl Expr {
} else {
Err(Error {
source_info: self.variable.source_info,
inner: RunnerError::TemplateVariableNotDefined { name: self.variable.name },
assert: false
inner: RunnerError::TemplateVariableNotDefined {
name: self.variable.name,
},
assert: false,
})
}
}

View File

@ -22,14 +22,11 @@ use crate::core::ast::*;
use crate::core::common::Value;
use crate::http;
use super::core::*;
use super::super::format;
use super::core::*;
use super::entry;
use crate::core::common::FormatError;
/// Run a Hurl file with the hurl http client
///
/// # Example
@ -112,8 +109,22 @@ pub fn run(
};
let start = Instant::now();
for (entry_index, entry) in hurl_file.entries.iter().take(n).cloned().enumerate().collect::<Vec<(usize, Entry)>>() {
let entry_result = entry::run(entry, http_client, entry_index, &mut variables, context_dir.clone(), &logger);
for (entry_index, entry) in hurl_file
.entries
.iter()
.take(n)
.cloned()
.enumerate()
.collect::<Vec<(usize, Entry)>>()
{
let entry_result = entry::run(
entry,
http_client,
entry_index,
&mut variables,
context_dir.clone(),
&logger,
);
entries.push(entry_result.clone());
for e in entry_result.errors.clone() {
let error = format::error::Error {
@ -133,7 +144,11 @@ pub fn run(
}
}
let time_in_ms = start.elapsed().as_millis();
let success = entries.iter().flat_map(|e| e.errors.clone()).next().is_none();
let success = entries
.iter()
.flat_map(|e| e.errors.clone())
.next()
.is_none();
let cookies = http_client.get_cookie_storage();
HurlResult {
@ -144,4 +159,3 @@ pub fn run(
cookies,
}
}

View File

@ -16,17 +16,12 @@
*
*/
use crate::http::Response;
use super::core::RunnerError;
use super::cookie::ResponseCookie;
use encoding::{EncodingRef, DecoderTrap};
use super::core::RunnerError;
use crate::http::Response;
use encoding::{DecoderTrap, EncodingRef};
impl Response {
pub fn cookies(&self) -> Vec<ResponseCookie> {
self.headers
.iter()
@ -40,16 +35,16 @@ impl Response {
///
fn encoding(&self) -> Result<EncodingRef, RunnerError> {
match self.content_type() {
Some(content_type) => {
match mime_charset(content_type) {
Some(charset) => match encoding::label::encoding_from_whatwg_label(charset.as_str()) {
Some(content_type) => match mime_charset(content_type) {
Some(charset) => {
match encoding::label::encoding_from_whatwg_label(charset.as_str()) {
None => Err(RunnerError::InvalidCharset { charset }),
Some(enc) => Ok(enc)
},
None => Ok(encoding::all::UTF_8),
Some(enc) => Ok(enc),
}
}
}
None => Ok(encoding::all::UTF_8)
None => Ok(encoding::all::UTF_8),
},
None => Ok(encoding::all::UTF_8),
}
}
@ -60,7 +55,9 @@ impl Response {
let encoding = self.encoding()?;
match encoding.decode(&self.body, DecoderTrap::Strict) {
Ok(s) => Ok(s),
Err(_) => Err(RunnerError::InvalidDecoding { charset: encoding.name().to_string() })
Err(_) => Err(RunnerError::InvalidDecoding {
charset: encoding.name().to_string(),
}),
}
}
@ -70,11 +67,10 @@ impl Response {
pub fn is_html(&self) -> bool {
match self.content_type() {
None => false,
Some(s) => s.starts_with("text/html")
Some(s) => s.starts_with("text/html"),
}
}
///
/// Return option cookie from response
///
@ -86,7 +82,6 @@ impl Response {
}
None
}
}
///
@ -95,13 +90,10 @@ impl Response {
fn mime_charset(mime_type: String) -> Option<String> {
match mime_type.find("charset=") {
None => None,
Some(index) => {
Some(mime_type[(index + 8)..].to_string())
}
Some(index) => Some(mime_type[(index + 8)..].to_string()),
}
}
#[cfg(test)]
pub mod tests {
use super::*;
@ -109,12 +101,17 @@ pub mod tests {
#[test]
pub fn test_charset() {
assert_eq!(mime_charset("text/plain; charset=utf-8".to_string()), Some("utf-8".to_string()));
assert_eq!(mime_charset("text/plain; charset=ISO-8859-1".to_string()), Some("ISO-8859-1".to_string()));
assert_eq!(
mime_charset("text/plain; charset=utf-8".to_string()),
Some("utf-8".to_string())
);
assert_eq!(
mime_charset("text/plain; charset=ISO-8859-1".to_string()),
Some("ISO-8859-1".to_string())
);
assert_eq!(mime_charset("text/plain;".to_string()), None);
}
fn hello_response() -> Response {
Response {
version: Version::Http10,
@ -128,9 +125,10 @@ pub mod tests {
Response {
version: Version::Http10,
status: 200,
headers: vec![
Header { name: "Content-Type".to_string(), value: "text/plain; charset=utf-8".to_string() }
],
headers: vec![Header {
name: "Content-Type".to_string(),
value: "text/plain; charset=utf-8".to_string(),
}],
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
}
}
@ -139,9 +137,10 @@ pub mod tests {
Response {
version: Version::Http10,
status: 200,
headers: vec![
Header { name: "Content-Type".to_string(), value: "text/plain; charset=ISO-8859-1".to_string() }
],
headers: vec![Header {
name: "Content-Type".to_string(),
value: "text/plain; charset=ISO-8859-1".to_string(),
}],
body: vec![0x63, 0x61, 0x66, 0xe9],
}
}
@ -149,53 +148,87 @@ pub mod tests {
#[test]
pub fn test_content_type() {
assert_eq!(hello_response().content_type(), None);
assert_eq!(utf8_encoding_response().content_type(), Some("text/plain; charset=utf-8".to_string()));
assert_eq!(latin1_encoding_response().content_type(), Some("text/plain; charset=ISO-8859-1".to_string()));
assert_eq!(
utf8_encoding_response().content_type(),
Some("text/plain; charset=utf-8".to_string())
);
assert_eq!(
latin1_encoding_response().content_type(),
Some("text/plain; charset=ISO-8859-1".to_string())
);
}
#[test]
pub fn test_encoding() {
assert_eq!(hello_response().encoding().unwrap().name(), "utf-8");
assert_eq!(utf8_encoding_response().encoding().unwrap().name(), "utf-8");
assert_eq!(latin1_encoding_response().encoding().unwrap().name(), "windows-1252");
assert_eq!(
latin1_encoding_response().encoding().unwrap().name(),
"windows-1252"
);
}
#[test]
pub fn test_text() {
assert_eq!(hello_response().text().unwrap(), "Hello World!".to_string());
assert_eq!(utf8_encoding_response().text().unwrap(), "café".to_string());
assert_eq!(latin1_encoding_response().text().unwrap(), "café".to_string());
assert_eq!(
latin1_encoding_response().text().unwrap(),
"café".to_string()
);
}
#[test]
pub fn test_invalid_charset() {
assert_eq!(Response {
version: Version::Http10,
status: 200,
headers: vec![
Header { name: "Content-Type".to_string(), value:"test/plain; charset=xxx".to_string()}
],
body: b"Hello World!".to_vec(),
}.encoding().err().unwrap(), RunnerError::InvalidCharset { charset: "xxx".to_string()});
assert_eq!(
Response {
version: Version::Http10,
status: 200,
headers: vec![Header {
name: "Content-Type".to_string(),
value: "test/plain; charset=xxx".to_string()
}],
body: b"Hello World!".to_vec(),
}
.encoding()
.err()
.unwrap(),
RunnerError::InvalidCharset {
charset: "xxx".to_string()
}
);
}
#[test]
pub fn test_invalid_decoding() {
assert_eq!(Response {
version: Version::Http10,
status: 200,
headers: vec![],
body: vec![0x63, 0x61, 0x66, 0xe9],
}.text().err().unwrap(), RunnerError::InvalidDecoding { charset: "utf-8".to_string()});
assert_eq!(
Response {
version: Version::Http10,
status: 200,
headers: vec![],
body: vec![0x63, 0x61, 0x66, 0xe9],
}
.text()
.err()
.unwrap(),
RunnerError::InvalidDecoding {
charset: "utf-8".to_string()
}
);
assert_eq!(Response {
version: Version::Http10,
status: 200,
headers: vec![
Header { name: "Content-Type".to_string(), value: "text/plain; charset=ISO-8859-1".to_string() }
],
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
}.text().unwrap(), "café".to_string());
assert_eq!(
Response {
version: Version::Http10,
status: 200,
headers: vec![Header {
name: "Content-Type".to_string(),
value: "text/plain; charset=ISO-8859-1".to_string()
}],
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
}
.text()
.unwrap(),
"café".to_string()
);
}
}
}

View File

@ -52,7 +52,6 @@ impl json::Value {
}
}
impl json::ListElement {
pub fn eval(self, variables: &HashMap<String, Value>) -> Result<String, Error> {
let s = self.value.eval(variables)?;
@ -63,81 +62,149 @@ impl json::ListElement {
impl json::ObjectElement {
pub fn eval(self, variables: &HashMap<String, Value>) -> Result<String, Error> {
let value = self.value.eval(variables)?;
Ok(format!("{}\"{}\"{}:{}{}{}", self.space0, self.name, self.space1, self.space2, value, self.space3))
Ok(format!(
"{}\"{}\"{}:{}{}{}",
self.space0, self.name, self.space1, self.space2, value, self.space3
))
}
}
#[cfg(test)]
mod tests {
use crate::core::ast::{Template, TemplateElement};
use crate::core::common::SourceInfo;
use super::*;
use super::super::core::RunnerError;
use super::*;
#[test]
fn test_scalar_value() {
let mut variables = HashMap::new();
variables.insert("name".to_string(), Value::String("Bob".to_string()));
assert_eq!(json::Value::Null {}.eval(&variables).unwrap(), "null".to_string());
assert_eq!(json::Value::Number("3.14".to_string()).eval(&variables).unwrap(), "3.14".to_string());
assert_eq!(json::Value::Boolean(false).eval(&variables).unwrap(), "false".to_string());
assert_eq!(json::tests::hello_world_value().eval(&variables).unwrap(), "\"Hello Bob!\"".to_string());
assert_eq!(
json::Value::Null {}.eval(&variables).unwrap(),
"null".to_string()
);
assert_eq!(
json::Value::Number("3.14".to_string())
.eval(&variables)
.unwrap(),
"3.14".to_string()
);
assert_eq!(
json::Value::Boolean(false).eval(&variables).unwrap(),
"false".to_string()
);
assert_eq!(
json::tests::hello_world_value().eval(&variables).unwrap(),
"\"Hello Bob!\"".to_string()
);
}
#[test]
fn test_error() {
let variables = HashMap::new();
let error = json::tests::hello_world_value().eval(&variables).err().unwrap();
let error = json::tests::hello_world_value()
.eval(&variables)
.err()
.unwrap();
assert_eq!(error.source_info, SourceInfo::init(1, 15, 1, 19));
assert_eq!(error.inner, RunnerError::TemplateVariableNotDefined { name: "name".to_string() });
assert_eq!(
error.inner,
RunnerError::TemplateVariableNotDefined {
name: "name".to_string()
}
);
}
#[test]
fn test_list_value() {
let mut variables = HashMap::new();
variables.insert("name".to_string(), Value::String("Bob".to_string()));
assert_eq!(json::Value::List {
space0: "".to_string(),
elements: vec![],
}.eval(&variables).unwrap(), "[]".to_string());
assert_eq!(
json::Value::List {
space0: "".to_string(),
elements: vec![],
}
.eval(&variables)
.unwrap(),
"[]".to_string()
);
assert_eq!(json::Value::List {
space0: "".to_string(),
elements: vec![
json::ListElement { space0: "".to_string(), value: json::Value::Number("1".to_string()), space1: "".to_string() },
json::ListElement { space0: " ".to_string(), value: json::Value::Number("-2".to_string()), space1: "".to_string() },
json::ListElement { space0: " ".to_string(), value: json::Value::Number("3.0".to_string()), space1: "".to_string() },
],
}.eval(&variables).unwrap(), "[1, -2, 3.0]".to_string());
assert_eq!(
json::Value::List {
space0: "".to_string(),
elements: vec![
json::ListElement {
space0: "".to_string(),
value: json::Value::Number("1".to_string()),
space1: "".to_string()
},
json::ListElement {
space0: " ".to_string(),
value: json::Value::Number("-2".to_string()),
space1: "".to_string()
},
json::ListElement {
space0: " ".to_string(),
value: json::Value::Number("3.0".to_string()),
space1: "".to_string()
},
],
}
.eval(&variables)
.unwrap(),
"[1, -2, 3.0]".to_string()
);
let template = Template {
quotes: true,
elements: vec![
TemplateElement::String {
encoded: "Hi".to_string(),
value: "Hi".to_string(),
}
],
elements: vec![TemplateElement::String {
encoded: "Hi".to_string(),
value: "Hi".to_string(),
}],
source_info: SourceInfo::init(0, 0, 0, 0),
};
assert_eq!(json::Value::List {
space0: "".to_string(),
elements: vec![
json::ListElement { space0: "".to_string(), value: json::Value::String(template), space1: "".to_string() },
json::ListElement { space0: " ".to_string(), value: json::tests::hello_world_value(), space1: "".to_string() },
],
}.eval(&variables).unwrap(), "[\"Hi\", \"Hello Bob!\"]".to_string());
assert_eq!(
json::Value::List {
space0: "".to_string(),
elements: vec![
json::ListElement {
space0: "".to_string(),
value: json::Value::String(template),
space1: "".to_string()
},
json::ListElement {
space0: " ".to_string(),
value: json::tests::hello_world_value(),
space1: "".to_string()
},
],
}
.eval(&variables)
.unwrap(),
"[\"Hi\", \"Hello Bob!\"]".to_string()
);
}
#[test]
fn test_object_value() {
let variables = HashMap::new();
assert_eq!(json::Value::Object { space0: "".to_string(), elements: vec![] }.eval(&variables).unwrap(), "{}".to_string());
assert_eq!(json::tests::person_value().eval(&variables).unwrap(), r#"{
assert_eq!(
json::Value::Object {
space0: "".to_string(),
elements: vec![]
}
.eval(&variables)
.unwrap(),
"{}".to_string()
);
assert_eq!(
json::tests::person_value().eval(&variables).unwrap(),
r#"{
"firstName": "John"
}"#.to_string());
}"#
.to_string()
);
}
}

View File

@ -16,14 +16,12 @@
*
*/
use crate::http::*;
use super::cookie::*;
use super::core::*;
use crate::http::*;
type ParseError = String;
pub fn parse_results(value: serde_json::Value) -> Result<Vec<HurlResult>, ParseError> {
if let serde_json::Value::Array(values) = value {
let mut results = vec![];
@ -37,7 +35,6 @@ pub fn parse_results(value: serde_json::Value) -> Result<Vec<HurlResult>, ParseE
}
}
fn parse_result(value: serde_json::Value) -> Result<HurlResult, ParseError> {
if let serde_json::Value::Object(map) = value.clone() {
let filename = map.get("filename").unwrap().as_str().unwrap().to_string();
@ -52,12 +49,10 @@ fn parse_result(value: serde_json::Value) -> Result<HurlResult, ParseError> {
return Err("expecting an array of entries".to_string());
};
let time_in_ms = match value.get("time") {
Some(serde_json::Value::Number(n)) => {
match n.as_u64() {
Some(x) => x as u128,
None => return Err("expecting an integer for the time".to_string()),
}
}
Some(serde_json::Value::Number(n)) => match n.as_u64() {
Some(x) => x as u128,
None => return Err("expecting an integer for the time".to_string()),
},
_ => return Err("expecting an integer for the time".to_string()),
};
let success = match value.get("success") {
@ -65,7 +60,13 @@ fn parse_result(value: serde_json::Value) -> Result<HurlResult, ParseError> {
_ => return Err("expecting a bool for the status".to_string()),
};
let cookies = vec![];
Ok(HurlResult { filename, entries, time_in_ms, success, cookies })
Ok(HurlResult {
filename,
entries,
time_in_ms,
success,
cookies,
})
} else {
Err("expecting an object for the result".to_string())
}
@ -96,7 +97,6 @@ fn parse_entry_result(value: serde_json::Value) -> Result<EntryResult, String> {
})
}
pub fn parse_request(value: serde_json::Value) -> Result<Request, ParseError> {
if let serde_json::Value::Object(map) = value {
let method = match map.get("method") {
@ -144,7 +144,6 @@ pub fn parse_request(value: serde_json::Value) -> Result<Request, ParseError> {
_ => vec![],
};
let cookies = match map.get("cookies") {
Some(serde_json::Value::Array(values)) => {
let mut headers = vec![];
@ -181,11 +180,13 @@ pub fn parse_request(value: serde_json::Value) -> Result<Request, ParseError> {
pub fn parse_response(value: serde_json::Value) -> Result<Response, ParseError> {
if let serde_json::Value::Object(map) = value {
let status = match map.get("status") {
Some(serde_json::Value::Number(x)) => if let Some(x) = x.as_u64() {
x as u32
} else {
return Err("expecting a integer for the status".to_string());
},
Some(serde_json::Value::Number(x)) => {
if let Some(x) = x.as_u64() {
x as u32
} else {
return Err("expecting a integer for the status".to_string());
}
}
_ => return Err("expecting a number for the status".to_string()),
};
@ -228,11 +229,10 @@ fn parse_method(s: String) -> Result<Method, ParseError> {
"OPTIONS" => Ok(Method::Options),
"TRACE" => Ok(Method::Trace),
"PATCH" => Ok(Method::Patch),
_ => Err(format!("Invalid method <{}>", s))
_ => Err(format!("Invalid method <{}>", s)),
}
}
fn parse_header(value: serde_json::Value) -> Result<Header, ParseError> {
if let serde_json::Value::Object(map) = value {
let name = match map.get("name") {
@ -295,90 +295,123 @@ pub fn parse_response_cookie(value: serde_json::Value) -> Result<ResponseCookie,
match map.get("expires") {
None => {}
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute { name: "Expires".to_string(), value: Some(s.to_string()) }),
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute {
name: "Expires".to_string(),
value: Some(s.to_string()),
}),
_ => return Err("expecting a string for the cookie expires".to_string()),
};
match map.get("max_age") {
None => {}
Some(serde_json::Value::Number(n)) => attributes.push(CookieAttribute { name: "Max-Age".to_string(), value: Some(n.to_string()) }),
Some(serde_json::Value::Number(n)) => attributes.push(CookieAttribute {
name: "Max-Age".to_string(),
value: Some(n.to_string()),
}),
_ => return Err("expecting an integer for the cookie max_age".to_string()),
};
match map.get("domain") {
None => {}
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute { name: "Domain".to_string(), value: Some(s.to_string()) }),
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute {
name: "Domain".to_string(),
value: Some(s.to_string()),
}),
_ => return Err("expecting a string for the cookie domain".to_string()),
};
match map.get("path") {
None => {}
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute { name: "Path".to_string(), value: Some(s.to_string()) }),
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute {
name: "Path".to_string(),
value: Some(s.to_string()),
}),
_ => return Err("expecting a string for the cookie path".to_string()),
};
match map.get("secure") {
None => {}
Some(serde_json::Value::Bool(true)) => attributes.push(CookieAttribute { name: "Secure".to_string(), value: None }),
Some(serde_json::Value::Bool(true)) => attributes.push(CookieAttribute {
name: "Secure".to_string(),
value: None,
}),
_ => return Err("expecting a true for the cookie secure flag".to_string()),
};
match map.get("http_only") {
None => {}
Some(serde_json::Value::Bool(true)) => attributes.push(CookieAttribute { name: "HttpOnly".to_string(), value: None }),
Some(serde_json::Value::Bool(true)) => attributes.push(CookieAttribute {
name: "HttpOnly".to_string(),
value: None,
}),
_ => return Err("expecting a true for the cookie http_only flag".to_string()),
};
match map.get("same_site") {
None => {}
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute { name: "SameSite".to_string(), value: Some(s.to_string()) }),
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute {
name: "SameSite".to_string(),
value: Some(s.to_string()),
}),
_ => return Err("expecting a string for the cookie same_site".to_string()),
};
Ok(ResponseCookie { name, value, attributes })
Ok(ResponseCookie {
name,
value,
attributes,
})
} else {
Err("Expecting object for one cookie".to_string())
}
}
fn parse_version(s: String) -> Result<Version, ParseError> {
match s.as_str() {
"HTTP/1.0" => Ok(Version::Http10),
"HTTP/1.1" => Ok(Version::Http11),
"HTTP/2" => Ok(Version::Http2),
_ => Err("Expecting version HTTP/1.0, HTTP/1.2 or HTTP/2".to_string())
_ => Err("Expecting version HTTP/1.0, HTTP/1.2 or HTTP/2".to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_request() {
let v: serde_json::Value = serde_json::from_str(r#"{
let v: serde_json::Value = serde_json::from_str(
r#"{
"method": "GET",
"url": "http://localhost:8000/hello",
"headers": []
}"#).unwrap();
}"#,
)
.unwrap();
assert_eq!(parse_request(v).unwrap(), hello_http_request());
let v: serde_json::Value = serde_json::from_str(r#"{
let v: serde_json::Value = serde_json::from_str(
r#"{
"method": "GET",
"url": "http://localhost:8000/querystring-params?param1=value1&param2=a%20b",
"headers": []
}"#).unwrap();
assert_eq!(parse_request(v).unwrap(), Request {
method: Method::Get,
url: "http://localhost:8000/querystring-params?param1=value1&param2=a%20b".to_string(),
querystring: vec![],
headers: vec![],
cookies: vec![],
body: vec![],
form: vec![],
multipart: vec![],
content_type: None,
});
}"#,
)
.unwrap();
assert_eq!(
parse_request(v).unwrap(),
Request {
method: Method::Get,
url: "http://localhost:8000/querystring-params?param1=value1&param2=a%20b"
.to_string(),
querystring: vec![],
headers: vec![],
cookies: vec![],
body: vec![],
form: vec![],
multipart: vec![],
content_type: None,
}
);
let v: serde_json::Value = serde_json::from_str(r#"{
let v: serde_json::Value = serde_json::from_str(
r#"{
"method": "GET",
"url": "http://localhost/custom",
"headers": [
@ -389,13 +422,16 @@ mod tests {
{"name": "theme", "value": "light"},
{"name": "sessionToken", "value": "abc123"}
]
}"#).unwrap();
}"#,
)
.unwrap();
assert_eq!(parse_request(v).unwrap(), custom_http_request());
}
#[test]
fn test_parse_response() {
let v: serde_json::Value = serde_json::from_str(r#"{
let v: serde_json::Value = serde_json::from_str(
r#"{
"status": 200,
"httpVersion": "HTTP/1.0",
"headers": [
@ -403,16 +439,27 @@ mod tests {
{"name": "Content-Length", "value": "12" }
]
}"#).unwrap();
assert_eq!(parse_response(v).unwrap(), Response {
version: Version::Http10,
status: 200,
headers: vec![
Header { name: String::from("Content-Type"), value: String::from("text/html; charset=utf-8") },
Header { name: String::from("Content-Length"), value: String::from("12") },
],
body: vec![],
});
}"#,
)
.unwrap();
assert_eq!(
parse_response(v).unwrap(),
Response {
version: Version::Http10,
status: 200,
headers: vec![
Header {
name: String::from("Content-Type"),
value: String::from("text/html; charset=utf-8")
},
Header {
name: String::from("Content-Length"),
value: String::from("12")
},
],
body: vec![],
}
);
}
#[test]
@ -425,30 +472,46 @@ mod tests {
#[test]
fn test_parse_header() {
let v: serde_json::Value = serde_json::from_str(r#"{
let v: serde_json::Value = serde_json::from_str(
r#"{
"name": "name1",
"value": "value1"
}"#).unwrap();
assert_eq!(parse_header(v).unwrap(), Header { name: "name1".to_string(), value: "value1".to_string() });
}"#,
)
.unwrap();
assert_eq!(
parse_header(v).unwrap(),
Header {
name: "name1".to_string(),
value: "value1".to_string()
}
);
}
#[test]
fn test_parse_response_cookie() {
let v: serde_json::Value = serde_json::from_str(r#"{
let v: serde_json::Value = serde_json::from_str(
r#"{
"name": "name1",
"value": "value1"
}"#).unwrap();
assert_eq!(parse_response_cookie(v).unwrap(),
ResponseCookie {
name: "name1".to_string(),
value: "value1".to_string(),
attributes: vec![],
}
}"#,
)
.unwrap();
assert_eq!(
parse_response_cookie(v).unwrap(),
ResponseCookie {
name: "name1".to_string(),
value: "value1".to_string(),
attributes: vec![],
}
);
}
#[test]
fn test_parse_version() {
assert_eq!(parse_version("HTTP/1.0".to_string()).unwrap(), Version::Http10);
assert_eq!(
parse_version("HTTP/1.0".to_string()).unwrap(),
Version::Http10
);
}
}

View File

@ -16,19 +16,18 @@
*
*/
use serde::ser::{Serializer, SerializeStruct};
use serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
use crate::http::*;
use super::cookie::*;
use super::core::*;
impl Serialize for HurlResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 3)?;
state.serialize_field("filename", &self.clone().filename)?;
@ -42,8 +41,8 @@ impl Serialize for HurlResult {
impl Serialize for EntryResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut state = serializer.serialize_struct("EntryResult", 3)?;
if let Some(request) = &self.request {
@ -61,11 +60,14 @@ impl Serialize for EntryResult {
impl Serialize for AssertResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 3)?;
if let AssertResult::Version { actual, expected,.. } = self {
if let AssertResult::Version {
actual, expected, ..
} = self
{
//state.serialize_field("source_info", source_info)?;
state.serialize_field("actual", actual)?;
state.serialize_field("expected", expected)?;
@ -74,11 +76,10 @@ impl Serialize for AssertResult {
}
}
impl Serialize for CaptureResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut state = serializer.serialize_struct("CaptureResult", 3)?;
state.serialize_field("name", self.name.as_str())?;
@ -87,11 +88,10 @@ impl Serialize for CaptureResult {
}
}
impl Serialize for Request {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
// 3 is the number of fields in the struct.
let mut state = serializer.serialize_struct("??", 3)?;
@ -110,11 +110,10 @@ impl Serialize for Request {
}
}
impl Serialize for Response {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
// 3 is the number of fields in the struct.
let mut state = serializer.serialize_struct("??", 3)?;
@ -130,8 +129,8 @@ impl Serialize for Response {
impl Serialize for Header {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 3)?;
state.serialize_field("name", &self.name)?;
@ -142,8 +141,8 @@ impl Serialize for Header {
impl Serialize for Param {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 3)?;
state.serialize_field("name", &self.name)?;
@ -154,8 +153,8 @@ impl Serialize for Param {
impl Serialize for RequestCookie {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 2)?;
state.serialize_field("name", &self.name)?;
@ -166,8 +165,8 @@ impl Serialize for RequestCookie {
impl Serialize for Version {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
match self {
Version::Http10 => serializer.serialize_str("HTTP/1.0"),
@ -179,8 +178,8 @@ impl Serialize for Version {
impl Serialize for ResponseCookie {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut state = serializer.serialize_struct("ResponseCookie", 3)?;
state.serialize_field("name", &self.clone().name)?;
@ -210,11 +209,10 @@ impl Serialize for ResponseCookie {
}
}
impl Serialize for Cookie {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Cookie", 3)?;
state.serialize_field("domain", &self.clone().domain)?;
@ -226,4 +224,4 @@ impl Serialize for Cookie {
state.serialize_field("value", &self.clone().value)?;
state.end()
}
}
}

View File

@ -29,11 +29,12 @@ mod capture;
mod cookie;
pub mod core;
mod entry;
mod expr;
pub mod file;
mod http_response;
mod json;
pub mod log_serialize;
pub mod log_deserialize;
pub mod log_serialize;
mod multipart;
mod predicate;
mod query;
@ -41,6 +42,3 @@ pub mod request;
mod response;
mod template;
mod xpath;
mod expr;

View File

@ -17,7 +17,6 @@
*/
extern crate libxml;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
@ -32,12 +31,11 @@ use crate::http;
use super::core::{Error, RunnerError};
impl MultipartParam {
pub fn eval(self,
variables: &HashMap<String, Value>,
context_dir: String,
pub fn eval(
self,
variables: &HashMap<String, Value>,
context_dir: String,
) -> Result<http::MultipartParam, Error> {
match self {
MultipartParam::Param(KeyValue { key, value, .. }) => {
@ -62,7 +60,11 @@ impl FileParam {
let absolute_filename = if path.is_absolute() {
filename.value.clone()
} else {
Path::new(context_dir.as_str()).join(filename.value.clone()).to_str().unwrap().to_string()
Path::new(context_dir.as_str())
.join(filename.value.clone())
.to_str()
.unwrap()
.to_string()
};
let data = match File::open(absolute_filename.clone()) {
@ -70,24 +72,34 @@ impl FileParam {
let mut bytes = Vec::new();
match f.read_to_end(&mut bytes) {
Ok(_) => bytes,
Err(_) => return Err(Error {
source_info: filename.source_info,
inner: RunnerError::FileReadAccess { value: absolute_filename },
assert: false,
})
Err(_) => {
return Err(Error {
source_info: filename.source_info,
inner: RunnerError::FileReadAccess {
value: absolute_filename,
},
assert: false,
})
}
}
}
Err(_) => return Err(Error {
source_info: filename.source_info,
inner: RunnerError::FileReadAccess { value: absolute_filename },
assert: false,
})
Err(_) => {
return Err(Error {
source_info: filename.source_info,
inner: RunnerError::FileReadAccess {
value: absolute_filename,
},
assert: false,
})
}
};
if !Path::new(&absolute_filename).exists() {
return Err(Error {
source_info: filename.source_info,
inner: RunnerError::FileReadAccess { value: filename.value.clone() },
inner: RunnerError::FileReadAccess {
value: filename.value.clone(),
},
assert: false,
});
}
@ -102,11 +114,13 @@ impl FileParam {
}
}
impl FileValue {
pub fn content_type(&self) -> String {
match self.content_type.clone() {
None => match Path::new(self.filename.value.as_str()).extension().and_then(OsStr::to_str) {
None => match Path::new(self.filename.value.as_str())
.extension()
.and_then(OsStr::to_str)
{
Some("gif") => "image/gif".to_string(),
Some("jpg") => "image/jpeg".to_string(),
Some("jpeg") => "image/jpeg".to_string(),
@ -118,13 +132,12 @@ impl FileValue {
Some("pdf") => "application/pdf".to_string(),
Some("xml") => "application/xml".to_string(),
_ => "application/octet-stream".to_string(),
}
Some(content_type) => content_type
},
Some(content_type) => content_type,
}
}
}
#[cfg(test)]
mod tests {
use crate::core::common::SourceInfo;
@ -138,7 +151,6 @@ mod tests {
}
}
#[test]
pub fn test_eval_file_param() {
let line_terminator = LineTerminator {
@ -146,78 +158,101 @@ mod tests {
comment: None,
newline: whitespace(),
};
assert_eq!(FileParam {
line_terminators: vec![],
space0: whitespace(),
key: EncodedString {
value: "upload1".to_string(),
encoded: "upload1".to_string(),
quotes: false,
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
value: FileValue {
assert_eq!(
FileParam {
line_terminators: vec![],
space0: whitespace(),
filename: Filename { value: "hello.txt".to_string(), source_info: SourceInfo::init(0, 0, 0, 0) },
key: EncodedString {
value: "upload1".to_string(),
encoded: "upload1".to_string(),
quotes: false,
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
content_type: None,
},
line_terminator0: line_terminator,
}.eval("integration/tests".to_string()).unwrap(),
http::FileParam {
name: "upload1".to_string(),
filename: "hello.txt".to_string(),
data: b"Hello World!".to_vec(),
content_type: "text/plain".to_string(),
});
value: FileValue {
space0: whitespace(),
filename: Filename {
value: "hello.txt".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0)
},
space1: whitespace(),
space2: whitespace(),
content_type: None,
},
line_terminator0: line_terminator,
}
.eval("integration/tests".to_string())
.unwrap(),
http::FileParam {
name: "upload1".to_string(),
filename: "hello.txt".to_string(),
data: b"Hello World!".to_vec(),
content_type: "text/plain".to_string(),
}
);
}
#[test]
pub fn test_file_value_content_type() {
assert_eq!(FileValue {
space0: whitespace(),
filename: Filename {
value: "hello.txt".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
content_type: None,
}.content_type(), "text/plain".to_string());
assert_eq!(
FileValue {
space0: whitespace(),
filename: Filename {
value: "hello.txt".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
content_type: None,
}
.content_type(),
"text/plain".to_string()
);
assert_eq!(FileValue {
space0: whitespace(),
filename: Filename {
value: "hello.html".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
content_type: None,
}.content_type(), "text/html".to_string());
assert_eq!(
FileValue {
space0: whitespace(),
filename: Filename {
value: "hello.html".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
content_type: None,
}
.content_type(),
"text/html".to_string()
);
assert_eq!(FileValue {
space0: whitespace(),
filename: Filename {
value: "hello.txt".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
content_type: Some("text/html".to_string()),
}.content_type(), "text/html".to_string());
assert_eq!(
FileValue {
space0: whitespace(),
filename: Filename {
value: "hello.txt".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
content_type: Some("text/html".to_string()),
}
.content_type(),
"text/html".to_string()
);
assert_eq!(FileValue {
space0: whitespace(),
filename: Filename {
value: "hello".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
content_type: None,
}.content_type(), "application/octet-stream".to_string());
assert_eq!(
FileValue {
space0: whitespace(),
filename: Filename {
value: "hello".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
space1: whitespace(),
space2: whitespace(),
content_type: None,
}
.content_type(),
"application/octet-stream".to_string()
);
}
}

View File

@ -19,12 +19,12 @@ use std::collections::HashMap;
use regex::Regex;
use crate::core::common::{Pos, SourceInfo};
use crate::core::common::Value;
use crate::core::common::{Pos, SourceInfo};
use super::core::{Error, RunnerError};
use super::core::*;
use super::super::core::ast::*;
use super::core::*;
use super::core::{Error, RunnerError};
// equals 10 function return ()
// not equals 10
@ -34,7 +34,6 @@ use super::super::core::ast::*;
// PredicateValue => Recoverable with a not
// PredicateType
// xpath boolean(//user) equals 10
// ^^^^^^^^^^ Type does not matched with value return by query (generic message for the time-being
// xpath boolean(//user) not equals 10
@ -47,7 +46,6 @@ use super::super::core::ast::*;
// Predicate
// 2 evals
// 1) eval template
// 2) eval predicate
@ -57,8 +55,14 @@ impl Predicate {
pub fn eval(self, variables: &HashMap<String, Value>, value: Option<Value>) -> PredicateResult {
let assert_result = self.predicate_func.clone().eval(variables, value)?;
let source_info = SourceInfo {
start: Pos { line: self.space0.source_info.start.line, column: 0 },
end: Pos { line: self.space0.source_info.start.line, column: 0 },
start: Pos {
line: self.space0.source_info.start.line,
column: 0,
},
end: Pos {
line: self.space0.source_info.start.line,
column: 0,
},
};
if assert_result.type_mismatch {
Err(Error {
@ -110,7 +114,14 @@ impl Value {
Value::Integer(v) => format!("int <{}>", v.to_string()),
Value::String(v) => format!("string <{}>", v),
Value::Float(i, d) => format!("float <{}.{}>", i, d),
Value::List(values) => format!("[{}]", values.iter().map(|v| v.clone().display()).collect::<Vec<String>>().join(", ")),
Value::List(values) => format!(
"[{}]",
values
.iter()
.map(|v| v.clone().display())
.collect::<Vec<String>>()
.join(", ")
),
Value::Nodeset(n) => format!("nodeset of size <{}>", n),
Value::Object(_) => "object".to_string(),
Value::Bytes(values) => format!("byte array of size <{}>", values.len()),
@ -120,7 +131,11 @@ impl Value {
}
impl PredicateFunc {
fn eval(self, variables: &HashMap<String, Value>, optional_value: Option<Value>) -> Result<AssertResult, Error> {
fn eval(
self,
variables: &HashMap<String, Value>,
optional_value: Option<Value>,
) -> Result<AssertResult, Error> {
match optional_value {
None => {
let type_mismatch = if let PredicateFuncValue::Exist {} = self.value {
@ -135,107 +150,139 @@ impl PredicateFunc {
type_mismatch,
})
}
Some(value) => self.eval_something(variables, value)
Some(value) => self.eval_something(variables, value),
}
}
fn expected(self, variables: &HashMap<String, Value>) -> Result<String, Error> {
match self.value {
PredicateFuncValue::EqualInt { value: expected, .. } => {
PredicateFuncValue::EqualInt {
value: expected, ..
} => {
let expected = expected.to_string();
Ok(format!("int <{}>", expected))
}
PredicateFuncValue::EqualFloat { value: Float { int: expected_int, decimal: expected_dec, .. }, .. } => {
Ok(format!("float <{}.{}>", expected_int, expected_dec))
}
PredicateFuncValue::EqualFloat {
value:
Float {
int: expected_int,
decimal: expected_dec,
..
},
..
} => Ok(format!("float <{}.{}>", expected_int, expected_dec)),
PredicateFuncValue::EqualNull { .. } => Ok("null".to_string()),
PredicateFuncValue::EqualBool { value: expected, .. } => {
PredicateFuncValue::EqualBool {
value: expected, ..
} => {
let expected = expected.to_string();
Ok(format!("bool <{}>", expected))
}
PredicateFuncValue::EqualString { value: expected, .. } => {
PredicateFuncValue::EqualString {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
Ok(format!("string <{}>", expected))
}
PredicateFuncValue::EqualExpression { value: expected, .. } => {
PredicateFuncValue::EqualExpression {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
todo!(">> {:?}", expected)
}
PredicateFuncValue::CountEqual { value: expected, .. } => {
PredicateFuncValue::CountEqual {
value: expected, ..
} => {
let expected = expected.to_string();
Ok(format!("count equals to <{}>", expected))
}
PredicateFuncValue::StartWith { value: expected, .. } => {
PredicateFuncValue::StartWith {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
Ok(format!("starts with string <{}>", expected))
}
PredicateFuncValue::Contain { value: expected, .. } => {
PredicateFuncValue::Contain {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
Ok(format!("contains string <{}>", expected))
}
PredicateFuncValue::IncludeString { value: expected, .. } => {
PredicateFuncValue::IncludeString {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
Ok(format!("includes string <{}>", expected))
}
PredicateFuncValue::IncludeInt { value: expected, .. } => {
Ok(format!("includes int <{}>", expected))
}
PredicateFuncValue::IncludeFloat { value: expected, .. } => {
Ok(format!("includes float <{}>", expected))
}
PredicateFuncValue::IncludeInt {
value: expected, ..
} => Ok(format!("includes int <{}>", expected)),
PredicateFuncValue::IncludeFloat {
value: expected, ..
} => Ok(format!("includes float <{}>", expected)),
PredicateFuncValue::IncludeNull { .. } => Ok("includes null".to_string()),
PredicateFuncValue::IncludeBool { value: expected, .. } => {
Ok(format!("includes bool <{}>", expected))
}
PredicateFuncValue::IncludeExpression { value: _expected, .. } => {
todo!()
}
PredicateFuncValue::Match { value: expected, .. } => {
PredicateFuncValue::IncludeBool {
value: expected, ..
} => Ok(format!("includes bool <{}>", expected)),
PredicateFuncValue::IncludeExpression {
value: _expected, ..
} => todo!(),
PredicateFuncValue::Match {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
Ok(format!("matches regex <{}>", expected))
}
PredicateFuncValue::Exist {} => Ok("something".to_string())
PredicateFuncValue::Exist {} => Ok("something".to_string()),
}
}
fn eval_something(self, variables: &HashMap<String, Value>, value: Value) -> Result<AssertResult, Error> {
fn eval_something(
self,
variables: &HashMap<String, Value>,
value: Value,
) -> Result<AssertResult, Error> {
match self.value {
// equals int
PredicateFuncValue::EqualInt { value: expected, .. } => {
assert_values_equal(value, Value::Integer(expected))
}
PredicateFuncValue::EqualInt {
value: expected, ..
} => assert_values_equal(value, Value::Integer(expected)),
// equals null
PredicateFuncValue::EqualNull { .. } => {
assert_values_equal(value, Value::Null)
}
PredicateFuncValue::EqualNull { .. } => assert_values_equal(value, Value::Null),
// equals bool
PredicateFuncValue::EqualBool { value: expected, .. } => {
assert_values_equal(value, Value::Bool(expected))
}
PredicateFuncValue::EqualBool {
value: expected, ..
} => assert_values_equal(value, Value::Bool(expected)),
// equals float
PredicateFuncValue::EqualFloat { value: Float { int, decimal, .. }, .. } => {
assert_values_equal(value, Value::Float(int, decimal))
}
PredicateFuncValue::EqualFloat {
value: Float { int, decimal, .. },
..
} => assert_values_equal(value, Value::Float(int, decimal)),
// equals string
PredicateFuncValue::EqualString { value: expected, .. } => {
PredicateFuncValue::EqualString {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
assert_values_equal(value, Value::String(expected))
}
// equals expression
PredicateFuncValue::EqualExpression { value: expected, .. } => {
PredicateFuncValue::EqualExpression {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
assert_values_equal(value, expected)
}
// countEquals
PredicateFuncValue::CountEqual { value: expected_value, .. } => {
PredicateFuncValue::CountEqual {
value: expected_value,
..
} => {
let actual = value.clone().display();
let expected = format!("count equals to <{}>", expected_value);
match value {
@ -256,13 +303,14 @@ impl PredicateFunc {
actual,
expected,
type_mismatch: true,
})
}),
}
}
// starts with string
PredicateFuncValue::StartWith { value: expected, .. } => {
PredicateFuncValue::StartWith {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
match value.clone() {
Value::String(actual) => Ok(AssertResult {
@ -276,12 +324,14 @@ impl PredicateFunc {
actual: value.display(),
expected: format!("starts with string <{}>", expected),
type_mismatch: true,
})
}),
}
}
// contains
PredicateFuncValue::Contain { value: expected, .. } => {
PredicateFuncValue::Contain {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
match value.clone() {
Value::String(actual) => Ok(AssertResult {
@ -295,48 +345,59 @@ impl PredicateFunc {
actual: value.display(),
expected: format!("contains string <{}>", expected),
type_mismatch: true,
})
}),
}
}
// includes String
PredicateFuncValue::IncludeString { value: expected, .. } => {
PredicateFuncValue::IncludeString {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
assert_include(value,Value::String(expected))
assert_include(value, Value::String(expected))
}
// includes int
PredicateFuncValue::IncludeInt { value: expected, .. } => {
assert_include(value,Value::Integer(expected))
}
PredicateFuncValue::IncludeInt {
value: expected, ..
} => assert_include(value, Value::Integer(expected)),
// includes float
PredicateFuncValue::IncludeFloat { value: Float { int, decimal, .. }, .. } => {
assert_include(value,Value::Float(int, decimal))
}
PredicateFuncValue::IncludeFloat {
value: Float { int, decimal, .. },
..
} => assert_include(value, Value::Float(int, decimal)),
// includes bool
PredicateFuncValue::IncludeBool { value: expected, .. } => {
assert_include(value,Value::Bool(expected))
}
PredicateFuncValue::IncludeBool {
value: expected, ..
} => assert_include(value, Value::Bool(expected)),
// includes null
PredicateFuncValue::IncludeNull { .. } => {
assert_include(value,Value::Null)
}
PredicateFuncValue::IncludeNull { .. } => assert_include(value, Value::Null),
// includes expression
PredicateFuncValue::IncludeExpression { value: expected, .. } => {
PredicateFuncValue::IncludeExpression {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
assert_include(value,expected)
assert_include(value, expected)
}
// match string
PredicateFuncValue::Match { value: expected, .. } => {
PredicateFuncValue::Match {
value: expected, ..
} => {
let expected = expected.eval(variables)?;
let regex = match Regex::new(expected.as_str()) {
Ok(re) => re,
Err(_) => return Err(Error { source_info: self.source_info.clone(), inner: RunnerError::InvalidRegex(), assert: false })
Err(_) => {
return Err(Error {
source_info: self.source_info.clone(),
inner: RunnerError::InvalidRegex(),
assert: false,
})
}
};
match value.clone() {
Value::String(actual) => Ok(AssertResult {
@ -350,32 +411,29 @@ impl PredicateFunc {
actual: value.display(),
expected: format!("matches regex <{}>", expected),
type_mismatch: true,
})
}),
}
}
// exists
PredicateFuncValue::Exist {} => {
match value {
Value::Nodeset(0) => Ok(AssertResult {
success: false,
actual: value.display(),
expected: "something".to_string(),
type_mismatch: false,
}),
_ => Ok(AssertResult {
success: true,
actual: value.display(),
expected: "something".to_string(),
type_mismatch: false,
})
}
}
PredicateFuncValue::Exist {} => match value {
Value::Nodeset(0) => Ok(AssertResult {
success: false,
actual: value.display(),
expected: "something".to_string(),
type_mismatch: false,
}),
_ => Ok(AssertResult {
success: true,
actual: value.display(),
expected: "something".to_string(),
type_mismatch: false,
}),
},
}
}
}
fn assert_values_equal(actual: Value, expected: Value) -> Result<AssertResult, Error> {
match (actual.clone(), expected.clone()) {
(Value::Null {}, Value::Null {}) => Ok(AssertResult {
@ -396,7 +454,7 @@ fn assert_values_equal(actual: Value, expected: Value) -> Result<AssertResult, E
expected: expected.display(),
type_mismatch: false,
}),
(Value::Float(int1,decimal), Value::Integer(int2)) => Ok(AssertResult {
(Value::Float(int1, decimal), Value::Integer(int2)) => Ok(AssertResult {
success: int1 == int2 && decimal == 0,
actual: actual.display(),
expected: expected.display(),
@ -431,13 +489,12 @@ fn assert_values_equal(actual: Value, expected: Value) -> Result<AssertResult, E
actual: actual.display(),
expected: expected.display(),
type_mismatch: true,
})
}),
}
}
fn assert_include(value: Value, element: Value) -> Result<AssertResult, Error> {
let expected = format!("includes {}", element.clone().display());
let expected = format!("includes {}", element.clone().display());
match value.clone() {
Value::List(values) => {
let mut success = false;
@ -460,15 +517,10 @@ fn assert_include(value: Value, element: Value) -> Result<AssertResult, Error> {
actual: value.display(),
expected,
type_mismatch: true,
})
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -476,13 +528,11 @@ mod tests {
#[test]
fn test_invalid_xpath() {}
#[test]
fn test_predicate() {
// not equals 10 with value 1 OK
// not equals 10 with value 10 ValueError
// not equals 10 with value true TypeError
// not equals 10 with value 1 OK
// not equals 10 with value 10 ValueError
// not equals 10 with value true TypeError
let variables = HashMap::new();
let whitespace = Whitespace {
value: String::from(" "),
@ -493,29 +543,48 @@ mod tests {
not: true,
space0: whitespace.clone(),
predicate_func: PredicateFunc {
value: PredicateFuncValue::EqualInt { space0: whitespace, value: 10 },
value: PredicateFuncValue::EqualInt {
space0: whitespace,
value: 10,
},
source_info: SourceInfo::init(1, 11, 1, 12),
},
};
let error = predicate.clone().eval(&variables, Some(Value::Bool(true))).err().unwrap();
assert_eq!(error.inner, RunnerError::AssertFailure {
actual: "bool <true>".to_string(),
expected: "int <10>".to_string(),
type_mismatch: true,
});
let error = predicate
.clone()
.eval(&variables, Some(Value::Bool(true)))
.err()
.unwrap();
assert_eq!(
error.inner,
RunnerError::AssertFailure {
actual: "bool <true>".to_string(),
expected: "int <10>".to_string(),
type_mismatch: true,
}
);
assert_eq!(error.source_info, SourceInfo::init(1, 0, 1, 0));
let error = predicate.clone().eval(&variables, Some(Value::Integer(10))).err().unwrap();
assert_eq!(error.inner, RunnerError::AssertFailure {
actual: "int <10>".to_string(),
expected: "not int <10>".to_string(),
type_mismatch: false,
});
let error = predicate
.clone()
.eval(&variables, Some(Value::Integer(10)))
.err()
.unwrap();
assert_eq!(
error.inner,
RunnerError::AssertFailure {
actual: "int <10>".to_string(),
expected: "not int <10>".to_string(),
type_mismatch: false,
}
);
assert_eq!(error.source_info, SourceInfo::init(1, 0, 1, 0));
assert_eq!(predicate.eval(&variables, Some(Value::Integer(1))).unwrap(), ());
assert_eq!(
predicate.eval(&variables, Some(Value::Integer(1))).unwrap(),
()
);
}
#[test]
@ -526,9 +595,14 @@ mod tests {
source_info: SourceInfo::init(0, 0, 0, 0),
};
let assert_result = PredicateFunc {
value: PredicateFuncValue::EqualInt { space0: whitespace, value: 10 },
value: PredicateFuncValue::EqualInt {
space0: whitespace,
value: 10,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval(&variables, Some(Value::Bool(true))).unwrap();
}
.eval(&variables, Some(Value::Bool(true)))
.unwrap();
assert_eq!(assert_result.success, false);
assert_eq!(assert_result.type_mismatch, true);
assert_eq!(assert_result.actual.as_str(), "bool <true>");
@ -544,34 +618,55 @@ mod tests {
};
let assert_result = PredicateFunc {
value: PredicateFuncValue::EqualInt { space0: whitespace.clone(), value: 10 },
value: PredicateFuncValue::EqualInt {
space0: whitespace.clone(),
value: 10,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::Integer(1)).unwrap();
}
.eval_something(&variables, Value::Integer(1))
.unwrap();
assert_eq!(assert_result.success, false);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "int <1>");
assert_eq!(assert_result.expected.as_str(), "int <10>");
let assert_result = PredicateFunc {
value: PredicateFuncValue::EqualBool { space0: whitespace.clone(), value: true },
value: PredicateFuncValue::EqualBool {
space0: whitespace.clone(),
value: true,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::Bool(false)).unwrap();
}
.eval_something(&variables, Value::Bool(false))
.unwrap();
assert_eq!(assert_result.success, false);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "bool <false>");
assert_eq!(assert_result.expected.as_str(), "bool <true>");
let assert_result = PredicateFunc {
value: PredicateFuncValue::EqualFloat { space0: whitespace, value: Float { int: 1, decimal: 200_000_000_000_000_000, decimal_digits: 0 } },
value: PredicateFuncValue::EqualFloat {
space0: whitespace,
value: Float {
int: 1,
decimal: 200_000_000_000_000_000,
decimal_digits: 0,
},
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::Float(1, 1)).unwrap();
}
.eval_something(&variables, Value::Float(1, 1))
.unwrap();
assert_eq!(assert_result.success, false);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "float <1.1>");
assert_eq!(assert_result.expected.as_str(), "float <1.200000000000000000>");
assert_eq!(
assert_result.expected.as_str(),
"float <1.200000000000000000>"
);
}
#[test]
fn test_predicate_value_equals() {
let variables = HashMap::new();
@ -580,39 +675,61 @@ mod tests {
source_info: SourceInfo::init(0, 0, 0, 0),
};
let assert_result = PredicateFunc {
value: PredicateFuncValue::EqualInt { space0: whitespace.clone(), value: 1 },
value: PredicateFuncValue::EqualInt {
space0: whitespace.clone(),
value: 1,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::Integer(1)).unwrap();
}
.eval_something(&variables, Value::Integer(1))
.unwrap();
assert_eq!(assert_result.success, true);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "int <1>");
assert_eq!(assert_result.expected.as_str(), "int <1>");
let assert_result = PredicateFunc {
value: PredicateFuncValue::EqualBool { space0: whitespace.clone(), value: false },
value: PredicateFuncValue::EqualBool {
space0: whitespace.clone(),
value: false,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::Bool(false)).unwrap();
}
.eval_something(&variables, Value::Bool(false))
.unwrap();
assert_eq!(assert_result.success, true);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "bool <false>");
assert_eq!(assert_result.expected.as_str(), "bool <false>");
let assert_result = PredicateFunc {
value: PredicateFuncValue::EqualFloat { space0: whitespace.clone(), value: Float { int: 1, decimal: 1, decimal_digits: 1 } },
value: PredicateFuncValue::EqualFloat {
space0: whitespace.clone(),
value: Float {
int: 1,
decimal: 1,
decimal_digits: 1,
},
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::Float(1, 1)).unwrap();
}
.eval_something(&variables, Value::Float(1, 1))
.unwrap();
assert_eq!(assert_result.success, true);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "float <1.1>");
assert_eq!(assert_result.expected.as_str(), "float <1.1>");
// a float can be equals to an int (but the reverse)
// a float can be equals to an int (but the reverse)
let assert_result = PredicateFunc {
value: PredicateFuncValue::EqualInt { space0: whitespace, value: 1 },
value: PredicateFuncValue::EqualInt {
space0: whitespace,
value: 1,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::Float(1, 0)).unwrap();
}
.eval_something(&variables, Value::Float(1, 0))
.unwrap();
assert_eq!(assert_result.success, true);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "float <1.0>");
@ -627,34 +744,72 @@ mod tests {
source_info: SourceInfo::init(0, 0, 0, 0),
};
let template = Template {
quotes: true,
elements: vec![
TemplateElement::Expression(Expr {
space0: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 11, 1, 11) },
variable: Variable { name: String::from("base_url"), source_info: SourceInfo::init(1, 11, 1, 19) },
space1: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 19, 1, 19) },
})],
elements: vec![TemplateElement::Expression(Expr {
space0: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 11, 1, 11),
},
variable: Variable {
name: String::from("base_url"),
source_info: SourceInfo::init(1, 11, 1, 19),
},
space1: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 19, 1, 19),
},
})],
source_info: SourceInfo::init(1, 1, 1, 1),
};
let error = PredicateFunc {
value: PredicateFuncValue::EqualString { space0: whitespace.clone(), value: template.clone() },
value: PredicateFuncValue::EqualString {
space0: whitespace.clone(),
value: template.clone(),
},
source_info: SourceInfo::init(1, 1, 1, 21),
}.eval_something(&variables, Value::String(String::from("http://localhost:8000"))).err().unwrap();
assert_eq!(error.inner, RunnerError::TemplateVariableNotDefined { name: String::from("base_url") });
}
.eval_something(
&variables,
Value::String(String::from("http://localhost:8000")),
)
.err()
.unwrap();
assert_eq!(
error.inner,
RunnerError::TemplateVariableNotDefined {
name: String::from("base_url")
}
);
assert_eq!(error.source_info, SourceInfo::init(1, 11, 1, 19));
variables.insert(String::from("base_url"), Value::String(String::from("http://localhost:8000")));
variables.insert(
String::from("base_url"),
Value::String(String::from("http://localhost:8000")),
);
let assert_result = PredicateFunc {
value: PredicateFuncValue::EqualString { space0: whitespace, value: template },
value: PredicateFuncValue::EqualString {
space0: whitespace,
value: template,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::String(String::from("http://localhost:8000"))).unwrap();
}
.eval_something(
&variables,
Value::String(String::from("http://localhost:8000")),
)
.unwrap();
assert_eq!(assert_result.success, true);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "string <http://localhost:8000>");
assert_eq!(assert_result.expected.as_str(), "string <http://localhost:8000>");
assert_eq!(
assert_result.actual.as_str(),
"string <http://localhost:8000>"
);
assert_eq!(
assert_result.expected.as_str(),
"string <http://localhost:8000>"
);
}
#[test]
@ -666,18 +821,28 @@ mod tests {
};
let assert_result = PredicateFunc {
value: PredicateFuncValue::CountEqual { space0: whitespace.clone(), value: 10 },
value: PredicateFuncValue::CountEqual {
space0: whitespace.clone(),
value: 10,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::Bool(true)).unwrap();
}
.eval_something(&variables, Value::Bool(true))
.unwrap();
assert_eq!(assert_result.success, false);
assert_eq!(assert_result.type_mismatch, true);
assert_eq!(assert_result.actual.as_str(), "bool <true>");
assert_eq!(assert_result.expected.as_str(), "count equals to <10>");
let assert_result = PredicateFunc {
value: PredicateFuncValue::CountEqual { space0: whitespace.clone(), value: 1 },
value: PredicateFuncValue::CountEqual {
space0: whitespace.clone(),
value: 1,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::List(vec![])).unwrap();
}
.eval_something(&variables, Value::List(vec![]))
.unwrap();
assert_eq!(assert_result.success, false);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "[]");
@ -685,8 +850,13 @@ mod tests {
let assert_result = PredicateFunc {
source_info: SourceInfo::init(0, 0, 0, 0),
value: PredicateFuncValue::CountEqual { space0: whitespace, value: 1 },
}.eval_something(&variables, Value::Nodeset(3)).unwrap();
value: PredicateFuncValue::CountEqual {
space0: whitespace,
value: 1,
},
}
.eval_something(&variables, Value::Nodeset(3))
.unwrap();
assert_eq!(assert_result.success, false);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "nodeset of size <3>");
@ -701,23 +871,31 @@ mod tests {
source_info: SourceInfo::init(0, 0, 0, 0),
};
let assert_result = PredicateFunc {
value: PredicateFuncValue::CountEqual { space0: whitespace.clone(), value: 1 },
value: PredicateFuncValue::CountEqual {
space0: whitespace.clone(),
value: 1,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::List(vec![Value::Integer(1)])).unwrap();
}
.eval_something(&variables, Value::List(vec![Value::Integer(1)]))
.unwrap();
assert_eq!(assert_result.success, true);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "[int <1>]");
assert_eq!(assert_result.expected.as_str(), "count equals to <1>");
let assert_result = PredicateFunc {
value: PredicateFuncValue::CountEqual { space0: whitespace, value: 1 },
value: PredicateFuncValue::CountEqual {
space0: whitespace,
value: 1,
},
source_info: SourceInfo::init(0, 0, 0, 0),
}.eval_something(&variables, Value::Nodeset(1)).unwrap();
}
.eval_something(&variables, Value::Nodeset(1))
.unwrap();
assert_eq!(assert_result.success, true);
assert_eq!(assert_result.type_mismatch, false);
assert_eq!(assert_result.actual.as_str(), "nodeset of size <1>");
assert_eq!(assert_result.expected.as_str(), "count equals to <1>");
}
}

View File

@ -24,16 +24,19 @@ use crate::core::common::Value;
use crate::http;
use crate::jsonpath;
use super::super::core::ast::*;
use super::cookie;
use super::core::{Error, RunnerError};
use super::super::core::ast::*;
use super::xpath;
pub type QueryResult = Result<Option<Value>, Error>;
impl Query {
pub fn eval(self, variables: &HashMap<String, Value>, http_response: http::Response) -> QueryResult {
pub fn eval(
self,
variables: &HashMap<String, Value>,
http_response: http::Response,
) -> QueryResult {
match self.value {
QueryValue::Status {} => Ok(Some(Value::Integer(i64::from(http_response.status)))),
QueryValue::Header { name, .. } => {
@ -45,11 +48,17 @@ impl Query {
let value = values.first().unwrap().to_string();
Ok(Some(Value::String(value)))
} else {
let values = values.iter().map(|v| Value::String(v.to_string())).collect();
let values = values
.iter()
.map(|v| Value::String(v.to_string()))
.collect();
Ok(Some(Value::List(values)))
}
}
QueryValue::Cookie { expr: CookiePath { name, attribute }, .. } => {
QueryValue::Cookie {
expr: CookiePath { name, attribute },
..
} => {
let cookie_name = name.eval(variables)?;
match http_response.get_cookie(cookie_name) {
None => Ok(None),
@ -67,14 +76,22 @@ impl Query {
// can return a string if encoding is known and utf8
match http_response.text() {
Ok(s) => Ok(Some(Value::String(s))),
Err(inner) => Err(Error { source_info: self.source_info.clone(), inner, assert: false }),
Err(inner) => Err(Error {
source_info: self.source_info.clone(),
inner,
assert: false,
}),
}
}
QueryValue::Xpath { expr, .. } => {
let source_info = expr.source_info.clone();
let value = expr.eval(variables)?;
match http_response.text() {
Err(inner) => Err(Error { source_info: self.source_info.clone(), inner, assert: false }),
Err(inner) => Err(Error {
source_info: self.source_info.clone(),
inner,
assert: false,
}),
Ok(xml) => {
let result = if http_response.is_html() {
xpath::eval_html(xml, value.clone())
@ -85,14 +102,12 @@ impl Query {
Ok(value) => Ok(Some(value)),
Err(xpath::XpathError::InvalidXML {}) => Err(Error {
source_info: self.source_info,
inner: RunnerError::QueryInvalidXml
,
inner: RunnerError::QueryInvalidXml,
assert: false,
}),
Err(xpath::XpathError::InvalidHtml {}) => Err(Error {
source_info: self.source_info,
inner: RunnerError::QueryInvalidXml
,
inner: RunnerError::QueryInvalidXml,
assert: false,
}),
Err(xpath::XpathError::Eval {}) => Err(Error {
@ -110,41 +125,58 @@ impl Query {
QueryValue::Jsonpath { expr, .. } => {
let value = expr.clone().eval(variables)?;
let source_info = expr.source_info;
// let expr = match jsonpath::Expr::init(value.as_str()) {
// None => return Err(Error { source_info: source_info.clone(), inner: RunnerError::QueryInvalidJsonpathExpression {}, assert: false }),
// Some(expr) => expr
// };
// let json = match String::from_utf8(http_response.body) {
// Err(_) => return Err(Error { source_info: self.source_info, inner: RunnerError::InvalidUtf8, assert: false }),
// Ok(v) => v
// };
// let value = match expr.eval(json.as_str()) {
// Err(_) => {
// return Err(Error { source_info: self.source_info, inner: RunnerError::QueryInvalidJson, assert: false });
// }
// Ok(value) => {
// if value == Value::List(vec![]) { Value::None } else { value }
// }
// };
// let expr = match jsonpath::Expr::init(value.as_str()) {
// None => return Err(Error { source_info: source_info.clone(), inner: RunnerError::QueryInvalidJsonpathExpression {}, assert: false }),
// Some(expr) => expr
// };
// let json = match String::from_utf8(http_response.body) {
// Err(_) => return Err(Error { source_info: self.source_info, inner: RunnerError::InvalidUtf8, assert: false }),
// Ok(v) => v
// };
// let value = match expr.eval(json.as_str()) {
// Err(_) => {
// return Err(Error { source_info: self.source_info, inner: RunnerError::QueryInvalidJson, assert: false });
// }
// Ok(value) => {
// if value == Value::List(vec![]) { Value::None } else { value }
// }
// };
// Using your own json implem
let query = match jsonpath::parser::parse::parse(value.as_str()) {
Ok(q) => q,
Err(_) => return Err(Error { source_info, inner: RunnerError::QueryInvalidJsonpathExpression { value }, assert: false }),
Err(_) => {
return Err(Error {
source_info,
inner: RunnerError::QueryInvalidJsonpathExpression { value },
assert: false,
})
}
};
let json = match http_response.text() {
Err(inner) => return Err(Error { source_info: self.source_info.clone(), inner, assert: false }),
Err(inner) => {
return Err(Error {
source_info: self.source_info.clone(),
inner,
assert: false,
})
}
Ok(v) => v,
};
let value = match serde_json::from_str(json.as_str()) {
Err(_) => {
return Err(Error { source_info: self.source_info, inner: RunnerError::QueryInvalidJson, assert: false });
return Err(Error {
source_info: self.source_info,
inner: RunnerError::QueryInvalidJson,
assert: false,
});
}
Ok(v) => v
Ok(v) => v,
};
let results = query.eval(value);
if results.is_empty() {
Ok(None)
} else if results.len() == 1 { // list coercions
} else if results.len() == 1 {
// list coercions
Ok(Some(Value::from_json(results.get(0).unwrap())))
} else {
Ok(Some(Value::from_json(&serde_json::Value::Array(results))))
@ -154,7 +186,13 @@ impl Query {
let value = expr.clone().eval(variables)?;
let source_info = expr.source_info;
let s = match http_response.text() {
Err(inner) => return Err(Error { source_info: self.source_info.clone(), inner, assert: false }),
Err(inner) => {
return Err(Error {
source_info: self.source_info.clone(),
inner,
assert: false,
})
}
Ok(v) => v,
};
match Regex::new(value.as_str()) {
@ -162,14 +200,14 @@ impl Query {
Some(captures) => match captures.get(1) {
Some(v) => Ok(Some(Value::String(v.as_str().to_string()))),
None => Ok(None),
}
},
None => Ok(None),
}
},
Err(_) => Err(Error {
source_info,
inner: RunnerError::InvalidRegex(),
assert: false,
})
}),
}
}
QueryValue::Variable { name, .. } => {
@ -192,18 +230,29 @@ impl CookieAttributeName {
CookieAttributeName::MaxAge(_) => cookie.max_age().map(Value::Integer),
CookieAttributeName::Domain(_) => cookie.domain().map(Value::String),
CookieAttributeName::Path(_) => cookie.path().map(Value::String),
CookieAttributeName::Secure(_) => if cookie.has_secure() { Some(Value::Bool(true)) } else { None },
CookieAttributeName::HttpOnly(_) => if cookie.has_httponly() { Some(Value::Bool(true)) } else { None },
CookieAttributeName::Secure(_) => {
if cookie.has_secure() {
Some(Value::Bool(true))
} else {
None
}
}
CookieAttributeName::HttpOnly(_) => {
if cookie.has_httponly() {
Some(Value::Bool(true))
} else {
None
}
}
CookieAttributeName::SameSite(_) => cookie.samesite().map(Value::String),
}
}
}
#[cfg(test)]
pub mod tests {
use crate::core::common::{Pos, SourceInfo};
use super::*;
use crate::core::common::{Pos, SourceInfo};
pub fn xpath_invalid_query() -> Query {
// xpath ???
@ -217,12 +266,10 @@ pub mod tests {
space0: whitespace,
expr: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "???".to_string(),
encoded: "???".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "???".to_string(),
encoded: "???".to_string(),
}],
source_info: SourceInfo::init(1, 7, 1, 10),
},
},
@ -240,12 +287,10 @@ pub mod tests {
space0: whitespace,
expr: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "count(//user)".to_string(),
encoded: "count(//user)".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "count(//user)".to_string(),
encoded: "count(//user)".to_string(),
}],
source_info: SourceInfo::init(0, 0, 0, 0),
},
},
@ -263,12 +308,10 @@ pub mod tests {
space0: whitespace,
expr: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "//user".to_string(),
encoded: "/user".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "//user".to_string(),
encoded: "/user".to_string(),
}],
source_info: SourceInfo::init(0, 0, 0, 0),
},
},
@ -280,7 +323,8 @@ pub mod tests {
version: http::Version::Http10,
status: 0,
headers: vec![],
body: String::into_bytes(r#"
body: String::into_bytes(
r#"
{
"success":false,
"errors": [
@ -288,12 +332,14 @@ pub mod tests {
{"id": "error2"}
]
}
"#.to_string()),
"#
.to_string(),
),
}
}
pub fn jsonpath_success() -> Query {
// jsonpath $.success
// jsonpath $.success
Query {
source_info: SourceInfo::init(1, 1, 1, 19),
value: QueryValue::Jsonpath {
@ -302,12 +348,10 @@ pub mod tests {
source_info: SourceInfo::init(1, 9, 1, 10),
},
expr: Template {
elements: vec![
TemplateElement::String {
value: String::from("$.success"),
encoded: String::from("$.success"),
}
],
elements: vec![TemplateElement::String {
value: String::from("$.success"),
encoded: String::from("$.success"),
}],
quotes: true,
//delimiter: "".to_string(),
source_info: SourceInfo::init(1, 10, 1, 19),
@ -317,7 +361,7 @@ pub mod tests {
}
pub fn jsonpath_errors() -> Query {
// jsonpath $.errors
// jsonpath $.errors
Query {
source_info: SourceInfo::init(1, 1, 1, 19),
value: QueryValue::Jsonpath {
@ -326,12 +370,10 @@ pub mod tests {
source_info: SourceInfo::init(1, 9, 1, 10),
},
expr: Template {
elements: vec![
TemplateElement::String {
value: String::from("$.errors"),
encoded: String::from("$.errors"),
}
],
elements: vec![TemplateElement::String {
value: String::from("$.errors"),
encoded: String::from("$.errors"),
}],
quotes: true,
// delimiter: "".to_string(),
source_info: SourceInfo::init(1, 10, 1, 18),
@ -341,7 +383,7 @@ pub mod tests {
}
pub fn jsonpath_duration() -> Query {
// jsonpath $.errors
// jsonpath $.errors
Query {
source_info: SourceInfo::init(1, 1, 1, 19),
value: QueryValue::Jsonpath {
@ -350,12 +392,10 @@ pub mod tests {
source_info: SourceInfo::init(1, 9, 1, 10),
},
expr: Template {
elements: vec![
TemplateElement::String {
value: String::from("$.duration"),
encoded: String::from("$.duration"),
}
],
elements: vec![TemplateElement::String {
value: String::from("$.duration"),
encoded: String::from("$.duration"),
}],
quotes: true,
// delimiter: "".to_string(),
source_info: SourceInfo::init(1, 10, 1, 18),
@ -365,7 +405,7 @@ pub mod tests {
}
pub fn regex_name() -> Query {
// regex "Hello ([a-zA-Z]+)!"
// regex "Hello ([a-zA-Z]+)!"
Query {
source_info: SourceInfo::init(1, 1, 1, 26),
value: QueryValue::Regex {
@ -375,12 +415,10 @@ pub mod tests {
},
expr: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "Hello ([a-zA-Z]+)!".to_string(),
encoded: "Hello ([a-zA-Z]+)!".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "Hello ([a-zA-Z]+)!".to_string(),
encoded: "Hello ([a-zA-Z]+)!".to_string(),
}],
source_info: SourceInfo::init(1, 7, 1, 26),
},
},
@ -388,7 +426,7 @@ pub mod tests {
}
pub fn regex_invalid() -> Query {
// regex ???"
// regex ???"
Query {
source_info: SourceInfo::init(1, 1, 1, 26),
value: QueryValue::Regex {
@ -398,12 +436,10 @@ pub mod tests {
},
expr: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "???".to_string(),
encoded: "???".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "???".to_string(),
encoded: "???".to_string(),
}],
source_info: SourceInfo::init(1, 7, 1, 10),
},
},
@ -414,7 +450,13 @@ pub mod tests {
fn test_query_status() {
let variables = HashMap::new();
assert_eq!(
Query { source_info: SourceInfo::init(0, 0, 0, 0), value: QueryValue::Status {} }.eval(&variables, http::hello_http_response()).unwrap().unwrap(),
Query {
source_info: SourceInfo::init(0, 0, 0, 0),
value: QueryValue::Status {}
}
.eval(&variables, http::hello_http_response())
.unwrap()
.unwrap(),
Value::Integer(200)
);
}
@ -426,47 +468,57 @@ pub mod tests {
let query_header = Query {
source_info: SourceInfo::init(0, 0, 0, 0),
value: QueryValue::Header {
space0: Whitespace { value: String::from(" "), source_info: SourceInfo::init(1, 7, 1, 8) },
space0: Whitespace {
value: String::from(" "),
source_info: SourceInfo::init(1, 7, 1, 8),
},
name: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "Custom".to_string(),
encoded: "Custom".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "Custom".to_string(),
encoded: "Custom".to_string(),
}],
source_info: SourceInfo::init(2, 8, 2, 14),
},
},
};
// let error = query_header.eval(http::hello_http_response()).err().unwrap();
// assert_eq!(error.source_info.start, Pos { line: 1, column: 8 });
// assert_eq!(error.inner, RunnerError::QueryHeaderNotFound);
assert_eq!(query_header.eval(&variables, http::hello_http_response()).unwrap(), None);
// let error = query_header.eval(http::hello_http_response()).err().unwrap();
// assert_eq!(error.source_info.start, Pos { line: 1, column: 8 });
// assert_eq!(error.inner, RunnerError::QueryHeaderNotFound);
assert_eq!(
query_header
.eval(&variables, http::hello_http_response())
.unwrap(),
None
);
}
#[test]
fn test_header() {
// header Content-Type
// header Content-Type
let variables = HashMap::new();
let query_header = Query {
source_info: SourceInfo::init(0, 0, 0, 0),
value: QueryValue::Header {
space0: Whitespace { value: String::from(" "), source_info: SourceInfo::init(1, 7, 1, 8) },
space0: Whitespace {
value: String::from(" "),
source_info: SourceInfo::init(1, 7, 1, 8),
},
name: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "Content-Type".to_string(),
encoded: "Content-Type".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "Content-Type".to_string(),
encoded: "Content-Type".to_string(),
}],
source_info: SourceInfo::init(1, 8, 1, 16),
},
},
};
assert_eq!(
query_header.eval(&variables, http::hello_http_response()).unwrap().unwrap(),
query_header
.eval(&variables, http::hello_http_response())
.unwrap()
.unwrap(),
Value::String(String::from("text/html; charset=utf-8"))
);
}
@ -498,17 +550,20 @@ pub mod tests {
expr: CookiePath {
name: Template {
quotes: true,
elements: vec![
TemplateElement::String { value: "LSID".to_string(), encoded: "LSID".to_string() }
],
elements: vec![TemplateElement::String {
value: "LSID".to_string(),
encoded: "LSID".to_string(),
}],
source_info: SourceInfo::init(0, 0, 0, 0),
},
attribute: None,
},
},
};
assert_eq!(query.eval(&variables, response.clone()).unwrap().unwrap(), Value::String("DQAAAKEaem_vYg".to_string()));
assert_eq!(
query.eval(&variables, response.clone()).unwrap().unwrap(),
Value::String("DQAAAKEaem_vYg".to_string())
);
// cookie "LSID[Path]"
let query = Query {
@ -518,12 +573,12 @@ pub mod tests {
expr: CookiePath {
name: Template {
quotes: true,
elements: vec![
TemplateElement::String { value: "LSID".to_string(), encoded: "LSID".to_string() }
],
elements: vec![TemplateElement::String {
value: "LSID".to_string(),
encoded: "LSID".to_string(),
}],
source_info: SourceInfo::init(0, 0, 0, 0),
}
,
},
attribute: Some(CookieAttribute {
space0: space.clone(),
name: CookieAttributeName::Path("Path".to_string()),
@ -532,8 +587,10 @@ pub mod tests {
},
},
};
assert_eq!(query.eval(&variables, response.clone()).unwrap().unwrap(), Value::String("/accounts".to_string()));
assert_eq!(
query.eval(&variables, response.clone()).unwrap().unwrap(),
Value::String("/accounts".to_string())
);
// cookie "LSID[Secure]"
let query = Query {
@ -543,12 +600,10 @@ pub mod tests {
expr: CookiePath {
name: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "LSID".to_string(),
encoded: "LSID".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "LSID".to_string(),
encoded: "LSID".to_string(),
}],
source_info: SourceInfo::init(0, 0, 0, 0),
},
attribute: Some(CookieAttribute {
@ -559,10 +614,12 @@ pub mod tests {
},
},
};
assert_eq!(query.eval(&variables, response.clone()).unwrap().unwrap(), Value::Bool(true));
assert_eq!(
query.eval(&variables, response.clone()).unwrap().unwrap(),
Value::Bool(true)
);
// cookie "LSID[Domain]"
// cookie "LSID[Domain]"
let query = Query {
source_info: SourceInfo::init(0, 0, 0, 0),
value: QueryValue::Cookie {
@ -570,9 +627,10 @@ pub mod tests {
expr: CookiePath {
name: Template {
quotes: true,
elements: vec![
TemplateElement::String { value: "LSID".to_string(), encoded: "LSID".to_string() }
],
elements: vec![TemplateElement::String {
value: "LSID".to_string(),
encoded: "LSID".to_string(),
}],
source_info: SourceInfo::init(0, 0, 0, 0),
},
attribute: Some(CookieAttribute {
@ -607,17 +665,51 @@ pub mod tests {
cookie::CookieAttribute {
name: "HttpOnly".to_string(),
value: None,
}
},
],
};
assert_eq!(CookieAttributeName::Value("_".to_string()).eval(cookie.clone()).unwrap(), Value::String("DQAAAKEaem_vYg".to_string()));
assert_eq!(CookieAttributeName::Domain("_".to_string()).eval(cookie.clone()), None);
assert_eq!(CookieAttributeName::Path("_".to_string()).eval(cookie.clone()).unwrap(), Value::String("/accounts".to_string()));
assert_eq!(CookieAttributeName::MaxAge("_".to_string()).eval(cookie.clone()), None);
assert_eq!(CookieAttributeName::Expires("_".to_string()).eval(cookie.clone()).unwrap(), Value::String("Wed, 13 Jan 2021 22:23:01 GMT".to_string()));
assert_eq!(CookieAttributeName::Secure("_".to_string()).eval(cookie.clone()).unwrap(), Value::Bool(true));
assert_eq!(CookieAttributeName::HttpOnly("_".to_string()).eval(cookie.clone()).unwrap(), Value::Bool(true));
assert_eq!(CookieAttributeName::SameSite("_".to_string()).eval(cookie), None);
assert_eq!(
CookieAttributeName::Value("_".to_string())
.eval(cookie.clone())
.unwrap(),
Value::String("DQAAAKEaem_vYg".to_string())
);
assert_eq!(
CookieAttributeName::Domain("_".to_string()).eval(cookie.clone()),
None
);
assert_eq!(
CookieAttributeName::Path("_".to_string())
.eval(cookie.clone())
.unwrap(),
Value::String("/accounts".to_string())
);
assert_eq!(
CookieAttributeName::MaxAge("_".to_string()).eval(cookie.clone()),
None
);
assert_eq!(
CookieAttributeName::Expires("_".to_string())
.eval(cookie.clone())
.unwrap(),
Value::String("Wed, 13 Jan 2021 22:23:01 GMT".to_string())
);
assert_eq!(
CookieAttributeName::Secure("_".to_string())
.eval(cookie.clone())
.unwrap(),
Value::Bool(true)
);
assert_eq!(
CookieAttributeName::HttpOnly("_".to_string())
.eval(cookie.clone())
.unwrap(),
Value::Bool(true)
);
assert_eq!(
CookieAttributeName::SameSite("_".to_string()).eval(cookie),
None
);
}
#[test]
@ -627,15 +719,26 @@ pub mod tests {
Query {
source_info: SourceInfo::init(0, 0, 0, 0),
value: QueryValue::Body {},
}.eval(&variables, http::hello_http_response()).unwrap().unwrap(),
}
.eval(&variables, http::hello_http_response())
.unwrap()
.unwrap(),
Value::String(String::from("Hello World!"))
);
let error = Query {
source_info: SourceInfo::init(1, 1, 1, 2),
value: QueryValue::Body {},
}.eval(&variables, http::bytes_http_response()).err().unwrap();
}
.eval(&variables, http::bytes_http_response())
.err()
.unwrap();
assert_eq!(error.source_info, SourceInfo::init(1, 1, 1, 2));
assert_eq!(error.inner, RunnerError::InvalidDecoding { charset: "utf-8".to_string() });
assert_eq!(
error.inner,
RunnerError::InvalidDecoding {
charset: "utf-8".to_string()
}
);
}
#[test]
@ -649,13 +752,18 @@ pub mod tests {
};
let error = xpath_users().eval(&variables, http_response).err().unwrap();
assert_eq!(error.source_info.start, Pos { line: 1, column: 1 });
assert_eq!(error.inner, RunnerError::InvalidDecoding { charset: "utf-8".to_string() });
assert_eq!(
error.inner,
RunnerError::InvalidDecoding {
charset: "utf-8".to_string()
}
);
}
#[test]
fn test_query_xpath_error_eval() {
let variables = HashMap::new();
// xpath ^^^
// xpath ^^^
let query = Query {
source_info: SourceInfo::init(0, 0, 0, 0),
value: QueryValue::Xpath {
@ -665,17 +773,18 @@ pub mod tests {
},
expr: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "^^^".to_string(),
encoded: "^^^".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "^^^".to_string(),
encoded: "^^^".to_string(),
}],
source_info: SourceInfo::init(1, 7, 1, 10),
},
},
};
let error = query.eval(&variables, http::xml_two_users_http_response()).err().unwrap();
let error = query
.eval(&variables, http::xml_two_users_http_response())
.err()
.unwrap();
assert_eq!(error.inner, RunnerError::QueryInvalidXpathEval);
assert_eq!(error.source_info.start, Pos { line: 1, column: 7 });
}
@ -684,8 +793,20 @@ pub mod tests {
fn test_query_xpath() {
let variables = HashMap::new();
assert_eq!(xpath_users().eval(&variables, http::xml_two_users_http_response()).unwrap().unwrap(), Value::Nodeset(2));
assert_eq!(xpath_count_user_query().eval(&variables, http::xml_two_users_http_response()).unwrap().unwrap(), Value::Float(2, 0));
assert_eq!(
xpath_users()
.eval(&variables, http::xml_two_users_http_response())
.unwrap()
.unwrap(),
Value::Nodeset(2)
);
assert_eq!(
xpath_count_user_query()
.eval(&variables, http::xml_two_users_http_response())
.unwrap()
.unwrap(),
Value::Float(2, 0)
);
}
#[cfg(test)]
@ -701,12 +822,10 @@ pub mod tests {
space0: whitespace,
expr: Template {
quotes: true,
elements: vec![
TemplateElement::String {
value: "normalize-space(/html/head/meta/@charset)".to_string(),
encoded: "normalize-space(/html/head/meta/@charset)".to_string(),
}
],
elements: vec![TemplateElement::String {
value: "normalize-space(/html/head/meta/@charset)".to_string(),
encoded: "normalize-space(/html/head/meta/@charset)".to_string(),
}],
source_info: SourceInfo::init(0, 0, 0, 0),
},
},
@ -716,14 +835,20 @@ pub mod tests {
#[test]
fn test_query_xpath_with_html() {
let variables = HashMap::new();
assert_eq!(xpath_html_charset().eval(&variables, http::html_http_response()).unwrap().unwrap(), Value::String(String::from("UTF-8")));
assert_eq!(
xpath_html_charset()
.eval(&variables, http::html_http_response())
.unwrap()
.unwrap(),
Value::String(String::from("UTF-8"))
);
}
#[test]
fn test_query_jsonpath_invalid_expression() {
let variables = HashMap::new();
// jsonpath xxx
// jsonpath xxx
let jsonpath_query = Query {
source_info: SourceInfo::init(0, 0, 0, 0),
value: QueryValue::Jsonpath {
@ -732,12 +857,10 @@ pub mod tests {
source_info: SourceInfo::init(1, 9, 1, 10),
},
expr: Template {
elements: vec![
TemplateElement::String {
value: String::from("xxx"),
encoded: String::from("xxx"),
}
],
elements: vec![TemplateElement::String {
value: String::from("xxx"),
encoded: String::from("xxx"),
}],
quotes: true,
// delimiter: "".to_string(),
source_info: SourceInfo::init(1, 10, 1, 13),
@ -745,9 +868,23 @@ pub mod tests {
},
};
let error = jsonpath_query.eval(&variables, json_http_response()).err().unwrap();
assert_eq!(error.source_info.start, Pos { line: 1, column: 10 });
assert_eq!(error.inner, RunnerError::QueryInvalidJsonpathExpression { value: "xxx".to_string() });
let error = jsonpath_query
.eval(&variables, json_http_response())
.err()
.unwrap();
assert_eq!(
error.source_info.start,
Pos {
line: 1,
column: 10
}
);
assert_eq!(
error.inner,
RunnerError::QueryInvalidJsonpathExpression {
value: "xxx".to_string()
}
);
}
#[test]
@ -759,7 +896,10 @@ pub mod tests {
headers: vec![],
body: String::into_bytes(String::from("xxx")),
};
let error = jsonpath_success().eval(&variables, http_response).err().unwrap();
let error = jsonpath_success()
.eval(&variables, http_response)
.err()
.unwrap();
assert_eq!(error.source_info.start, Pos { line: 1, column: 1 });
assert_eq!(error.inner, RunnerError::QueryInvalidJson);
}
@ -774,21 +914,36 @@ pub mod tests {
body: String::into_bytes(String::from("{}")),
};
//assert_eq!(jsonpath_success().eval(http_response).unwrap(), Value::List(vec![]));
assert_eq!(jsonpath_success().eval(&variables, http_response).unwrap(), None);
assert_eq!(
jsonpath_success().eval(&variables, http_response).unwrap(),
None
);
}
#[test]
fn test_query_json() {
let variables = HashMap::new();
assert_eq!(
jsonpath_success().eval(&variables, json_http_response()).unwrap().unwrap(),
jsonpath_success()
.eval(&variables, json_http_response())
.unwrap()
.unwrap(),
Value::Bool(false)
);
assert_eq!(
jsonpath_errors().eval(&variables, json_http_response()).unwrap().unwrap(),
jsonpath_errors()
.eval(&variables, json_http_response())
.unwrap()
.unwrap(),
Value::List(vec![
Value::Object(vec![(String::from("id"), Value::String(String::from("error1")))]),
Value::Object(vec![(String::from("id"), Value::String(String::from("error2")))])
Value::Object(vec![(
String::from("id"),
Value::String(String::from("error1"))
)]),
Value::Object(vec![(
String::from("id"),
Value::String(String::from("error2"))
)])
])
);
}
@ -797,11 +952,17 @@ pub mod tests {
fn test_query_regex() {
let variables = HashMap::new();
assert_eq!(
regex_name().eval(&variables, http::hello_http_response()).unwrap().unwrap(),
regex_name()
.eval(&variables, http::hello_http_response())
.unwrap()
.unwrap(),
Value::String("World".to_string())
);
let error = regex_invalid().eval(&variables, http::hello_http_response()).err().unwrap();
let error = regex_invalid()
.eval(&variables, http::hello_http_response())
.err()
.unwrap();
assert_eq!(error.source_info, SourceInfo::init(1, 7, 1, 10));
assert_eq!(error.inner, RunnerError::InvalidRegex());
}

View File

@ -30,24 +30,21 @@ use crate::http;
use super::core::Error;
impl Request {
pub fn eval(self,
variables: &HashMap<String, Value>,
context_dir: String,
pub fn eval(
self,
variables: &HashMap<String, Value>,
context_dir: String,
) -> Result<http::Request, Error> {
let method = self.method.clone().eval();
let url = self.clone().url.eval(&variables)?;
// headers
let mut headers: Vec<http::Header> = vec![];
for header in self.clone().headers {
let name = header.key.value;
let value = header.value.eval(variables)?;
headers.push(http::Header {
name,
value,
});
headers.push(http::Header { name, value });
}
let mut querystring: Vec<http::Param> = vec![];
@ -63,12 +60,12 @@ impl Request {
let value = param.value.eval(variables)?;
form.push(http::Param { name, value });
}
// if !self.clone().form_params().is_empty() {
// headers.push(http::core::Header {
// name: String::from("Content-Type"),
// value: String::from("application/x-www-form-urlencoded"),
// });
// }
// if !self.clone().form_params().is_empty() {
// headers.push(http::core::Header {
// name: String::from("Content-Type"),
// value: String::from("application/x-www-form-urlencoded"),
// });
// }
let mut cookies = vec![];
for cookie in self.clone().cookies() {
@ -81,7 +78,7 @@ impl Request {
let bytes = match self.clone().body {
Some(body) => body.eval(variables, context_dir.clone())?,
None => vec![]
None => vec![],
};
let mut multipart = vec![];
@ -94,23 +91,27 @@ impl Request {
Some("application/x-www-form-urlencoded".to_string())
} else if !multipart.is_empty() {
Some("multipart/form-data".to_string())
} else if let Some(Body { value:Bytes::Json {..}, ..}) = self.body {
} else if let Some(Body {
value: Bytes::Json { .. },
..
}) = self.body
{
Some("application/json".to_string())
} else {
None
};
// add implicit content type
// if self.content_type().is_none() {
// if let Some(body) = self.body {
// if let Bytes::Json { .. } = body.value {
// headers.push(http::core::Header {
// name: String::from("Content-Type"),
// value: String::from("application/json"),
// });
// }
// }
// }
// if self.content_type().is_none() {
// if let Some(body) = self.body {
// if let Bytes::Json { .. } = body.value {
// headers.push(http::core::Header {
// name: String::from("Content-Type"),
// value: String::from("application/json"),
// });
// }
// }
// }
Ok(http::Request {
method,
@ -121,7 +122,7 @@ impl Request {
body: bytes,
multipart,
form,
content_type
content_type,
})
}
@ -173,13 +174,12 @@ impl Method {
// }
//}
#[cfg(test)]
mod tests {
use crate::core::common::SourceInfo;
use super::*;
use super::super::core::RunnerError;
use super::*;
pub fn whitespace() -> Whitespace {
Whitespace {
@ -212,7 +212,7 @@ mod tests {
TemplateElement::String {
value: String::from("/hello"),
encoded: String::from("/hello"),
}
},
],
quotes: false,
source_info: SourceInfo::init(0, 0, 0, 0),
@ -254,67 +254,59 @@ mod tests {
method: Method::Get,
space1: whitespace(),
url: Template {
elements: vec![
TemplateElement::String {
value: String::from("http://localhost:8000/querystring-params"),
encoded: String::from("http://localhost:8000/querystring-params"),
}
],
elements: vec![TemplateElement::String {
value: String::from("http://localhost:8000/querystring-params"),
encoded: String::from("http://localhost:8000/querystring-params"),
}],
quotes: false,
source_info: SourceInfo::init(0, 0, 0, 0),
},
line_terminator0: line_terminator.clone(),
headers: vec![],
sections: vec![
Section {
line_terminators: vec![],
space0: whitespace(),
line_terminator0: line_terminator,
value: SectionValue::QueryParams(vec![
simple_key_value(
EncodedString {
quotes: false,
value: "param1".to_string(),
encoded: "param1".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
Template {
quotes: false,
elements: vec![
TemplateElement::Expression(Expr {
space0: whitespace(),
variable: Variable {
name: String::from("param1"),
source_info: SourceInfo::init(1, 7, 1, 15),
},
space1: whitespace(),
})
],
source_info: SourceInfo::init(0, 0, 0, 0),
},
),
simple_key_value(
EncodedString {
quotes: false,
value: "param2".to_string(),
encoded: "param2".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
Template {
quotes: false,
elements: vec![
TemplateElement::String {
value: "a b".to_string(),
encoded: "a b".to_string(),
}
],
source_info: SourceInfo::init(0, 0, 0, 0),
},
)
]),
source_info: SourceInfo::init(0, 0, 0, 0),
},
],
sections: vec![Section {
line_terminators: vec![],
space0: whitespace(),
line_terminator0: line_terminator,
value: SectionValue::QueryParams(vec![
simple_key_value(
EncodedString {
quotes: false,
value: "param1".to_string(),
encoded: "param1".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
Template {
quotes: false,
elements: vec![TemplateElement::Expression(Expr {
space0: whitespace(),
variable: Variable {
name: String::from("param1"),
source_info: SourceInfo::init(1, 7, 1, 15),
},
space1: whitespace(),
})],
source_info: SourceInfo::init(0, 0, 0, 0),
},
),
simple_key_value(
EncodedString {
quotes: false,
value: "param2".to_string(),
encoded: "param2".to_string(),
source_info: SourceInfo::init(0, 0, 0, 0),
},
Template {
quotes: false,
elements: vec![TemplateElement::String {
value: "a b".to_string(),
encoded: "a b".to_string(),
}],
source_info: SourceInfo::init(0, 0, 0, 0),
},
),
]),
source_info: SourceInfo::init(0, 0, 0, 0),
}],
body: None,
source_info: SourceInfo::init(0, 0, 0, 0),
}
@ -323,24 +315,42 @@ mod tests {
#[test]
pub fn test_error_variable() {
let variables = HashMap::new();
let error = hello_request().eval(&variables, "current_dir".to_string()).err().unwrap();
let error = hello_request()
.eval(&variables, "current_dir".to_string())
.err()
.unwrap();
assert_eq!(error.source_info, SourceInfo::init(1, 7, 1, 15));
assert_eq!(error.inner, RunnerError::TemplateVariableNotDefined { name: String::from("base_url") });
assert_eq!(
error.inner,
RunnerError::TemplateVariableNotDefined {
name: String::from("base_url")
}
);
}
#[test]
pub fn test_hello_request() {
let mut variables = HashMap::new();
variables.insert(String::from("base_url"), Value::String(String::from("http://localhost:8000")));
let http_request = hello_request().eval(&variables, "current_dir".to_string()).unwrap();
variables.insert(
String::from("base_url"),
Value::String(String::from("http://localhost:8000")),
);
let http_request = hello_request()
.eval(&variables, "current_dir".to_string())
.unwrap();
assert_eq!(http_request, http::hello_http_request());
}
#[test]
pub fn test_query_request() {
let mut variables = HashMap::new();
variables.insert(String::from("param1"), Value::String(String::from("value1")));
let http_request = query_request().eval(&variables, "current_dir".to_string()).unwrap();
variables.insert(
String::from("param1"),
Value::String(String::from("value1")),
);
let http_request = query_request()
.eval(&variables, "current_dir".to_string())
.unwrap();
assert_eq!(http_request, http::query_http_request());
}
}

View File

@ -17,20 +17,22 @@
*/
use std::collections::HashMap;
use crate::core::common::{Pos, SourceInfo};
use crate::core::common::Value;
use crate::core::common::{Pos, SourceInfo};
use crate::http;
use crate::runner::core::RunnerError;
use super::core::*;
use super::core::Error;
use super::super::core::ast::*;
use super::core::Error;
use super::core::*;
impl Response {
pub fn eval_asserts(self, variables: &HashMap<String, Value>, http_response: http::Response, context_dir: String) -> Vec<AssertResult> {
pub fn eval_asserts(
self,
variables: &HashMap<String, Value>,
http_response: http::Response,
context_dir: String,
) -> Vec<AssertResult> {
let mut asserts = vec![];
let version = self.clone().version;
@ -77,11 +79,17 @@ impl Response {
source_info: header.value.clone().source_info,
});
} else {
// failure by default
// expected value not found in the list
// actual is therefore the full list
let mut actual = format!("[{}]", actuals.iter().map(|v| format!("\"{}\"", v)).collect::<Vec<String>>().join(", "));
let mut actual = format!(
"[{}]",
actuals
.iter()
.map(|v| format!("\"{}\"", v))
.collect::<Vec<String>>()
.join(", ")
);
for value in actuals {
if value == expected {
actual = value;
@ -164,12 +172,19 @@ impl Response {
source_info: value.source_info,
})
}
Bytes::Base64 { value, space0, space1, .. } =>
asserts.push(AssertResult::Body {
actual: Ok(Value::Bytes(http_response.body.clone())),
expected: Ok(Value::Bytes(value)),
source_info: SourceInfo { start: space0.source_info.end, end: space1.source_info.start },
}),
Bytes::Base64 {
value,
space0,
space1,
..
} => asserts.push(AssertResult::Body {
actual: Ok(Value::Bytes(http_response.body.clone())),
expected: Ok(Value::Bytes(value)),
source_info: SourceInfo {
start: space0.source_info.end,
end: space1.source_info.start,
},
}),
Bytes::File { .. } => {
let expected = match body.clone().eval(variables, context_dir) {
Ok(bytes) => Ok(Value::Bytes(bytes)),
@ -192,7 +207,11 @@ impl Response {
asserts
}
pub fn eval_captures(self, http_response: http::Response, variables: &HashMap<String, Value>) -> Result<Vec<CaptureResult>, Error> {
pub fn eval_captures(
self,
http_response: http::Response,
variables: &HashMap<String, Value>,
) -> Result<Vec<CaptureResult>, Error> {
let mut captures = vec![];
for capture in self.captures() {
let capture_result = capture.eval(variables, http_response.clone())?;
@ -202,7 +221,6 @@ impl Response {
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -223,9 +241,15 @@ mod tests {
// HTTP/1.1 200
Response {
line_terminators: vec![],
version: Version { value: VersionValue::Version1, source_info: SourceInfo::init(2, 6, 2, 9) },
version: Version {
value: VersionValue::Version1,
source_info: SourceInfo::init(2, 6, 2, 9),
},
space0: whitespace.clone(),
status: Status { value: 200, source_info: SourceInfo::init(2, 10, 2, 13) },
status: Status {
value: 200,
source_info: SourceInfo::init(2, 10, 2, 13),
},
space1: whitespace.clone(),
line_terminator0: line_terminator.clone(),
headers: vec![],
@ -234,33 +258,32 @@ mod tests {
line_terminators: vec![],
space0: whitespace.clone(),
line_terminator0: line_terminator.clone(),
value: SectionValue::Asserts(vec![
assert::tests::assert_count_user(),
]),
value: SectionValue::Asserts(vec![assert::tests::assert_count_user()]),
source_info: SourceInfo::init(0, 0, 0, 0),
},
Section {
line_terminators: vec![],
space0: whitespace,
line_terminator0: line_terminator,
value: SectionValue::Captures(vec![
capture::tests::user_count_capture(),
]),
value: SectionValue::Captures(vec![capture::tests::user_count_capture()]),
source_info: SourceInfo::init(0, 0, 0, 0),
}
},
],
body: None,
source_info: SourceInfo::init(0, 0, 0, 0),
}
}
#[test]
pub fn test_eval_asserts() {
let variables = HashMap::new();
let context_dir = "undefined".to_string();
assert_eq!(
user_response().eval_asserts(&variables, http::xml_two_users_http_response(), context_dir),
user_response().eval_asserts(
&variables,
http::xml_two_users_http_response(),
context_dir
),
vec![
AssertResult::Version {
actual: String::from("1.0"),
@ -293,13 +316,13 @@ mod tests {
pub fn test_eval_captures() {
let variables = HashMap::new();
assert_eq!(
user_response().eval_captures(http::xml_two_users_http_response(), &variables).unwrap(),
vec![
CaptureResult {
name: "UserCount".to_string(),
value: Value::Float(2, 0),
}
]
user_response()
.eval_captures(http::xml_two_users_http_response(), &variables)
.unwrap(),
vec![CaptureResult {
name: "UserCount".to_string(),
value: Value::Float(2, 0),
}]
);
}
}

View File

@ -41,17 +41,30 @@ impl Template {
impl TemplateElement {
pub fn eval(self, variables: &HashMap<String, Value>) -> Result<String, Error> {
match self {
TemplateElement::String { value, .. } => { Ok(value) }
TemplateElement::Expression(Expr { variable: Variable { name, source_info }, .. }) => {
match variables.get(&name as &str) {
Some(value) => if value.is_renderable() {
TemplateElement::String { value, .. } => Ok(value),
TemplateElement::Expression(Expr {
variable: Variable { name, source_info },
..
}) => match variables.get(&name as &str) {
Some(value) => {
if value.is_renderable() {
Ok(value.clone().to_string())
} else {
Err(Error { source_info, inner: RunnerError::UnrenderableVariable { value: value.to_string() }, assert: false })
},
_ => Err(Error { source_info, inner: RunnerError::TemplateVariableNotDefined { name }, assert: false }),
Err(Error {
source_info,
inner: RunnerError::UnrenderableVariable {
value: value.to_string(),
},
assert: false,
})
}
}
}
_ => Err(Error {
source_info,
inner: RunnerError::TemplateVariableNotDefined { name },
assert: false,
}),
},
}
}
}
@ -65,7 +78,6 @@ impl Value {
}
}
#[cfg(test)]
mod tests {
use crate::core::common::SourceInfo;
@ -75,32 +87,59 @@ mod tests {
fn template_element_expression() -> TemplateElement {
// {{name}}
TemplateElement::Expression(Expr {
space0: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 3, 1, 3) },
variable: Variable { name: "name".to_string(), source_info: SourceInfo::init(1, 3, 1, 7) },
space1: Whitespace { value: "".to_string(), source_info: SourceInfo::init(1, 7, 1, 7) },
space0: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 3, 1, 3),
},
variable: Variable {
name: "name".to_string(),
source_info: SourceInfo::init(1, 3, 1, 7),
},
space1: Whitespace {
value: "".to_string(),
source_info: SourceInfo::init(1, 7, 1, 7),
},
})
}
#[test]
fn test_template_element() {
let variables = HashMap::new();
assert_eq!(TemplateElement::String { value: "World".to_string(), encoded: "World".to_string() }.eval(&variables).unwrap(),
"World".to_string()
assert_eq!(
TemplateElement::String {
value: "World".to_string(),
encoded: "World".to_string()
}
.eval(&variables)
.unwrap(),
"World".to_string()
);
let mut variables = HashMap::new();
variables.insert("name".to_string(), Value::String("World".to_string()));
assert_eq!(template_element_expression().eval(&variables).unwrap(), "World".to_string());
assert_eq!(
template_element_expression().eval(&variables).unwrap(),
"World".to_string()
);
}
#[test]
fn test_template_element_error() {
let mut variables = HashMap::new();
variables.insert("name".to_string(), Value::List(vec![Value::Integer(1), Value::Integer(2)]));
let error = template_element_expression().eval(&variables).err().unwrap();
variables.insert(
"name".to_string(),
Value::List(vec![Value::Integer(1), Value::Integer(2)]),
);
let error = template_element_expression()
.eval(&variables)
.err()
.unwrap();
assert_eq!(error.source_info, SourceInfo::init(1, 3, 1, 7));
assert_eq!(error.inner, RunnerError::UnrenderableVariable { value: "[1,2]".to_string() });
assert_eq!(
error.inner,
RunnerError::UnrenderableVariable {
value: "[1,2]".to_string()
}
);
}
}

View File

@ -33,12 +33,14 @@ pub enum XpathError {
pub fn eval_xml(xml: String, expr: String) -> Result<Value, XpathError> {
let parser = libxml::parser::Parser::default();
match parser.parse_string(xml) {
Ok(doc) => if doc.get_root_element() == None {
Err(XpathError::InvalidXML {})
} else {
eval(doc, expr)
},
Err(_) => Err(XpathError::InvalidXML {})
Ok(doc) => {
if doc.get_root_element() == None {
Err(XpathError::InvalidXML {})
} else {
eval(doc, expr)
}
}
Err(_) => Err(XpathError::InvalidXML {}),
}
}
@ -54,29 +56,30 @@ pub fn eval_html(html: String, expr: String) -> Result<Value, XpathError> {
eval(doc, expr)
}
}
Err(_) => Err(XpathError::InvalidHtml {})
Err(_) => Err(XpathError::InvalidHtml {}),
}
}
pub fn eval(doc: libxml::tree::Document, expr: String) -> Result<Value, XpathError> {
let context = match libxml::xpath::Context::new(&doc) {
Ok(context) => context,
_ => panic!("error setting context in xpath module")
_ => panic!("error setting context in xpath module"),
};
unsafe {
libxml::bindings::initGenericErrorDefaultFunc(&mut None);
}
let result = match context.evaluate(expr.as_str()) {
Ok(object) => {
object
}
Err(_) => return Err(XpathError::Eval {})
Ok(object) => object,
Err(_) => return Err(XpathError::Eval {}),
};
match unsafe { *result.ptr }.type_ {
libxml::bindings::xmlXPathObjectType_XPATH_NUMBER => Ok(Value::from_f64(unsafe { *result.ptr }.floatval)),
libxml::bindings::xmlXPathObjectType_XPATH_BOOLEAN =>
Ok(Value::Bool(unsafe { *result.ptr }.boolval != 0)),
libxml::bindings::xmlXPathObjectType_XPATH_NUMBER => {
Ok(Value::from_f64(unsafe { *result.ptr }.floatval))
}
libxml::bindings::xmlXPathObjectType_XPATH_BOOLEAN => {
Ok(Value::Bool(unsafe { *result.ptr }.boolval != 0))
}
libxml::bindings::xmlXPathObjectType_XPATH_STRING => {
// TO BE CLEANED
let c_s = unsafe { *result.ptr }.stringval;
@ -87,27 +90,28 @@ pub fn eval(doc: libxml::tree::Document, expr: String) -> Result<Value, XpathErr
Ok(Value::String(s))
}
libxml::bindings::xmlXPathObjectType_XPATH_NODESET => Ok(Value::Nodeset(result.get_number_of_nodes())),
_ => {
Err(XpathError::Unsupported {})
libxml::bindings::xmlXPathObjectType_XPATH_NODESET => {
Ok(Value::Nodeset(result.get_number_of_nodes()))
}
_ => Err(XpathError::Unsupported {}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_xml() {
let xml = String::from(r#"<?xml version="1.0" encoding="utf-8"?>
let xml = String::from(
r#"<?xml version="1.0" encoding="utf-8"?>
<food>
<banana type="fruit" price="1.1"/>
<apple type="fruit"/>
<beef type="meat"/>
</food>
"#);
"#,
);
let xpath = String::from("count(//food/*)");
assert_eq!(eval_xml(xml.clone(), xpath).unwrap(), Value::from_f64(3.0));
@ -121,46 +125,66 @@ mod tests {
assert_eq!(eval_xml(xml.clone(), xpath).unwrap(), Value::from_f64(1.1));
}
#[test]
fn test_error_eval() {
assert_eq!(eval_xml(String::from("<a/>"), String::from("^^^")).err().unwrap(), XpathError::Eval {});
assert_eq!(eval_xml(String::from("<a/>"), String::from("//")).err().unwrap(), XpathError::Eval {});
assert_eq!(
eval_xml(String::from("<a/>"), String::from("^^^"))
.err()
.unwrap(),
XpathError::Eval {}
);
assert_eq!(
eval_xml(String::from("<a/>"), String::from("//"))
.err()
.unwrap(),
XpathError::Eval {}
);
// assert_eq!(1,2);
}
// TBC!!!
// Invalid XML not detected at parsing??? => goes into an eval error
// Invalid XML not detected at parsing??? => goes into an eval error
#[test]
fn test_invalid_xml() {
assert_eq!(eval_xml(String::from("??"), String::from("//person")).err().unwrap(), XpathError::InvalidXML);
assert_eq!(
eval_xml(String::from("??"), String::from("//person"))
.err()
.unwrap(),
XpathError::InvalidXML
);
}
#[test]
fn test_cafe() {
assert_eq!(eval_xml(
String::from("<data>café</data>"), String::from("normalize-space(//data)")).unwrap(),
Value::String(String::from("café"))
assert_eq!(
eval_xml(
String::from("<data>café</data>"),
String::from("normalize-space(//data)")
)
.unwrap(),
Value::String(String::from("café"))
);
}
#[test]
fn test_html() {
let html = String::from(r#"<html>
let html = String::from(
r#"<html>
<head>
<meta charset="UTF-8"\>
</head>
<body>
<br>
</body>
</html>"#);
</html>"#,
);
let xpath = String::from("normalize-space(/html/head/meta/@charset)");
assert_eq!(eval_html(html.clone(), xpath).unwrap(), Value::String(String::from("UTF-8")));
assert_eq!(
eval_html(html.clone(), xpath).unwrap(),
Value::String(String::from("UTF-8"))
);
}
#[test]
fn test_bug() {
let html = String::from(r#"<html></html>"#);

View File

@ -18,26 +18,25 @@
extern crate hurl;
extern crate proptest;
use std::{fs};
use std::fs;
use proptest::prelude::*;
use proptest::prelude::prop::test_runner::TestRunner;
use proptest::prelude::*;
use hurl::core::ast::{Expr, Template, TemplateElement, Variable, Whitespace};
use hurl::core::common::SourceInfo;
use hurl::core::json;
use hurl::format::token::*;
use hurl::core::ast::{Template, TemplateElement, Expr, Variable, Whitespace};
use hurl::core::common::SourceInfo;
fn whitespace() -> BoxedStrategy<String> {
prop_oneof![
Just("".to_string()),
Just(" ".to_string()),
Just(" ".to_string()),
].boxed()
]
.boxed()
}
// region strategy scalar/leaves
fn value_number() -> BoxedStrategy<json::Value> {
@ -46,136 +45,210 @@ fn value_number() -> BoxedStrategy<json::Value> {
Just(json::Value::Number("1".to_string())),
Just(json::Value::Number("1.33".to_string())),
Just(json::Value::Number("-100".to_string()))
].boxed()
]
.boxed()
}
fn value_boolean() -> BoxedStrategy<json::Value> {
prop_oneof![
Just(json::Value::Boolean(true)),
Just(json::Value::Boolean(false)),
].boxed()
]
.boxed()
}
fn value_string() -> BoxedStrategy<json::Value> {
let source_info = SourceInfo::init(0, 0, 0, 0);
let variable = Variable { name: "name".to_string(), source_info: source_info.clone() };
let variable = Variable {
name: "name".to_string(),
source_info: source_info.clone(),
};
prop_oneof![
Just(json::Value::String(Template {
elements: vec![
],
quotes: true,
source_info:source_info.clone()
})),
Just(json::Value::String(Template {
elements: vec![
TemplateElement::String {
encoded: "Hello".to_string(),
value: "Hello".to_string(),
}
],
elements: vec![],
quotes: true,
source_info: source_info.clone()
})),
Just(json::Value::String(Template {
Just(json::Value::String(Template {
elements: vec![TemplateElement::String {
encoded: "Hello".to_string(),
value: "Hello".to_string(),
}],
quotes: true,
source_info: source_info.clone()
})),
Just(json::Value::String(Template {
elements: vec![
TemplateElement::String {
encoded: "Hello\\u0020 ".to_string(),
value: "Hello ".to_string(),
},
TemplateElement::Expression(
Expr {
space0: Whitespace {
value: "".to_string(),
source_info: source_info.clone()
},
variable,
space1: Whitespace {
value: "".to_string(),
source_info: source_info.clone()
},
}
)
TemplateElement::Expression(Expr {
space0: Whitespace {
value: "".to_string(),
source_info: source_info.clone()
},
variable,
space1: Whitespace {
value: "".to_string(),
source_info: source_info.clone()
},
})
],
quotes: true,
source_info
})),
].boxed()
]
.boxed()
}
// endregion
// region strategy value
fn value() -> BoxedStrategy<json::Value> {
let leaf = prop_oneof![
value_boolean(),
value_string(),
value_number(),
];
let leaf = prop_oneof![value_boolean(), value_string(), value_number(),];
leaf.prop_recursive(
8, // 8 levels deep
8, // 8 levels deep
256, // Shoot for maximum size of 256 nodes
10, // We put up to 10 items per collection
10, // We put up to 10 items per collection
|value| {
prop_oneof![
// Lists
(whitespace()).prop_map(|space0| json::Value::List{ space0, elements: vec![]}),
(whitespace(), whitespace(), value.clone()).prop_map(|(space0, space1, value)| json::Value::List{ space0, elements: vec![
json::ListElement { space0: "".to_string(), value, space1 }
]}),
(whitespace(), whitespace(), value_number(), whitespace(), whitespace(), value_number()).prop_map(|(space00, space01, value0, space10, space11, value1)| json::Value::List{ space0: space00, elements: vec![
json::ListElement { space0: "".to_string(), value: value0, space1: space01 },
json::ListElement { space0: space10, value: value1, space1: space11 },
]}),
(whitespace(), whitespace(), value_boolean(), whitespace(), whitespace(), value_boolean()).prop_map(|(space00, space01, value0, space10, space11, value1)| json::Value::List{ space0: space00, elements: vec![
json::ListElement { space0: "".to_string(), value: value0, space1: space01 },
json::ListElement { space0: space10, value: value1, space1: space11 },
]}),
(whitespace(), whitespace(), value_string(), whitespace(), whitespace(), value_string()).prop_map(|(space00, space01, value0, space10, space11, value1)| json::Value::List{ space0: space00, elements: vec![
json::ListElement { space0: "".to_string(), value: value0, space1: space01 },
json::ListElement { space0: space10, value: value1, space1: space11 },
]}),
(whitespace()).prop_map(|space0| json::Value::List {
space0,
elements: vec![]
}),
(whitespace(), whitespace(), value.clone()).prop_map(|(space0, space1, value)| {
json::Value::List {
space0,
elements: vec![json::ListElement {
space0: "".to_string(),
value,
space1,
}],
}
}),
(
whitespace(),
whitespace(),
value_number(),
whitespace(),
whitespace(),
value_number()
)
.prop_map(
|(space00, space01, value0, space10, space11, value1)| json::Value::List {
space0: space00,
elements: vec![
json::ListElement {
space0: "".to_string(),
value: value0,
space1: space01
},
json::ListElement {
space0: space10,
value: value1,
space1: space11
},
]
}
),
(
whitespace(),
whitespace(),
value_boolean(),
whitespace(),
whitespace(),
value_boolean()
)
.prop_map(
|(space00, space01, value0, space10, space11, value1)| json::Value::List {
space0: space00,
elements: vec![
json::ListElement {
space0: "".to_string(),
value: value0,
space1: space01
},
json::ListElement {
space0: space10,
value: value1,
space1: space11
},
]
}
),
(
whitespace(),
whitespace(),
value_string(),
whitespace(),
whitespace(),
value_string()
)
.prop_map(
|(space00, space01, value0, space10, space11, value1)| json::Value::List {
space0: space00,
elements: vec![
json::ListElement {
space0: "".to_string(),
value: value0,
space1: space01
},
json::ListElement {
space0: space10,
value: value1,
space1: space11
},
]
}
),
// Object
(whitespace()).prop_map(|space0| json::Value::Object{ space0, elements: vec![]}),
(whitespace(), whitespace(), whitespace(), value, whitespace()).prop_map(|(space0, space1, space2, value, space3)| json::Value::Object{ space0, elements: vec![
json::ObjectElement {
space0: "".to_string(),
name: "key1".to_string(),
space1,
space2,
value,
space3 }
]}),
(whitespace()).prop_map(|space0| json::Value::Object {
space0,
elements: vec![]
}),
(
whitespace(),
whitespace(),
whitespace(),
value,
whitespace()
)
.prop_map(|(space0, space1, space2, value, space3)| {
json::Value::Object {
space0,
elements: vec![json::ObjectElement {
space0: "".to_string(),
name: "key1".to_string(),
space1,
space2,
value,
space3,
}],
}
}),
]
}).boxed()
},
)
.boxed()
}
// endregion
// region test-echo
fn format_token(token: Token) -> String {
match token {
Token::Whitespace(s) |
Token::Number(s) |
Token::String(s) |
Token::Keyword(s) |
Token::Quote(s) |
Token::QueryType(s) |
Token::CodeVariable(s) |
Token::CodeDelimiter(s) => s,
Token::Whitespace(s)
| Token::Number(s)
| Token::String(s)
| Token::Keyword(s)
| Token::Quote(s)
| Token::QueryType(s)
| Token::CodeVariable(s)
| Token::CodeDelimiter(s) => s,
_ => panic!("invalid token {:?}", token),
}
}
@ -183,44 +256,43 @@ fn format_token(token: Token) -> String {
fn format_value(value: json::Value) -> String {
let tokens = value.tokenize();
//eprintln!("{:?}", tokens);
tokens.iter().map(|t| format_token(t.clone())).collect::<Vec<String>>().join("")
tokens
.iter()
.map(|t| format_token(t.clone()))
.collect::<Vec<String>>()
.join("")
}
#[test]
fn test_echo() {
let mut runner = TestRunner::default();
//let mut runner = TestRunner::new(ProptestConfig::with_cases(10000));
runner.run(&value(), |value| {
//eprintln!("value={:#?}", value);
let s = format_value(value);
eprintln!("s={}", s);
let mut reader = hurl::parser::reader::Reader::init(s.as_str());
let parsed_value = hurl::parser::json::parse(&mut reader).unwrap();
assert_eq!(format_value(parsed_value), s);
runner
.run(&value(), |value| {
//eprintln!("value={:#?}", value);
let s = format_value(value);
eprintln!("s={}", s);
let mut reader = hurl::parser::reader::Reader::init(s.as_str());
let parsed_value = hurl::parser::json::parse(&mut reader).unwrap();
assert_eq!(format_value(parsed_value), s);
Ok(())
}).unwrap();
Ok(())
})
.unwrap();
//assert_eq!(1,2);
}
#[test]
fn test_parse_files() {
let paths = fs::read_dir("tests/json").unwrap();
for p in paths {
let path = p.unwrap().path();
println!("parsing json file {}", path.display());
let s = fs::read_to_string(path).expect("Something went wrong reading the file");
let s = fs::read_to_string(path).expect("Something went wrong reading the file");
let mut reader = hurl::parser::reader::Reader::init(s.as_str());
let parsed_value = hurl::parser::json::parse(&mut reader).unwrap();
assert_eq!(format_value(parsed_value), s);
}
}

View File

@ -17,7 +17,7 @@
*/
extern crate hurl;
use std::fs::{read_to_string};
use std::fs::read_to_string;
use serde_json;
use serde_json::json;
@ -26,40 +26,64 @@ use hurl::jsonpath;
fn test_ok(s: &str, value: serde_json::Value) -> Vec<serde_json::Value> {
return match jsonpath::parser::parse::parse(s) {
Ok(expr) => {
expr.eval(value)
}
Ok(expr) => expr.eval(value),
Err(e) => panic!("{:?}", e),
};
}
#[test]
fn test_bookstore_path() {
let no_result : Vec<serde_json::Value> = vec![];
let no_result: Vec<serde_json::Value> = vec![];
let s = read_to_string("tests/bookstore.json").expect("could not read string from file");
let value: serde_json::Value = serde_json::from_str(s.as_str()).expect("could not parse json file");
let value: serde_json::Value =
serde_json::from_str(s.as_str()).expect("could not parse json file");
assert_eq!(test_ok("$.store.book[0].title", value.clone()), vec![json!("Sayings of the Century")]);
assert_eq!(test_ok("$.store.book[0].title", value.clone()), vec![json!("Sayings of the Century")]);
assert_eq!(test_ok("$.store.book[?(@.price<10)].title", value.clone()), vec![json!("Sayings of the Century"), json!("Moby Dick")]);
assert_eq!(test_ok("$.store.book[?(@.price < 10)].title", value.clone()), vec![json!("Sayings of the Century"), json!("Moby Dick")]);
assert_eq!(
test_ok("$.store.book[0].title", value.clone()),
vec![json!("Sayings of the Century")]
);
assert_eq!(
test_ok("$.store.book[0].title", value.clone()),
vec![json!("Sayings of the Century")]
);
assert_eq!(
test_ok("$.store.book[?(@.price<10)].title", value.clone()),
vec![json!("Sayings of the Century"), json!("Moby Dick")]
);
assert_eq!(
test_ok("$.store.book[?(@.price < 10)].title", value.clone()),
vec![json!("Sayings of the Century"), json!("Moby Dick")]
);
assert_eq!(test_ok("$..book[2]", value.clone()), vec![json!({
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
})]);
assert_eq!(test_ok("$..author", value.clone()), vec![json!("Nigel Rees"), json!("Evelyn Waugh"), json!("Herman Melville"), json!("J. R. R. Tolkien")] );
assert_eq!(test_ok("$.store.book[?(@.price>100)]", value.clone()), no_result.clone());
assert_eq!(
test_ok("$..book[2]", value.clone()),
vec![json!({
"category": "fiction",
"author": "Herman Melville",
"title": "Moby Dick",
"isbn": "0-553-21311-3",
"price": 8.99
})]
);
assert_eq!(
test_ok("$..author", value.clone()),
vec![
json!("Nigel Rees"),
json!("Evelyn Waugh"),
json!("Herman Melville"),
json!("J. R. R. Tolkien")
]
);
assert_eq!(
test_ok("$.store.book[?(@.price>100)]", value.clone()),
no_result.clone()
);
}
#[test]
fn test_array() {
let array = json!([0,1,2,3]);
assert_eq!(test_ok("$[2]", array.clone()), vec![json!(2)] );
let array = json!([0, 1, 2, 3]);
assert_eq!(test_ok("$[2]", array.clone()), vec![json!(2)]);
let array = json!([{"name": "Bob"},{"name": "Bill"}]);
assert_eq!(test_ok("$[0].name", array.clone()), vec![json!("Bob")]);

View File

@ -3,10 +3,8 @@ use std::io::prelude::*;
use curl::easy::Easy;
use hurl::http::*;
use server::Server;
macro_rules! t {
@ -27,7 +25,6 @@ pub fn new_header(name: &str, value: &str) -> Header {
}
}
#[test]
fn get_easy() {
let s = Server::new();
@ -46,16 +43,17 @@ fn get_easy() {
handle.url(&s.url("/hello")).unwrap();
{
let mut transfer = handle.transfer();
transfer.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
}).unwrap();
transfer
.write_function(|new_data| {
data.extend_from_slice(new_data);
Ok(new_data.len())
})
.unwrap();
transfer.perform().unwrap();
}
assert_eq!(data, b"Hello World!");
}
fn default_client() -> Client {
let options = ClientOptions {
follow_location: false,
@ -83,7 +81,6 @@ fn default_get_request(url: String) -> Request {
}
}
// region basic
#[test]
@ -96,8 +93,14 @@ fn test_hello() {
assert_eq!(response.body, b"Hello World!".to_vec());
assert_eq!(response.headers.len(), 4);
assert!(response.headers.contains(&Header { name: "Content-Length".to_string(), value: "12".to_string() }));
assert!(response.headers.contains(&Header { name: "Content-Type".to_string(), value: "text/html; charset=utf-8".to_string() }));
assert!(response.headers.contains(&Header {
name: "Content-Length".to_string(),
value: "12".to_string()
}));
assert!(response.headers.contains(&Header {
name: "Content-Type".to_string(),
value: "text/html; charset=utf-8".to_string()
}));
assert_eq!(response.get_header_values("Date".to_string()).len(), 1);
}
@ -131,9 +134,18 @@ fn test_patch() {
method: Method::Patch,
url: "http://localhost:8000/patch/file.txt".to_string(),
headers: vec![
Header { name: "Host".to_string(), value: "www.example.com".to_string() },
Header { name: "Content-Type".to_string(), value: "application/example".to_string() },
Header { name: "If-Match".to_string(), value: "\"e0023aa4e\"".to_string() },
Header {
name: "Host".to_string(),
value: "www.example.com".to_string(),
},
Header {
name: "Content-Type".to_string(),
value: "application/example".to_string(),
},
Header {
name: "If-Match".to_string(),
value: "\"e0023aa4e\"".to_string(),
},
],
querystring: vec![],
form: vec![],
@ -188,10 +200,22 @@ fn test_querystring_params() {
url: "http://localhost:8000/querystring-params".to_string(),
headers: vec![],
querystring: vec![
Param { name: "param1".to_string(), value: "value1".to_string() },
Param { name: "param2".to_string(), value: "".to_string() },
Param { name: "param3".to_string(), value: "a=b".to_string() },
Param { name: "param4".to_string(), value: "1,2,3".to_string() }
Param {
name: "param1".to_string(),
value: "value1".to_string(),
},
Param {
name: "param2".to_string(),
value: "".to_string(),
},
Param {
name: "param3".to_string(),
value: "a=b".to_string(),
},
Param {
name: "param4".to_string(),
value: "1,2,3".to_string(),
},
],
form: vec![],
multipart: vec![],
@ -217,10 +241,22 @@ fn test_form_params() {
headers: vec![],
querystring: vec![],
form: vec![
Param { name: "param1".to_string(), value: "value1".to_string() },
Param { name: "param2".to_string(), value: "".to_string() },
Param { name: "param3".to_string(), value: "a=b".to_string() },
Param { name: "param4".to_string(), value: "a%3db".to_string() }
Param {
name: "param1".to_string(),
value: "value1".to_string(),
},
Param {
name: "param2".to_string(),
value: "".to_string(),
},
Param {
name: "param3".to_string(),
value: "a=b".to_string(),
},
Param {
name: "param4".to_string(),
value: "a%3db".to_string(),
},
],
multipart: vec![],
cookies: vec![],
@ -231,7 +267,6 @@ fn test_form_params() {
assert_eq!(response.status, 200);
assert!(response.body.is_empty());
// make sure you can reuse client for other request
let request = default_get_request("http://localhost:8000/hello".to_string());
let response = client.execute(&request, 0).unwrap();
@ -250,8 +285,13 @@ fn test_follow_location() {
let mut client = default_client();
let response = client.execute(&request, 0).unwrap();
assert_eq!(response.status, 302);
assert_eq!(response.get_header_values("Location".to_string()).get(0).unwrap(),
"http://localhost:8000/redirected");
assert_eq!(
response
.get_header_values("Location".to_string())
.get(0)
.unwrap(),
"http://localhost:8000/redirected"
);
assert_eq!(client.redirect_count, 0);
let options = ClientOptions {
@ -266,10 +306,15 @@ fn test_follow_location() {
let mut client = Client::init(options);
let response = client.execute(&request, 0).unwrap();
assert_eq!(response.status, 200);
assert_eq!(response.get_header_values("Content-Length".to_string()).get(0).unwrap(), "0");
assert_eq!(
response
.get_header_values("Content-Length".to_string())
.get(0)
.unwrap(),
"0"
);
assert_eq!(client.redirect_count, 1);
// make sure that the redirect count is reset to 0
let request = default_get_request("http://localhost:8000/hello".to_string());
let response = client.execute(&request, 0).unwrap();
@ -278,7 +323,6 @@ fn test_follow_location() {
assert_eq!(client.redirect_count, 0);
}
#[test]
fn test_max_redirect() {
let options = ClientOptions {
@ -340,7 +384,6 @@ fn test_multipart_form_data() {
cookies: vec![],
body: vec![],
content_type: Some("multipart/form-data".to_string()),
};
let response = client.execute(&request, 0).unwrap();
assert_eq!(response.status, 200);
@ -396,7 +439,6 @@ fn test_error_fail_to_connect() {
let error = client.execute(&request, 0).err().unwrap();
assert_eq!(error, HttpError::FailToConnect);
let options = ClientOptions {
follow_location: false,
max_redirect: None,
@ -412,7 +454,6 @@ fn test_error_fail_to_connect() {
assert_eq!(error, HttpError::FailToConnect);
}
#[test]
fn test_error_could_not_resolve_proxy_name() {
let options = ClientOptions {
@ -444,9 +485,10 @@ fn test_cookie() {
querystring: vec![],
form: vec![],
multipart: vec![],
cookies: vec![
RequestCookie { name: "cookie1".to_string(), value: "valueA".to_string() }
],
cookies: vec![RequestCookie {
name: "cookie1".to_string(),
value: "valueA".to_string(),
}],
body: vec![],
content_type: None,
};
@ -467,7 +509,6 @@ fn test_cookie() {
assert_eq!(response.status, 200);
assert!(response.body.is_empty());
// For the time-being setting a cookie on a request
// update the cookie store as well
// The same cookie does not need to be set explicitly on further requests
@ -480,34 +521,40 @@ fn test_cookie() {
#[test]
fn test_cookie_storage() {
let mut client = default_client();
let request = default_get_request("http://localhost:8000/cookies/set-session-cookie2-valueA".to_string());
let request =
default_get_request("http://localhost:8000/cookies/set-session-cookie2-valueA".to_string());
let response = client.execute(&request, 0).unwrap();
assert_eq!(response.status, 200);
assert!(response.body.is_empty());
let cookie_store = client.get_cookie_storage();
assert_eq!(cookie_store.get(0).unwrap().clone(), Cookie {
domain: "localhost".to_string(),
include_subdomain: "FALSE".to_string(),
path: "/".to_string(),
https: "FALSE".to_string(),
expires: "0".to_string(),
name: "cookie2".to_string(),
value: "valueA".to_string(),
});
assert_eq!(
cookie_store.get(0).unwrap().clone(),
Cookie {
domain: "localhost".to_string(),
include_subdomain: "FALSE".to_string(),
path: "/".to_string(),
https: "FALSE".to_string(),
expires: "0".to_string(),
name: "cookie2".to_string(),
value: "valueA".to_string(),
}
);
let request = default_get_request("http://localhost:8000/cookies/assert-that-cookie2-is-valueA".to_string());
let request = default_get_request(
"http://localhost:8000/cookies/assert-that-cookie2-is-valueA".to_string(),
);
let response = client.execute(&request, 0).unwrap();
assert_eq!(response.status, 200);
assert!(response.body.is_empty());
}
#[test]
fn test_cookie_file() {
let temp_file = "/tmp/cookies";
let mut file = File::create(temp_file).expect("can not create temp file!");
file.write_all(b"localhost\tFALSE\t/\tFALSE\t0\tcookie2\tvalueA\n").unwrap();
file.write_all(b"localhost\tFALSE\t/\tFALSE\t0\tcookie2\tvalueA\n")
.unwrap();
let options = ClientOptions {
follow_location: false,
@ -519,7 +566,9 @@ fn test_cookie_file() {
insecure: false,
};
let mut client = Client::init(options);
let request = default_get_request("http://localhost:8000/cookies/assert-that-cookie2-is-valueA".to_string());
let request = default_get_request(
"http://localhost:8000/cookies/assert-that-cookie2-is-valueA".to_string(),
);
let response = client.execute(&request, 0).unwrap();
assert_eq!(response.status, 200);
assert!(response.body.is_empty());
@ -549,4 +598,3 @@ fn test_proxy() {
}
// endregion

View File

@ -18,14 +18,13 @@
extern crate hurl;
use hurl::core::ast;
use hurl::core::ast::{EncodedString, Template, TemplateElement};
use hurl::core::common::{Pos, SourceInfo};
use hurl::runner;
use hurl::format;
use hurl::http;
use std::collections::HashMap;
use hurl::core::ast::{Template, TemplateElement, EncodedString};
use hurl::runner;
use hurl::runner::core::RunnerOptions;
use std::collections::HashMap;
// can be used for debugging
#[test]
@ -64,7 +63,7 @@ fn test_hurl_file() {
filename: Some(filename.to_string()),
lines: lines,
verbose: false,
color: false
color: false,
};
let _hurl_log = runner::file::run(
@ -74,11 +73,9 @@ fn test_hurl_file() {
filename.to_string(),
"current_dir".to_string(),
options,
logger
logger,
);
// assert_eq!(1,2)
// assert_eq!(1,2)
}
#[cfg(test)]
@ -104,9 +101,10 @@ fn hello_request() -> ast::Request {
space1: whitespace.clone(),
url: Template {
quotes: false,
elements: vec![
TemplateElement::String { value: "http://localhost:8000/hello".to_string(), encoded: "http://localhost:8000/hello".to_string() }
],
elements: vec![TemplateElement::String {
value: "http://localhost:8000/hello".to_string(),
encoded: "http://localhost:8000/hello".to_string(),
}],
source_info: source_info.clone(),
},
line_terminator0: ast::LineTerminator {
@ -114,28 +112,27 @@ fn hello_request() -> ast::Request {
comment: None,
newline: whitespace.clone(),
},
headers: vec![
ast::KeyValue {
line_terminators: vec![],
space0: whitespace.clone(),
key: EncodedString {
quotes: false,
value: "User-Agent".to_string(),
encoded: "User-Agent".to_string(),
source_info: source_info.clone(),
},
space1: whitespace.clone(),
space2: whitespace.clone(),
value: Template {
quotes: false,
elements: vec![
TemplateElement::String { value: "test".to_string(), encoded: "test".to_string() }
],
source_info: source_info.clone(),
},
line_terminator0: line_terminator.clone(),
}
],
headers: vec![ast::KeyValue {
line_terminators: vec![],
space0: whitespace.clone(),
key: EncodedString {
quotes: false,
value: "User-Agent".to_string(),
encoded: "User-Agent".to_string(),
source_info: source_info.clone(),
},
space1: whitespace.clone(),
space2: whitespace.clone(),
value: Template {
quotes: false,
elements: vec![TemplateElement::String {
value: "test".to_string(),
encoded: "test".to_string(),
}],
source_info: source_info.clone(),
},
line_terminator0: line_terminator.clone(),
}],
sections: vec![],
body: None,
source_info: source_info.clone(),
@ -191,9 +188,7 @@ fn test_hello() {
}],
line_terminators: vec![],
};
let lines = vec![
String::from("line1")
];
let lines = vec![String::from("line1")];
let variables = HashMap::new();
let options = RunnerOptions {
fail_fast: true,
@ -204,7 +199,7 @@ fn test_hello() {
filename: None,
lines,
verbose: false,
color: false
color: false,
};
let _hurl_log = runner::file::run(
hurl_file,
@ -212,17 +207,17 @@ fn test_hello() {
String::from("filename"),
"current_dir".to_string(),
options,
logger
logger,
);
//assert_eq!(hurl_log.entries.len(), 1);
//assert_eq!(hurl_log.entries.get(0).unwrap().response.status, 200);
// assert!(hurl_log
// .entries
// .get(0)
// .unwrap()
// .asserts
// .get(0)
// .unwrap()
// .clone()
// .success());
// assert!(hurl_log
// .entries
// .get(0)
// .unwrap()
// .asserts
// .get(0)
// .unwrap()
// .clone()
// .success());
}