mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-10-06 03:58:16 +03:00
rustfmt all your existing code!
This commit is contained in:
parent
42c246c7f2
commit
591ea32660
1
build.rs
1
build.rs
@ -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> {
|
||||
|
2
build.sh
2
build.sh
@ -8,6 +8,8 @@ cargo doc --document-private-items
|
||||
touch src/lib.rs
|
||||
cargo clippy -- -D warnings
|
||||
|
||||
cargo fmt -- --check
|
||||
|
||||
echo
|
||||
echo "!!! Build successful !!!"
|
||||
|
||||
|
@ -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&)
|
||||
|
414
src/bin/hurl.rs
414
src/bin/hurl.rs
@ -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
|
||||
))],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
*/
|
||||
pub mod options;
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Error {
|
||||
pub message: String,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
157
src/core/ast.rs
157
src/core/ast.rs
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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),
|
||||
|
@ -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>");
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 (0300–036F)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -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;
|
||||
|
@ -30,7 +30,6 @@ pub enum LinterError {
|
||||
OneSpace {},
|
||||
}
|
||||
|
||||
|
||||
impl FormatError for Error {
|
||||
fn source_info(&self) -> SourceInfo {
|
||||
self.clone().source_info
|
||||
|
@ -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::*;
|
||||
|
@ -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!(
|
||||
|
@ -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]
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
/*
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}
|
||||
);
|
||||
|
@ -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 {
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 (0300–036F)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -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(§ion.name())
|
||||
.contains(§ion.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;");
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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 {});
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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("");
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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¶m2=a%20b",
|
||||
"headers": []
|
||||
}"#).unwrap();
|
||||
assert_eq!(parse_request(v).unwrap(), Request {
|
||||
method: Method::Get,
|
||||
url: "http://localhost:8000/querystring-params?param1=value1¶m2=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¶m2=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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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>"#);
|
||||
|
290
tests/json.rs
290
tests/json.rs
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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")]);
|
||||
|
148
tests/libcurl.rs
148
tests/libcurl.rs
@ -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
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user