Merge pull request #32 from Orange-OpenSource/feature/clean-and-isolate-modules

Clean and isolate modules
This commit is contained in:
Fabrice Reix 2020-10-11 18:10:24 +02:00 committed by GitHub
commit 87b7353df8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1589 additions and 1974 deletions

View File

@ -4,7 +4,7 @@ set -e
shellcheck ./*.sh ./*/*.sh
# watch eprintln!/eprintln!
find src -name "*.rs" | grep -E -v 'logger|hurlfmt|http/client' | while read -r f; do
find src -name "*.rs" | grep -E -v 'logger|hurlfmt|http/client|hurl_file' | while read -r f; do
if grep -q eprintln "$f"; then
echo "file '$f' contains a println!"
exit 1

View File

@ -1,2 +1,2 @@
<!DOCTYPE html>
<html><head><title>Hurl Report</title><link rel="stylesheet" type="text/css" href="report.css"></head><body><h2>Hurl Report</h2><div class="date">Thu, 24 Sep 2020 21:02:09 +0200</div><table><thead><tr><td>filename</td><td>duration</td></tr></thead><tbody><tr><td class="success">tests/assert_base64.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/assert_header.hurl</td><td>0.003s</td></tr><tr><td class="success">tests/assert_json.hurl</td><td>0.009s</td></tr><tr><td class="success">tests/assert_match.hurl</td><td>0.016s</td></tr><tr><td class="success">tests/assert_regex.hurl</td><td>0.004s</td></tr><tr><td class="success">tests/assert_xpath.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/bytes.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/capture_and_assert.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/captures.hurl</td><td>0.008s</td></tr><tr><td class="success">tests/cookies.hurl</td><td>0.016s</td></tr><tr><td class="success">tests/cookie_storage.hurl</td><td>0.003s</td></tr><tr><td class="success">tests/delete.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/empty.hurl</td><td>0s</td></tr><tr><td class="success">tests/encoding.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_assert_base64.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_assert_file.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_assert_header_not_found.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_assert_header_value.hurl</td><td>0.001s</td></tr><tr><td class="failure">tests/error_assert_http_version.hurl</td><td>0.001s</td></tr><tr><td class="failure">tests/error_assert_invalid_predicate_type.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_assert_match_utf8.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_assert_query_cookie.hurl</td><td>0.004s</td></tr><tr><td class="failure">tests/error_assert_query_invalid_regex.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_assert_query_invalid_xpath.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_assert_status.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_assert_template_variable_not_found.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_assert_value_error.hurl</td><td>0.004s</td></tr><tr><td class="failure">tests/error_assert_variable.hurl</td><td>0.005s</td></tr><tr><td class="failure">tests/error_file_read_access.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_http_connection.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_invalid_jsonpath.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_invalid_url.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_invalid_xml.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_multipart_form_data.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_predicate.hurl</td><td>0.013s</td></tr><tr><td class="failure">tests/error_query_header_not_found.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_query_invalid_json.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_query_invalid_utf8.hurl</td><td>0.002s</td></tr><tr><td class="failure">tests/error_template_variable_not_found.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_template_variable_not_renderable.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/error_timeout.hurl</td><td>2.004s</td></tr><tr><td class="failure">tests/follow_redirect.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/form_params.hurl</td><td>0.004s</td></tr><tr><td class="success">tests/headers.hurl</td><td>0.011s</td></tr><tr><td class="success">tests/hello.hurl</td><td>0.003s</td></tr><tr><td class="success">tests/multipart_form_data.hurl</td><td>0.003s</td></tr><tr><td class="success">tests/no_entry.hurl</td><td>0s</td></tr><tr><td class="success">tests/output.hurl</td><td>0.003s</td></tr><tr><td class="success">tests/patch.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/post_base64.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/post_file.hurl</td><td>0.002s</td></tr><tr><td class="success">tests/post_json.hurl</td><td>0.015s</td></tr><tr><td class="success">tests/post_multilines.hurl</td><td>0.005s</td></tr><tr><td class="success">tests/post_xml.hurl</td><td>0.004s</td></tr><tr><td class="success">tests/predicates-string.hurl</td><td>0.005s</td></tr><tr><td class="success">tests/put.hurl</td><td>0.001s</td></tr><tr><td class="success">tests/querystring_params.hurl</td><td>0.007s</td></tr><tr><td class="success">tests/redirect.hurl</td><td>0.004s</td></tr><tr><td class="success">tests/utf8.hurl</td><td>0.002s</td></tr></tbody></table></body></html>
<html><head><title>Hurl Report</title><link rel="stylesheet" type="text/css" href="report.css"></head><body><h2>Hurl Report</h2><div class="date">Sun, 11 Oct 2020 16:54:45 +0200</div><table><thead><tr><td>filename</td><td>duration</td></tr></thead><tbody><tr><td class="success">tests/assert_base64.hurl</td><td>0.005s</td></tr><tr><td class="success">tests/assert_header.hurl</td><td>0.006s</td></tr><tr><td class="success">tests/assert_json.hurl</td><td>0.011s</td></tr><tr><td class="success">tests/assert_match.hurl</td><td>0.021s</td></tr><tr><td class="success">tests/assert_regex.hurl</td><td>0.008s</td></tr><tr><td class="success">tests/assert_xpath.hurl</td><td>0.006s</td></tr><tr><td class="success">tests/bytes.hurl</td><td>0.005s</td></tr><tr><td class="success">tests/capture_and_assert.hurl</td><td>0.006s</td></tr><tr><td class="success">tests/captures.hurl</td><td>0.012s</td></tr><tr><td class="success">tests/cookies.hurl</td><td>0.018s</td></tr><tr><td class="success">tests/cookie_storage.hurl</td><td>0.007s</td></tr><tr><td class="success">tests/delete.hurl</td><td>0.005s</td></tr><tr><td class="success">tests/empty.hurl</td><td>0s</td></tr><tr><td class="success">tests/encoding.hurl</td><td>0.007s</td></tr><tr><td class="failure">tests/error_assert_base64.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_assert_file.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_assert_header_not_found.hurl</td><td>0.007s</td></tr><tr><td class="failure">tests/error_assert_header_value.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_assert_http_version.hurl</td><td>0.005s</td></tr><tr><td class="failure">tests/error_assert_invalid_predicate_type.hurl</td><td>0.007s</td></tr><tr><td class="failure">tests/error_assert_match_utf8.hurl</td><td>0.005s</td></tr><tr><td class="failure">tests/error_assert_query_cookie.hurl</td><td>0.008s</td></tr><tr><td class="failure">tests/error_assert_query_invalid_regex.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_assert_query_invalid_xpath.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_assert_status.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_assert_template_variable_not_found.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_assert_value_error.hurl</td><td>0.008s</td></tr><tr><td class="failure">tests/error_assert_variable.hurl</td><td>0.008s</td></tr><tr><td class="failure">tests/error_file_read_access.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_http_connection.hurl</td><td>0.06s</td></tr><tr><td class="failure">tests/error_invalid_jsonpath.hurl</td><td>0.005s</td></tr><tr><td class="failure">tests/error_invalid_url.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_invalid_xml.hurl</td><td>0.005s</td></tr><tr><td class="failure">tests/error_multipart_form_data.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_predicate.hurl</td><td>0.016s</td></tr><tr><td class="failure">tests/error_query_header_not_found.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_query_invalid_json.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_query_invalid_utf8.hurl</td><td>0.006s</td></tr><tr><td class="failure">tests/error_template_variable_not_found.hurl</td><td>0s</td></tr><tr><td class="failure">tests/error_template_variable_not_renderable.hurl</td><td>0.006s</td></tr><tr><td class="success">tests/error_timeout.hurl</td><td>2.007s</td></tr><tr><td class="failure">tests/follow_redirect.hurl</td><td>0.006s</td></tr><tr><td class="success">tests/form_params.hurl</td><td>0.008s</td></tr><tr><td class="success">tests/headers.hurl</td><td>0.014s</td></tr><tr><td class="success">tests/hello.hurl</td><td>0.007s</td></tr><tr><td class="success">tests/multipart_form_data.hurl</td><td>0.007s</td></tr><tr><td class="success">tests/no_entry.hurl</td><td>0s</td></tr><tr><td class="success">tests/output.hurl</td><td>0.007s</td></tr><tr><td class="success">tests/patch.hurl</td><td>0.006s</td></tr><tr><td class="success">tests/post_base64.hurl</td><td>0.005s</td></tr><tr><td class="success">tests/post_file.hurl</td><td>0.006s</td></tr><tr><td class="success">tests/post_json.hurl</td><td>0.017s</td></tr><tr><td class="success">tests/post_multilines.hurl</td><td>0.009s</td></tr><tr><td class="success">tests/post_xml.hurl</td><td>0.007s</td></tr><tr><td class="success">tests/predicates-string.hurl</td><td>0.008s</td></tr><tr><td class="success">tests/put.hurl</td><td>0.005s</td></tr><tr><td class="success">tests/querystring_params.hurl</td><td>0.011s</td></tr><tr><td class="success">tests/redirect.hurl</td><td>0.007s</td></tr><tr><td class="success">tests/utf8.hurl</td><td>0.006s</td></tr></tbody></table></body></html>

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ upload1: file,hello.txt;
upload2: file,hello.html;
upload3: file,hello.txt; text/html
HTTP/1.0 200
HTTP/* 200

View File

@ -15,10 +15,12 @@
* limitations under the License.
*
*/
use super::json;
use core::fmt;
use super::common::SourceInfo;
use super::json;
///
/// Hurl AST
///
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HurlFile {
@ -483,7 +485,7 @@ pub struct Number {
// TBC: Issue with equality for f64
// represent your float only with integers
// must be easily compared to the core float value
// must be easily compared to the ast float value
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Float {
pub int: i64,
@ -534,6 +536,38 @@ pub enum Bytes {
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Pos {
pub line: usize,
pub column: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourceInfo {
pub start: Pos,
pub end: Pos,
}
impl SourceInfo {
pub fn init(
start_line: usize,
start_col: usize,
end_line: usize,
end_column: usize,
) -> SourceInfo {
SourceInfo {
start: Pos {
line: start_line,
column: start_col,
},
end: Pos {
line: end_line,
column: end_column,
},
}
}
}
//
// template
//

View File

@ -15,7 +15,14 @@
* limitations under the License.
*
*/
use super::ast::Template;
use super::core::Template;
///
/// This the AST for the JSON used within hurl
///
/// It is a superset of the standard json spec.
/// Strings have been replaced by hurl template.
///
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Value {
@ -65,11 +72,11 @@ pub struct ObjectElement {
#[cfg(test)]
pub mod tests {
use super::super::ast::{Expr, TemplateElement, Variable, Whitespace};
use super::super::common::SourceInfo;
use super::super::core::SourceInfo;
use super::super::core::{Expr, TemplateElement, Variable, Whitespace};
use super::*;
pub fn person_value() -> Value {
pub fn json_person_value() -> Value {
Value::Object {
space0: "\n ".to_string(),
elements: vec![ObjectElement {
@ -90,7 +97,7 @@ pub mod tests {
}
}
pub fn hello_world_value() -> Value {
pub fn json_hello_world_value() -> Value {
// "hello\u0020{{name}}!"
Value::String(Template {
quotes: true,

View File

@ -15,17 +15,14 @@
* limitations under the License.
*
*/
pub mod ast;
pub mod common;
pub mod json;
//#[derive(Clone, Debug, PartialEq, Eq)]
//pub struct Pos {
// pub line: usize,
// pub column: usize,
//}
//
//#[derive(Clone, Debug, PartialEq, Eq)]
//pub struct SourceInfo {
// pub start: Pos,
// pub end: Pos,
//}
pub use self::core::*;
pub use self::json::ListElement as JsonListElement;
pub use self::json::ObjectElement as JsonObjectElement;
pub use self::json::Value as JsonValue;
#[cfg(test)]
pub use self::json::tests::*;
mod core;
mod json;

View File

@ -28,14 +28,11 @@ use chrono::{DateTime, Local};
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::runner::{HurlResult, RunnerOptions};
use std::time::Duration;
#[derive(Clone, Debug, PartialEq, Eq)]
@ -61,43 +58,49 @@ fn execute(
current_dir: &Path,
file_root: Option<String>,
cli_options: CLIOptions,
logger: format::logger::Logger,
log_verbose: &impl Fn(&str),
log_error_message: &impl Fn(bool, &str),
) -> HurlResult {
let lines: Vec<String> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(&contents)
.map(|l| l.to_string())
.collect();
let optional_filename = if filename == "" {
None
} else {
Some(filename.to_string())
};
let log_parser_error =
cli::make_logger_parser_error(lines.clone(), cli_options.color, optional_filename.clone());
let log_runner_error =
cli::make_logger_runner_error(lines, cli_options.color, optional_filename);
match parser::parse_hurl_file(contents.as_str()) {
Err(e) => {
let error = hurl::format::error::Error {
source_info: e.source_info(),
description: e.description(),
fixme: e.fixme(),
lines: vec![],
filename: "".to_string(),
warning: false,
color: logger.color,
};
logger.error(&error);
log_parser_error(&e, false);
std::process::exit(2);
}
Ok(hurl_file) => {
logger.verbose(format!("fail fast: {}", cli_options.fail_fast).as_str());
logger.verbose(format!("insecure: {}", cli_options.insecure).as_str());
logger.verbose(format!("follow redirect: {}", cli_options.follow_location).as_str());
log_verbose(format!("fail fast: {}", cli_options.fail_fast).as_str());
log_verbose(format!("insecure: {}", cli_options.insecure).as_str());
log_verbose(format!("follow redirect: {}", cli_options.follow_location).as_str());
if let Some(n) = cli_options.max_redirect {
logger.verbose(format!("max redirect: {}", n).as_str());
log_verbose(format!("max redirect: {}", n).as_str());
}
if let Some(proxy) = cli_options.proxy.clone() {
logger.verbose(format!("proxy: {}", proxy).as_str());
log_verbose(format!("proxy: {}", proxy).as_str());
}
if !cli_options.variables.is_empty() {
logger.verbose("variables:");
log_verbose("variables:");
for (name, value) in cli_options.variables.clone() {
logger.verbose(format!(" {}={}", name, value).as_str());
log_verbose(format!(" {}={}", name, value).as_str());
}
}
if let Some(to_entry) = cli_options.to_entry {
if to_entry < hurl_file.entries.len() {
logger.verbose(
log_verbose(
format!(
"executing {}/{} entries",
to_entry.to_string(),
@ -106,7 +109,7 @@ fn execute(
.as_str(),
);
} else {
logger.verbose("executing all entries");
log_verbose("executing all entries");
}
}
@ -145,19 +148,20 @@ fn execute(
}
Some(filename) => filename,
};
let options = RunnerOptions {
fail_fast: cli_options.fail_fast,
variables: cli_options.variables,
to_entry: cli_options.to_entry,
context_dir,
};
runner::file::run(
runner::run_hurl_file(
hurl_file,
&mut client,
filename.to_string(),
context_dir,
options,
logger,
&log_verbose,
&log_error_message,
&log_runner_error,
)
}
}
@ -173,63 +177,57 @@ fn output_color(matches: ArgMatches) -> bool {
}
}
fn to_entry(matches: ArgMatches, logger: format::logger::Logger) -> Option<usize> {
fn to_entry(matches: ArgMatches) -> Result<Option<usize>, cli::CLIError> {
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);
}
Ok(v) => Ok(Some(v)),
Err(_) => Err(cli::CLIError {
message: "Invalid value for option --to-entry - must be a positive integer!"
.to_string(),
}),
},
None => None,
None => Ok(None),
}
}
fn json_file(
matches: ArgMatches,
logger: format::logger::Logger,
) -> (Vec<HurlResult>, Option<std::path::PathBuf>) {
log_verbose: impl Fn(&str),
) -> Result<(Vec<HurlResult>, Option<std::path::PathBuf>), cli::CLIError> {
if let Some(filename) = matches.value_of("json") {
let path = Path::new(filename);
let results = if matches.is_present("append") && std::path::Path::new(&path).exists() {
logger.verbose(format!("Appending session to {}", path.display()).as_str());
log_verbose(format!("Appending session to {}", path.display()).as_str());
let data = fs::read_to_string(path).unwrap();
let v: serde_json::Value = match serde_json::from_str(data.as_str()) {
Ok(v) => v,
Ok(val) => val,
Err(_) => {
logger.error_message(format!(
"The file {} is not a valid json file",
path.display()
));
std::process::exit(127);
return Err(cli::CLIError {
message: format!("The file {} is not a valid json file", path.display()),
});
}
};
match log_deserialize::parse_results(v) {
match runner::deserialize_results(v) {
Err(msg) => {
logger
.error_message(format!("Existing Hurl json can not be parsed! - {}", msg));
std::process::exit(127);
return Err(cli::CLIError {
message: format!("Existing Hurl json can not be parsed! - {}", msg),
});
}
Ok(results) => results,
}
} else {
if matches.is_present("verbose") {
logger.error_message(format!("* Writing session to {}", path.display()));
}
log_verbose(format!("* Writing session to {}", path.display()).as_str());
vec![]
};
(results, Some(path.to_path_buf()))
Ok((results, Some(path.to_path_buf())))
} else {
(vec![], None)
Ok((vec![], None))
}
}
fn html_report(matches: ArgMatches, logger: format::logger::Logger) -> Option<std::path::PathBuf> {
fn html_report(matches: ArgMatches) -> Result<Option<std::path::PathBuf>, cli::CLIError> {
if let Some(dir) = matches.value_of("html_report") {
let path = Path::new(dir);
if std::path::Path::new(&path).exists() {
@ -238,48 +236,51 @@ fn html_report(matches: ArgMatches, logger: format::logger::Logger) -> Option<st
.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)
return Err(cli::CLIError {
message: format!(
"Html dir {} already exists and is not empty",
path.display()
),
});
}
Some(path.to_path_buf())
Ok(Some(path.to_path_buf()))
} else {
match std::fs::create_dir(path) {
Err(_) => {
logger.error_message(format!("Html dir {} can not be created", path.display()));
std::process::exit(127)
}
Ok(_) => Some(path.to_path_buf()),
Err(_) => Err(cli::CLIError {
message: format!("Html dir {} can not be created", path.display()),
}),
Ok(_) => Ok(Some(path.to_path_buf())),
}
}
} else {
None
Ok(None)
}
}
fn variables(matches: ArgMatches, logger: format::logger::Logger) -> HashMap<String, String> {
fn variables(matches: ArgMatches) -> Result<HashMap<String, String>, cli::CLIError> {
let mut variables = HashMap::new();
if matches.is_present("variable") {
let input: Vec<_> = matches.values_of("variable").unwrap().collect();
for s in input {
match s.find('=') {
None => {
logger.error_message(format!("Missing variable value for {}!", s));
std::process::exit(1);
return Err(cli::CLIError {
message: format!("Missing variable value for {}!", s),
});
}
Some(index) => {
let (name, value) = s.split_at(index);
if variables.contains_key(name) {
std::process::exit(1);
return Err(cli::CLIError {
message: format!("Variable {} defined twice!", name),
});
}
variables.insert(name.to_string(), value[1..].to_string());
}
};
}
}
variables
Ok(variables)
}
fn app() -> clap::App<'static, 'static> {
@ -437,25 +438,25 @@ fn app() -> clap::App<'static, 'static> {
)
}
pub fn unwrap_or_exit<T>(result: Result<T, cli::Error>, logger: format::logger::Logger) -> T {
pub fn unwrap_or_exit<T>(
log_error_message: &impl Fn(bool, &str),
result: Result<T, cli::CLIError>,
) -> T {
match result {
Ok(v) => v,
Err(e) => {
logger.error_message(e.message);
log_error_message(false, e.message.as_str());
std::process::exit(127);
}
}
}
fn parse_options(
matches: ArgMatches,
logger: format::logger::Logger,
) -> Result<CLIOptions, cli::Error> {
fn parse_options(matches: ArgMatches) -> Result<CLIOptions, cli::CLIError> {
let verbose = matches.is_present("verbose");
let color = output_color(matches.clone());
let fail_fast = !matches.is_present("fail_at_end");
let variables = variables(matches.clone(), logger.clone());
let to_entry = to_entry(matches.clone(), logger);
let variables = variables(matches.clone())?;
let to_entry = to_entry(matches.clone())?;
let proxy = matches.value_of("proxy").map(|x| x.to_string());
let no_proxy = matches.value_of("proxy").map(|x| x.to_string());
let insecure = matches.is_present("insecure");
@ -467,7 +468,7 @@ fn parse_options(
Some(s) => match s.parse::<usize>() {
Ok(x) => Some(x),
Err(_) => {
return Err(cli::Error {
return Err(cli::CLIError {
message: "max_redirs option can not be parsed".to_string(),
});
}
@ -479,7 +480,7 @@ fn parse_options(
Some(s) => match s.parse::<u64>() {
Ok(n) => Duration::from_secs(n),
Err(_) => {
return Err(cli::Error {
return Err(cli::CLIError {
message: "max_time option can not be parsed".to_string(),
});
}
@ -491,7 +492,7 @@ fn parse_options(
Some(s) => match s.parse::<u64>() {
Ok(n) => Duration::from_secs(n),
Err(_) => {
return Err(cli::Error {
return Err(cli::CLIError {
message: "connect-timeout option can not be parsed".to_string(),
});
}
@ -515,7 +516,7 @@ fn parse_options(
})
}
fn main() -> Result<(), cli::Error> {
fn main() {
let app = app();
let matches = app.clone().get_matches();
@ -540,32 +541,26 @@ fn main() -> Result<(), cli::Error> {
Some(value) => Some(value.to_string()),
_ => None,
};
let verbose = matches.is_present("verbose");
let log_verbose = cli::make_logger_verbose(verbose);
let color = output_color(matches.clone());
let log_error_message = cli::make_logger_error_message(color);
let cli_options = unwrap_or_exit(&log_error_message, parse_options(matches.clone()));
let logger = format::logger::Logger {
filename: None,
lines: vec![],
verbose: matches.is_present("verbose"),
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 (mut hurl_results, json_file) =
unwrap_or_exit(&log_error_message, json_file(matches.clone(), &log_verbose));
let html_report = unwrap_or_exit(&log_error_message, html_report(matches.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(),
&log_error_message,
cli::cookies_output_file(filename.to_string(), filenames.len()),
);
Some(filename)
}
};
let cli_options = unwrap_or_exit(
parse_options(matches.clone(), logger.clone()),
logger.clone(),
);
for filename in filenames {
let contents = if filename == "-" {
let mut contents = String::new();
@ -575,30 +570,22 @@ fn main() -> Result<(), cli::Error> {
contents
} else {
if !Path::new(filename).exists() {
logger.error_message(format!("Input file {} does not exit!", filename));
log_error_message(
false,
format!("Input file {} does not exit!", filename).as_str(),
);
std::process::exit(1);
}
fs::read_to_string(filename).expect("Something went wrong reading the file")
};
let lines: Vec<String> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(&contents)
.map(|l| l.to_string())
.collect();
let logger = format::logger::Logger {
filename: Some(filename.to_string()),
lines: lines.clone(),
verbose: cli_options.verbose,
color: cli_options.color,
};
let hurl_result = execute(
filename,
contents,
current_dir,
file_root.clone(),
cli_options.clone(),
logger.clone(),
&log_verbose,
&log_error_message,
);
if hurl_result.errors().is_empty() {
@ -607,7 +594,7 @@ 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(
cli::log_info(
format!(
"HTTP/{} {}",
response.version.to_string(),
@ -616,24 +603,28 @@ fn main() -> Result<(), cli::Error> {
.as_str(),
);
for header in response.headers.clone() {
logger.info(format!("{}: {}", header.name, header.value).as_str());
cli::log_info(format!("{}: {}", header.name, header.value).as_str());
}
logger.info("");
cli::log_info("");
}
write_output(response.body, matches.value_of("output"), logger.clone());
unwrap_or_exit(
&log_error_message,
write_output(response.body, matches.value_of("output")),
);
} else {
logger.warning_message("no response has been received".to_string());
cli::log_info("no response has been received");
}
} else {
logger.warning_message(format!(
"warning: no entry have been executed {}",
if filename == "-" {
"".to_string()
} else {
format!("for file {}", filename)
}
));
let source = if filename == "-" {
"".to_string()
} else {
format!("for file {}", filename).to_string()
};
log_error_message(
true,
format!("no entry have been executed {}", source).as_str(),
);
};
}
@ -643,34 +634,38 @@ fn main() -> Result<(), cli::Error> {
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
));
log_error_message(
false,
format!("Issue writing to {}: {:?}", file_path.display(), why).as_str(),
);
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
));
log_error_message(
false,
format!("Issue writing to {}: {:?}", file_path.display(), why).as_str(),
);
std::process::exit(127)
}
}
if let Some(dir_path) = html_report {
logger.verbose(format!("Writing html report to {}", dir_path.display()).as_str());
write_html_report(dir_path, hurl_results.clone(), logger.clone());
log_verbose(format!("Writing html report to {}", dir_path.display()).as_str());
unwrap_or_exit(
&log_error_message,
write_html_report(dir_path, hurl_results.clone()),
);
}
if let Some(file_path) = cookies_output_file {
logger.verbose(format!("Writing cookies to {}", file_path.display()).as_str());
write_cookies_file(file_path, hurl_results.clone(), logger);
log_verbose(format!("Writing cookies to {}", file_path.display()).as_str());
unwrap_or_exit(
&log_error_message,
write_cookies_file(file_path, hurl_results.clone()),
);
}
std::process::exit(exit_code(hurl_results));
@ -680,7 +675,7 @@ 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
let runner_errors: Vec<runner::Error> = hurl_result
.clone()
.errors()
.iter()
@ -704,7 +699,7 @@ fn exit_code(hurl_results: Vec<HurlResult>) -> i32 {
}
}
fn write_output(bytes: Vec<u8>, filename: Option<&str>, logger: format::logger::Logger) {
fn write_output(bytes: Vec<u8>, filename: Option<&str>) -> Result<(), cli::CLIError> {
match filename {
None => {
let stdout = io::stdout();
@ -713,18 +708,21 @@ fn write_output(bytes: Vec<u8>, filename: Option<&str>, logger: format::logger::
handle
.write_all(bytes.as_slice())
.expect("writing bytes to console");
Ok(())
}
Some(filename) => {
let path = Path::new(filename);
let mut file = match std::fs::File::create(&path) {
Err(why) => {
logger.error_message(format!("Issue writing to {}: {:?}", path.display(), why));
std::process::exit(127)
return Err(cli::CLIError {
message: format!("Issue writing to {}: {:?}", path.display(), why),
});
}
Ok(file) => file,
};
file.write_all(bytes.as_slice())
.expect("writing bytes to file");
Ok(())
}
}
}
@ -732,16 +730,12 @@ fn write_output(bytes: Vec<u8>, filename: Option<&str>, logger: format::logger::
fn write_cookies_file(
file_path: PathBuf,
hurl_results: Vec<HurlResult>,
logger: format::logger::Logger,
) {
) -> Result<(), cli::CLIError> {
let mut file = match std::fs::File::create(&file_path) {
Err(why) => {
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
return Err(cli::CLIError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
});
}
Ok(file) => file,
};
@ -752,8 +746,9 @@ fn write_cookies_file(
.to_string();
match hurl_results.first() {
None => {
logger.error_message("Issue fetching results".to_string());
std::process::exit(127)
return Err(cli::CLIError {
message: "Issue fetching results".to_string(),
});
}
Some(result) => {
for cookie in result.cookies.clone() {
@ -764,20 +759,17 @@ fn write_cookies_file(
}
if let Err(why) = file.write_all(s.as_bytes()) {
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
return Err(cli::CLIError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
});
}
Ok(())
}
fn write_html_report(
dir_path: PathBuf,
hurl_results: Vec<HurlResult>,
logger: format::logger::Logger,
) {
) -> Result<(), cli::CLIError> {
//let now: DateTime<Utc> = Utc::now();
let now: DateTime<Local> = Local::now();
let html = create_html_index(now.to_rfc2822(), hurl_results);
@ -786,65 +778,54 @@ fn write_html_report(
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
));
std::process::exit(127)
return Err(cli::CLIError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
});
}
Ok(file) => file,
};
if let Err(why) = file.write_all(s.as_bytes()) {
logger.error_message(format!(
"Issue writing to {}: {:?}",
file_path.display(),
why
));
std::process::exit(127)
return Err(cli::CLIError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
});
}
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
));
std::process::exit(127)
return Err(cli::CLIError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
});
}
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
));
std::process::exit(127)
return Err(cli::CLIError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
});
}
Ok(())
}
fn create_html_index(now: String, hurl_results: Vec<HurlResult>) -> html::ast::Html {
let head = html::ast::Head {
fn create_html_index(now: String, hurl_results: Vec<HurlResult>) -> html::Html {
let head = html::Head {
title: "Hurl Report".to_string(),
stylesheet: Some("report.css".to_string()),
};
let body = html::ast::Body {
let body = html::Body {
children: vec![
html::ast::Element::NodeElement {
html::Element::NodeElement {
name: "h2".to_string(),
attributes: vec![],
children: vec![html::ast::Element::TextElement("Hurl Report".to_string())],
children: vec![html::Element::TextElement("Hurl Report".to_string())],
},
html::ast::Element::NodeElement {
html::Element::NodeElement {
name: "div".to_string(),
attributes: vec![html::ast::Attribute::Class("date".to_string())],
children: vec![html::ast::Element::TextElement(now)],
attributes: vec![html::Attribute::Class("date".to_string())],
children: vec![html::Element::TextElement(now)],
},
html::ast::Element::NodeElement {
html::Element::NodeElement {
name: "table".to_string(),
attributes: vec![],
children: vec![
@ -854,64 +835,64 @@ fn create_html_index(now: String, hurl_results: Vec<HurlResult>) -> html::ast::H
},
],
};
html::ast::Html { head, body }
html::Html { head, body }
}
fn create_html_table_header() -> html::ast::Element {
html::ast::Element::NodeElement {
fn create_html_table_header() -> html::Element {
html::Element::NodeElement {
name: "thead".to_string(),
attributes: vec![],
children: vec![html::ast::Element::NodeElement {
children: vec![html::Element::NodeElement {
name: "tr".to_string(),
attributes: vec![],
children: vec![
html::ast::Element::NodeElement {
html::Element::NodeElement {
name: "td".to_string(),
attributes: vec![],
children: vec![html::ast::Element::TextElement("filename".to_string())],
children: vec![html::Element::TextElement("filename".to_string())],
},
html::ast::Element::NodeElement {
html::Element::NodeElement {
name: "td".to_string(),
attributes: vec![],
children: vec![html::ast::Element::TextElement("duration".to_string())],
children: vec![html::Element::TextElement("duration".to_string())],
},
],
}],
}
}
fn create_html_table_body(hurl_results: Vec<HurlResult>) -> html::ast::Element {
fn create_html_table_body(hurl_results: Vec<HurlResult>) -> html::Element {
let children = hurl_results
.iter()
.map(|result| create_html_result(result.clone()))
.collect();
html::ast::Element::NodeElement {
html::Element::NodeElement {
name: "tbody".to_string(),
attributes: vec![],
children,
}
}
fn create_html_result(result: HurlResult) -> html::ast::Element {
fn create_html_result(result: HurlResult) -> html::Element {
let status = if result.success {
"success".to_string()
} else {
"failure".to_string()
};
html::ast::Element::NodeElement {
html::Element::NodeElement {
name: "tr".to_string(),
attributes: vec![],
children: vec![
html::ast::Element::NodeElement {
html::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::Attribute::Class(status)],
children: vec![html::Element::TextElement(result.filename.clone())],
},
html::ast::Element::NodeElement {
html::Element::NodeElement {
name: "td".to_string(),
attributes: vec![],
children: vec![html::ast::Element::TextElement(format!(
children: vec![html::Element::TextElement(format!(
"{}s",
result.time_in_ms as f64 / 1000.0
))],

View File

@ -25,10 +25,9 @@ use std::process;
use atty::Stream;
use hurl::core::common::FormatError;
use hurl::format::html;
use hurl::format::text;
use hurl::linter::core::Lintable;
use hurl::cli;
use hurl::format;
use hurl::linter::Lintable;
use hurl::parser;
fn main() {
@ -123,6 +122,8 @@ fn main() {
atty::is(Stream::Stdout)
};
let log_error_message = cli::make_logger_error_message(output_color);
let filename = match matches.value_of("INPUT") {
None => "-",
Some("-") => "-",
@ -140,7 +141,7 @@ fn main() {
};
if matches.is_present("in_place") && filename == "-" {
eprintln!("You can not use inplace with standard input stream!");
log_error_message(false, "You can not use inplace with standard input stream!");
std::process::exit(1);
};
@ -159,43 +160,31 @@ fn main() {
.split(&contents)
.collect();
let lines = lines.iter().map(|s| (*s).to_string()).collect();
let lines: Vec<String> = lines.iter().map(|s| (*s).to_string()).collect();
let optional_filename = if filename == "" {
None
} else {
Some(filename.to_string())
};
let log_parser_error =
cli::make_logger_parser_error(lines.clone(), output_color, optional_filename.clone());
let log_linter_error = cli::make_logger_linter_error(lines, output_color, optional_filename);
match parser::parse_hurl_file(contents.as_str()) {
Err(e) => {
//eprintln!("Error {:?}", e);
let error = hurl::format::error::Error {
source_info: e.source_info(),
description: e.description(),
fixme: e.fixme(),
lines,
filename: filename.to_string(),
warning: true,
color: output_color,
};
eprintln!("{}", error.format());
log_parser_error(&e, false);
process::exit(2);
}
Ok(hurl_file) => {
if matches.is_present("check") {
for e in hurl_file.errors() {
let error = hurl::format::error::Error {
source_info: e.source_info(),
description: e.description(),
fixme: e.fixme(),
lines: lines.clone(),
filename: filename.to_string(),
warning: true,
color: output_color,
};
eprintln!("{}", error.format());
log_linter_error(&e, true);
}
std::process::exit(1);
} else if matches.is_present("ast_output") {
eprintln!("{:#?}", hurl_file);
} else if matches.is_present("html_output") {
let standalone = matches.is_present("standalone");
println!("{}", html::format(hurl_file, standalone));
println!("{}", format::format_html(hurl_file, standalone));
} else {
let hurl_file = if matches.is_present("no_format") {
hurl_file
@ -205,13 +194,13 @@ fn main() {
if matches.is_present("in_place") {
match fs::File::create(filename) {
Ok(mut f) => {
let s = text::format(hurl_file, false);
let s = format::format_text(hurl_file, false);
f.write_all(s.as_bytes()).unwrap();
}
Err(_) => eprintln!("Error opening file {} in write mode", filename),
};
} else {
print!("{}", text::format(hurl_file, output_color));
print!("{}", format::format_text(hurl_file, output_color));
};
}
}

227
src/cli/error.rs Normal file
View File

@ -0,0 +1,227 @@
use crate::ast::SourceInfo;
use crate::linter;
use crate::linter::LinterError;
use crate::parser;
use crate::parser::ParseError;
use crate::runner;
use crate::runner::RunnerError;
pub trait Error {
fn source_info(&self) -> SourceInfo;
fn description(&self) -> String;
fn fixme(&self) -> String;
}
///
/// Textual Output for parser errors
///
impl Error for parser::Error {
fn source_info(&self) -> SourceInfo {
SourceInfo {
start: self.pos.clone(),
end: self.pos.clone(),
}
}
fn description(&self) -> String {
match self.clone().inner {
ParseError::Method { .. } => "Parsing Method".to_string(),
ParseError::Version { .. } => "Parsing Version".to_string(),
ParseError::Status { .. } => "Parsing Status".to_string(),
ParseError::Filename { .. } => "Parsing Filename".to_string(),
ParseError::Expecting { .. } => "Parsing literal".to_string(),
ParseError::Space { .. } => "Parsing space".to_string(),
ParseError::SectionName { .. } => "Parsing section name".to_string(),
ParseError::JsonpathExpr { .. } => "Parsing jsonpath expression".to_string(),
ParseError::XPathExpr { .. } => "Parsing xpath expression".to_string(),
ParseError::TemplateVariable { .. } => "Parsing template variable".to_string(),
ParseError::Json { .. } => "Parsing json".to_string(),
ParseError::Predicate { .. } => "Parsing predicate".to_string(),
ParseError::PredicateValue { .. } => "Parsing predicate value".to_string(),
ParseError::RegexExpr { .. } => "Parsing regex".to_string(),
ParseError::DuplicateSection { .. } => "Parsing section".to_string(),
ParseError::RequestSection { .. } => "Parsing section".to_string(),
ParseError::ResponseSection { .. } => "Parsing section".to_string(),
ParseError::EscapeChar { .. } => "Parsing escape character".to_string(),
ParseError::InvalidCookieAttribute { .. } => "Parsing cookie attribute".to_string(),
_ => format!("{:?}", self),
}
}
fn fixme(&self) -> String {
match self.inner.clone() {
ParseError::Method { .. } => "Available HTTP Method GET, POST, ...".to_string(),
ParseError::Version { .. } => "The http version must be 1.0, 1.1, 2 or *".to_string(),
ParseError::Status { .. } => "The http status is not valid".to_string(),
ParseError::Filename { .. } => "expecting a filename".to_string(),
ParseError::Expecting { value } => format!("expecting '{}'", value),
ParseError::Space { .. } => "expecting a space".to_string(),
ParseError::SectionName { name } => format!("the section {} is not valid", name),
ParseError::JsonpathExpr { .. } => "expecting a jsonpath expression".to_string(),
ParseError::XPathExpr { .. } => "expecting a xpath expression".to_string(),
ParseError::TemplateVariable { .. } => "expecting a variable".to_string(),
ParseError::Json { .. } => "json error".to_string(),
ParseError::Predicate { .. } => "expecting a predicate".to_string(),
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::EscapeChar { .. } => "The escaping sequence is not valid".to_string(),
ParseError::InvalidCookieAttribute { .. } => {
"The cookie attribute is not valid".to_string()
}
_ => format!("{:?}", self),
}
}
}
///
/// Textual Output for runner errors
///
///
impl Error for runner::Error {
fn source_info(&self) -> SourceInfo {
self.clone().source_info
}
fn description(&self) -> String {
match &self.inner {
RunnerError::InvalidURL(..) => "Invalid url".to_string(),
RunnerError::TemplateVariableNotDefined { .. } => "Undefined Variable".to_string(),
RunnerError::VariableNotDefined { .. } => "Undefined Variable".to_string(),
RunnerError::HttpConnection { .. } => "Http Connection".to_string(),
RunnerError::CouldNotResolveProxyName => "Http Connection".to_string(),
RunnerError::CouldNotResolveHost => "Http Connection".to_string(),
RunnerError::FailToConnect => "Http Connection".to_string(),
RunnerError::Timeout => "Http Connection".to_string(),
RunnerError::TooManyRedirect => "Http Connection".to_string(),
RunnerError::CouldNotParseResponse => "Http Connection".to_string(),
RunnerError::SSLCertificate => "Http Connection".to_string(),
RunnerError::PredicateValue { .. } => "Assert - Predicate Value Failed".to_string(),
RunnerError::InvalidRegex {} => "Invalid regex".to_string(),
RunnerError::FileReadAccess { .. } => "File ReadAccess".to_string(),
RunnerError::QueryInvalidXml { .. } => "Invalid XML".to_string(),
RunnerError::QueryInvalidXpathEval {} => "Invalid xpath expression".to_string(),
RunnerError::QueryHeaderNotFound {} => "Header not Found".to_string(),
RunnerError::QueryCookieNotFound {} => "Cookie not Found".to_string(),
RunnerError::AssertHeaderValueError { .. } => "Assert Header Value".to_string(),
RunnerError::AssertBodyValueError { .. } => "Assert Body Value".to_string(),
RunnerError::AssertVersion { .. } => "Assert Http Version".to_string(),
RunnerError::AssertStatus { .. } => "Assert Status".to_string(),
RunnerError::QueryInvalidJson { .. } => "Invalid Json".to_string(),
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::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::HttpConnection { url, message } => {
format!("can not connect to {} ({})", url, message)
}
RunnerError::CouldNotResolveProxyName => "Could not resolve proxy name".to_string(),
RunnerError::CouldNotResolveHost => "Could not resolve host".to_string(),
RunnerError::FailToConnect => "Fail to connect".to_string(),
RunnerError::Timeout => "Timeout has been reached".to_string(),
RunnerError::TooManyRedirect => "Too many redirect".to_string(),
RunnerError::CouldNotParseResponse => "Could not parse response".to_string(),
RunnerError::SSLCertificate => "SSl Certificate".to_string(),
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::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::NoQueryResult { .. } => "The query didn't return any result".to_string(),
}
}
}
///
/// Textual Output for linter errors
///
///
impl Error for linter::Error {
fn source_info(&self) -> SourceInfo {
self.clone().source_info
}
fn description(&self) -> String {
match self.inner {
LinterError::UnneccessarySpace { .. } => "Unnecessary space".to_string(),
LinterError::UnneccessaryJsonEncoding {} => "Unnecessary json encoding".to_string(),
LinterError::OneSpace {} => "One space ".to_string(),
}
}
fn fixme(&self) -> String {
match self.inner {
LinterError::UnneccessarySpace { .. } => "Remove space".to_string(),
LinterError::UnneccessaryJsonEncoding {} => "Use Simple String".to_string(),
LinterError::OneSpace {} => "Use only one space".to_string(),
}
}
}

175
src/cli/logger.rs Normal file
View File

@ -0,0 +1,175 @@
/*
* hurl (https://hurl.dev)
* Copyright (C) 2020 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use super::error::Error;
use crate::format::TerminalColor;
use crate::linter;
use crate::parser;
use crate::runner;
pub fn make_logger_verbose(verbose: bool) -> impl Fn(&str) {
move |message| log_verbose(verbose, message)
}
pub fn make_logger_error_message(color: bool) -> impl Fn(bool, &str) {
move |warning, message| log_error_message(color, warning, message)
}
pub fn make_logger_parser_error(
lines: Vec<String>,
color: bool,
filename: Option<String>,
) -> impl Fn(&parser::Error, bool) {
move |error: &parser::Error, warning: bool| {
log_error(lines.clone(), color, filename.clone(), error, warning)
}
}
pub fn make_logger_runner_error(
lines: Vec<String>,
color: bool,
filename: Option<String>,
) -> impl Fn(&runner::Error, bool) {
move |error: &runner::Error, warning: bool| {
log_error(lines.clone(), color, filename.clone(), error, warning)
}
}
pub fn make_logger_linter_error(
lines: Vec<String>,
color: bool,
filename: Option<String>,
) -> impl Fn(&linter::Error, bool) {
move |error: &linter::Error, warning: bool| {
log_error(lines.clone(), color, filename.clone(), error, warning)
}
}
pub fn log_info(message: &str) {
eprintln!("{}", message);
}
fn log_error_message(color: bool, warning: bool, message: &str) {
let log_type = match (color, warning) {
(false, false) => "warning".to_string(),
(false, true) => "error".to_string(),
(true, false) => TerminalColor::Red.format("error".to_string()),
(true, true) => TerminalColor::Yellow.format("warning".to_string()),
};
eprintln!("{}: {}", log_type, message);
}
fn log_verbose(verbose: bool, message: &str) {
if verbose {
eprintln!("* {}", message);
}
}
fn log_error(
lines: Vec<String>,
color: bool,
filename: Option<String>,
error: &dyn Error,
warning: bool,
) {
let line_number_size = if lines.len() < 100 {
2
} else if lines.len() < 1000 {
3
} else {
4
};
let error_type = if warning {
String::from("warning")
} else {
String::from("error")
};
let error_type = if !color {
error_type
} else if warning {
TerminalColor::Yellow.format(error_type)
} else {
TerminalColor::Red.format(error_type)
};
eprintln!("{}: {}", error_type, error.description());
if let Some(filename) = filename {
eprintln!(
"{}--> {}:{}:{}",
" ".repeat(line_number_size).as_str(),
filename,
error.source_info().start.line,
error.source_info().start.column,
);
}
eprintln!("{} |", " ".repeat(line_number_size));
let line = lines.get(error.source_info().start.line - 1).unwrap();
let line = str::replace(line, "\t", " "); // replace all your tabs with 4 characters
eprintln!(
"{line_number:>width$} |{line}",
line_number = error.source_info().start.line,
width = line_number_size,
line = if line.is_empty() {
line
} else {
format!(" {}", line)
}
);
// TODO: to clean/Refacto
// specific case for assert errors
if error.source_info().start.column == 0 {
let fix_me = &error.fixme();
let fixme_lines: Vec<&str> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(fix_me)
.collect();
// edd an empty line at the end?
for line in fixme_lines {
eprintln!(
"{} | {}",
" ".repeat(line_number_size).as_str(),
fixme = line,
);
}
} else {
let line = lines.get(error.source_info().start.line - 1).unwrap();
let width = (error.source_info().end.column - error.source_info().start.column) as usize;
let mut tab_shift = 0;
for (i, c) in line.chars().enumerate() {
if i >= error.source_info().start.column - 1 {
break;
};
if c == '\t' {
tab_shift += 1;
}
}
eprintln!(
"{} | {}{} {fixme}",
" ".repeat(line_number_size).as_str(),
" ".repeat(error.source_info().start.column - 1 + tab_shift * 3),
"^".repeat(if width > 1 { width } else { 1 }),
fixme = error.fixme().as_str(),
);
}
eprintln!("{} |\n", " ".repeat(line_number_size));
}

View File

@ -15,9 +15,14 @@
* limitations under the License.
*
*/
pub mod options;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub message: String,
}
pub use self::logger::{
log_info, make_logger_error_message, make_logger_linter_error, make_logger_parser_error,
make_logger_runner_error, make_logger_verbose,
};
pub use self::options::cookies_output_file;
pub use self::options::CLIError;
mod error;
mod logger;
mod options;

View File

@ -16,11 +16,13 @@
*
*/
use super::Error;
pub struct CLIError {
pub message: String,
}
pub fn cookies_output_file(filename: String, n: usize) -> Result<std::path::PathBuf, Error> {
pub fn cookies_output_file(filename: String, n: usize) -> Result<std::path::PathBuf, CLIError> {
if n > 1 {
Err(Error {
Err(CLIError {
message: "Only save cookies for a unique session".to_string(),
})
} else {
@ -28,24 +30,3 @@ pub fn cookies_output_file(filename: String, n: usize) -> Result<std::path::Path
Ok(path.to_path_buf())
}
}
pub fn output_color(color_present: bool, no_color_present: bool, stdout: bool) -> bool {
if color_present {
true
} else if no_color_present {
false
} else {
stdout
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_output_color() {
assert_eq!(output_color(true, false, true), true);
assert_eq!(output_color(false, false, true), true);
}
}

View File

@ -1,338 +0,0 @@
/*
* hurl (https://hurl.dev)
* Copyright (C) 2020 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use std::fmt;
use serde::ser::Serializer;
use serde::Serialize;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DeprecatedValue {
Int(i32),
String(String),
List(usize),
Bool(bool),
Number(i32, u32),
// 9 decimal digits
ListInt(Vec<i32>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
//#[derive(Clone, Debug, PartialEq, PartialOrd)]
pub enum Value {
Unit,
Bool(bool),
Integer(i64),
// can use simply Float(f64)
// the trait `std::cmp::Eq` is not implemented for `f64`
// integer/ decimals with 18 digits
Float(i64, u64),
// integer part, decimal part (9 digits) TODO Clarify your custom type
String(String),
List(Vec<Value>),
Object(Vec<(String, Value)>),
Nodeset(usize),
Bytes(Vec<u8>),
Null,
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value = match self {
Value::Integer(x) => x.to_string(),
Value::Bool(x) => x.to_string(),
Value::Float(int, dec) => format!("{}.{}", int, dec),
Value::String(x) => x.clone(),
Value::List(values) => {
let values: Vec<String> = values.iter().map(|e| e.to_string()).collect();
format!("[{}]", values.join(","))
}
Value::Object(_) => "Object()".to_string(),
Value::Nodeset(x) => format!("Nodeset{:?}", x),
Value::Bytes(x) => format!("Bytes({:x?})", x),
Value::Null => "Null".to_string(),
Value::Unit => "Unit".to_string(),
};
write!(f, "{}", value)
}
}
impl Value {
pub fn _type(&self) -> String {
match self {
Value::Integer(_) => "integer".to_string(),
Value::Bool(_) => "boolean".to_string(),
Value::Float(_, _) => "float".to_string(),
Value::String(_) => "string".to_string(),
Value::List(_) => "list".to_string(),
Value::Object(_) => "object".to_string(),
Value::Nodeset(_) => "nodeset".to_string(),
Value::Bytes(_) => "bytes".to_string(),
Value::Null => "null".to_string(),
Value::Unit => "unit".to_string(),
}
}
pub fn from_f64(value: f64) -> Value {
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)
}
pub fn is_scalar(&self) -> bool {
match self {
Value::Nodeset(_) | Value::List(_) => false,
_ => true,
}
}
pub fn from_json(value: &serde_json::Value) -> Value {
match value {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(bool) => Value::Bool(*bool),
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::Object(map) => {
let mut elements = vec![];
for (key, value) in map {
elements.push((key.to_string(), Value::from_json(value)));
//
}
Value::Object(elements)
}
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Pos {
pub line: usize,
pub column: usize,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourceInfo {
pub start: Pos,
pub end: Pos,
}
impl SourceInfo {
pub fn init(
start_line: usize,
start_col: usize,
end_line: usize,
end_column: usize,
) -> SourceInfo {
SourceInfo {
start: Pos {
line: start_line,
column: start_col,
},
end: Pos {
line: end_line,
column: end_column,
},
}
}
}
pub trait FormatError {
fn source_info(&self) -> SourceInfo;
fn description(&self) -> String;
fn fixme(&self) -> String;
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::Bool(v) => serializer.serialize_bool(*v),
Value::Integer(v) => serializer.serialize_i64(*v),
Value::Float(i, d) => {
let value = *i as f64 + (*d as f64) / 1_000_000_000_000_000_000.0;
serializer.serialize_f64(value)
}
Value::String(s) => serializer.serialize_str(s),
Value::List(values) => serializer.collect_seq(values),
Value::Object(values) => serializer.collect_map(values.iter().map(|(k, v)| (k, v))),
Value::Nodeset(size) => {
let size = *size as i64;
serializer.collect_map(vec![
("type", serde_json::Value::String("nodeset".to_string())),
("size", serde_json::Value::from(size)),
])
}
Value::Bytes(v) => {
let encoded = base64::encode(v);
serializer.serialize_str(&encoded)
}
Value::Null => serializer.serialize_none(),
Value::Unit => todo!("how to serialize that in json?"),
}
}
}
impl Value {
pub fn to_json_value(&self) -> (String, serde_json::Value) {
match self.clone() {
Value::Bool(v) => ("bool".to_string(), serde_json::Value::Bool(v)),
Value::Integer(v) => ("integer".to_string(), serde_json::Value::from(v)),
Value::Float(i, d) => {
let value = i as f64 + (d as f64) / 1_000_000_000_000_000_000.0;
("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::Object(_) => todo!(),
Value::Nodeset(_) => todo!(),
Value::Bytes(_) => todo!(),
Value::Null => todo!(),
Value::Unit => todo!(),
}
}
pub fn to_json(&self) -> serde_json::Value {
match self.clone() {
Value::Bool(v) => serde_json::Value::Bool(v),
Value::Integer(v) => serde_json::Value::from(v),
Value::Float(i, d) => {
let value = i as f64 + (d as f64) / 1_000_000_000_000_000_000.0;
serde_json::Value::from(value)
}
Value::String(v) => serde_json::Value::String(v),
Value::List(_) => serde_json::Value::Array(vec![]),
Value::Object(_) => todo!(),
Value::Nodeset(_) => todo!(),
Value::Bytes(_) => todo!(),
Value::Null => todo!(),
Value::Unit => todo!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
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)
);
}
#[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)
);
}
#[test]
fn test_is_scalar() {
assert_eq!(Value::Integer(1).is_scalar(), true);
assert_eq!(Value::List(vec![]).is_scalar(), false);
}
#[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::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::Null {}).unwrap(), "null");
}
}

View File

@ -1,249 +0,0 @@
/*
* hurl (https://hurl.dev)
* Copyright (C) 2020 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use crate::core::common::SourceInfo;
use super::color::TerminalColor;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
//pub exit_code: usize,
pub source_info: SourceInfo,
pub description: String,
pub fixme: String,
pub lines: Vec<String>,
pub filename: String,
pub warning: bool,
pub color: bool,
}
impl Error {
pub fn format(self) -> String {
let mut s = "".to_string();
let line_number_size = if self.lines.len() < 100 {
2
} else if self.lines.len() < 1000 {
3
} else {
4
};
let error_type = if self.warning {
String::from("warning")
} else {
String::from("error")
};
let error_type = if !self.color {
error_type
} else if self.warning {
TerminalColor::Yellow.format(error_type)
} else {
TerminalColor::Red.format(error_type)
};
s.push_str(format!("{}: {}\n", error_type, self.description).as_str());
if self.filename != "-" {
s.push_str(
format!(
"{}--> {}:{}:{}\n",
" ".repeat(line_number_size).as_str(),
self.filename,
self.source_info.start.line,
self.source_info.start.column,
)
.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
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)
}
)
.as_str(),
);
// TODO: to clean/Refacto
// specific case for assert errors
if self.source_info.start.column == 0 {
let fixme_lines: Vec<&str> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(&self.fixme)
.collect();
// edd an empty line at the end?
for line in fixme_lines {
s.push_str(
format!(
"{} | {}\n",
" ".repeat(line_number_size).as_str(),
fixme = line,
)
.as_str(),
);
}
} else {
let line = self.lines.get(self.source_info.start.line - 1).unwrap();
let width = (self.source_info.end.column - self.source_info.start.column) as usize;
let mut tab_shift = 0;
for (i, c) in line.chars().enumerate() {
if i >= self.source_info.start.column - 1 {
break;
};
if c == '\t' {
tab_shift += 1;
}
}
s.push_str(
format!(
"{} | {}{} {fixme}\n",
" ".repeat(line_number_size).as_str(),
" ".repeat(self.source_info.start.column - 1 + tab_shift * 3),
"^".repeat(if width > 1 { width } else { 1 }),
fixme = self.fixme.as_str(),
)
.as_str(),
);
}
s.push_str(format!("{} |\n", " ".repeat(line_number_size)).as_str());
s
}
}
#[cfg(test)]
mod tests {
use super::*;
#[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 error = Error {
source_info: SourceInfo::init(1, 4, 1, 5),
description: String::from("One space"),
fixme: String::from("Use only one space"),
lines,
filename,
warning: true,
color: false,
};
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 error = Error {
source_info: SourceInfo::init(1, 32, 1, 32),
description: String::from("Unnecessary space"),
fixme: String::from("Remove space"),
lines,
filename,
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"
)
);
}
#[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(""),
];
let error = Error {
source_info: SourceInfo::init(3, 1, 3, 1),
description: String::from("Parsing json"),
fixme: String::from("json error"),
lines,
filename,
warning: true,
color: false,
};
assert_eq!(
error.format(),
String::from(
r#"warning: Parsing json
--> hurl_error_parser/json_unexpected_eof.hurl:3:1
|
3 |
| ^ json error
|
"#
)
);
}
#[test]
fn test_assert_error() {
let filename = String::from("hurl_error_parser/json_unexpected_eof.hurl");
let lines = vec![
String::from("...\n"),
String::from("[Asserts]\n"),
String::from("jsonpath \"$.message\" startsWith \"hello\""),
];
let _error = Error {
source_info: SourceInfo::init(3, 0, 3, 0),
description: String::from("Assert Error"),
fixme: String::from("actual: string <tutu>\nexpected: starts with string <toto>"),
lines,
filename,
warning: false,
color: false,
};
//assert_eq!(1,2);
}
}

View File

@ -15,7 +15,7 @@
* limitations under the License.
*
*/
use super::super::core::ast::*;
use crate::ast::*;
pub trait Htmlable {
fn to_html(&self) -> String;

View File

@ -1,157 +0,0 @@
/*
* hurl (https://hurl.dev)
* Copyright (C) 2020 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use super::color::TerminalColor;
use super::error::Error;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Logger {
pub filename: Option<String>,
pub lines: Vec<String>,
pub verbose: bool,
pub color: bool,
}
impl Logger {
pub fn info(&self, s: &str) {
println!("{}", s);
}
pub fn verbose(&self, s: &str) {
if self.verbose {
eprintln!("* {}", s);
}
}
pub fn send(&self, s: String) {
if self.verbose {
eprintln!("> {}", s);
}
}
pub fn receive(&self, s: String) {
if self.verbose {
eprintln!("< {}", s);
}
}
pub fn error_message(&self, s: String) {
let error_type = if !self.color {
"error".to_string()
} else {
TerminalColor::Red.format("error".to_string())
};
eprintln!("{}: {}", error_type, s);
}
pub fn warning_message(&self, s: String) {
let error_type = if !self.color {
"warning".to_string()
} else {
TerminalColor::Yellow.format("warning".to_string())
};
eprintln!("{}: {}", error_type, s);
}
pub fn error(&self, err: &Error) {
let line_number_size = if self.lines.len() < 100 {
2
} else if self.lines.len() < 1000 {
3
} else {
4
};
let error_type = if err.warning {
String::from("warning")
} else {
String::from("error")
};
let error_type = if !self.color {
error_type
} else if err.warning {
TerminalColor::Yellow.format(error_type)
} else {
TerminalColor::Red.format(error_type)
};
eprintln!("{}: {}", error_type, err.description);
if let Some(filename) = self.filename.clone() {
eprintln!(
"{}--> {}:{}:{}",
" ".repeat(line_number_size).as_str(),
filename,
err.source_info.start.line,
err.source_info.start.column,
);
}
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
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)
}
);
// TODO: to clean/Refacto
// specific case for assert errors
if err.source_info.start.column == 0 {
let fixme_lines: Vec<&str> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(&err.fixme)
.collect();
// edd an empty line at the end?
for line in fixme_lines {
eprintln!(
"{} | {}",
" ".repeat(line_number_size).as_str(),
fixme = line,
);
}
} else {
let line = self.lines.get(err.source_info.start.line - 1).unwrap();
let width = (err.source_info.end.column - err.source_info.start.column) as usize;
let mut tab_shift = 0;
for (i, c) in line.chars().enumerate() {
if i >= err.source_info.start.column - 1 {
break;
};
if c == '\t' {
tab_shift += 1;
}
}
eprintln!(
"{} | {}{} {fixme}",
" ".repeat(line_number_size).as_str(),
" ".repeat(err.source_info.start.column - 1 + tab_shift * 3),
"^".repeat(if width > 1 { width } else { 1 }),
fixme = err.fixme.as_str(),
);
}
eprintln!("{} |\n", " ".repeat(line_number_size));
}
}

View File

@ -15,9 +15,13 @@
* limitations under the License.
*
*/
pub mod color;
pub mod error;
pub mod html;
pub mod logger;
pub mod text;
pub mod token;
pub use self::color::TerminalColor;
pub use self::html::format as format_html;
pub use self::text::format as format_text;
pub use self::token::{Token, Tokenizable};
mod color;
mod html;
mod text;
mod token;

View File

@ -15,7 +15,8 @@
* limitations under the License.
*
*/
use super::super::core::ast::*;
use crate::ast::*;
use super::color::TerminalColor;
use super::token::*;

View File

@ -16,8 +16,7 @@
*
*/
use super::super::core::ast::*;
use super::super::core::json;
use crate::ast::*;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Token {
@ -714,22 +713,22 @@ impl Tokenizable for Filename {
}
}
impl Tokenizable for json::Value {
impl Tokenizable for JsonValue {
fn tokenize(&self) -> Vec<Token> {
let mut tokens: Vec<Token> = vec![];
match self {
json::Value::String(s) => {
JsonValue::String(s) => {
//tokens.push(Token::CodeDelimiter("\"".to_string()));
tokens.append(&mut s.tokenize());
//tokens.push(Token::CodeDelimiter("\"".to_string()));
}
json::Value::Number(value) => {
JsonValue::Number(value) => {
tokens.push(Token::Number(value.clone()));
}
json::Value::Boolean(value) => {
JsonValue::Boolean(value) => {
tokens.push(Token::Number(value.to_string()));
}
json::Value::List { space0, elements } => {
JsonValue::List { space0, elements } => {
tokens.push(Token::CodeDelimiter("[".to_string()));
tokens.push(Token::Whitespace(space0.clone()));
for (i, element) in elements.iter().enumerate() {
@ -740,7 +739,7 @@ impl Tokenizable for json::Value {
}
tokens.push(Token::CodeDelimiter("]".to_string()));
}
json::Value::Object { space0, elements } => {
JsonValue::Object { space0, elements } => {
tokens.push(Token::CodeDelimiter("{".to_string()));
tokens.push(Token::Whitespace(space0.clone()));
for (i, element) in elements.iter().enumerate() {
@ -751,7 +750,7 @@ impl Tokenizable for json::Value {
}
tokens.push(Token::CodeDelimiter("}".to_string()));
}
json::Value::Null {} => {
JsonValue::Null {} => {
tokens.push(Token::Keyword("null".to_string()));
}
}
@ -759,7 +758,7 @@ impl Tokenizable for json::Value {
}
}
impl Tokenizable for json::ListElement {
impl Tokenizable for JsonListElement {
fn tokenize(&self) -> Vec<Token> {
let mut tokens: Vec<Token> = vec![];
tokens.push(Token::Whitespace(self.space0.clone()));
@ -769,7 +768,7 @@ impl Tokenizable for json::ListElement {
}
}
impl Tokenizable for json::ObjectElement {
impl Tokenizable for JsonObjectElement {
fn tokenize(&self) -> Vec<Token> {
let mut tokens: Vec<Token> = vec![];
tokens.push(Token::Whitespace(self.space0.clone()));

View File

@ -15,5 +15,8 @@
* limitations under the License.
*
*/
pub mod ast;
pub mod render;
pub use self::ast::{Attribute, Body, Element, Head, Html};
mod ast;
mod render;

View File

@ -15,6 +15,9 @@
* limitations under the License.
*
*/
pub mod ast;
pub mod eval;
pub mod parser;
pub use self::parser::parse;
mod ast;
mod eval;
mod parser;

View File

@ -19,47 +19,6 @@ 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)),
Err(e) => {
if e.recoverable {
p.state = start;
Ok(None)
} else {
Err(e)
}
}
}
}
// make an error recoverable
// but does not reset cursor
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,
}),
}
}
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,
}),
}
}
pub fn zero_or_more<'a, T>(f: ParseFunc<'a, T>, p: &mut Reader) -> ParseResult<'a, Vec<T>> {
let _start = p.state.clone();
@ -87,40 +46,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) {
Ok(r) => {
let mut v = vec![r];
loop {
let initial_state = reader.state.clone();
match f(reader) {
Ok(r) => {
v.push(r);
}
Err(e) => {
if e.recoverable {
reader.state.pos = initial_state.pos;
reader.state.cursor = initial_state.cursor;
return Ok(v);
} else {
return Err(e);
};
}
}
}
}
Err(Error { pos, inner, .. }) => {
// if zero occurence => should fail?
Err(Error {
pos,
recoverable: false,
inner,
})
}
}
}
// return the last error when no default error is specified
// tipically this should be recoverable
pub fn choice<'a, T>(fs: Vec<ParseFunc<'a, T>>, p: &mut Reader) -> ParseResult<'a, T> {
@ -144,22 +69,3 @@ pub fn choice<'a, T>(fs: Vec<ParseFunc<'a, T>>, p: &mut Reader) -> ParseResult<'
}
}
}
pub fn peek<T>(f: ParseFunc<T>, p: Reader) -> ParseResult<T> {
let start = p.state.clone();
let mut p = p;
match f(&mut p) {
Ok(r) => {
p.state = start;
Ok(r)
}
Err(e) => {
p.state = start;
Err(Error {
pos: e.pos,
recoverable: false,
inner: e.inner,
})
}
}
}

View File

@ -15,15 +15,10 @@
* limitations under the License.
*
*/
use error::Error;
use reader::Reader;
pub mod combinators;
pub mod error;
pub mod parse;
pub mod primitives;
pub mod reader;
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Pos {
pub line: usize,
@ -32,3 +27,11 @@ pub struct Pos {
pub type ParseResult<'a, T> = std::result::Result<T, Error>;
pub type ParseFunc<'a, T> = fn(&mut Reader) -> ParseResult<'a, T>;
pub use self::parse::parse;
mod combinators;
mod error;
mod parse;
mod primitives;
mod reader;

View File

@ -20,8 +20,8 @@
#[macro_use]
extern crate float_cmp;
pub mod ast;
pub mod cli;
pub mod core;
pub mod format;
pub mod html;
pub mod http;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*
*/
use crate::core::common::{FormatError, SourceInfo};
use crate::ast::SourceInfo;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
@ -30,28 +30,6 @@ pub enum LinterError {
OneSpace {},
}
impl FormatError for Error {
fn source_info(&self) -> SourceInfo {
self.clone().source_info
}
fn description(&self) -> String {
match self.inner {
LinterError::UnneccessarySpace { .. } => "Unnecessary space".to_string(),
LinterError::UnneccessaryJsonEncoding {} => "Unnecessary json encoding".to_string(),
LinterError::OneSpace {} => "One space ".to_string(),
}
}
fn fixme(&self) -> String {
match self.inner {
LinterError::UnneccessarySpace { .. } => "Remove space".to_string(),
LinterError::UnneccessaryJsonEncoding {} => "Use Simple String".to_string(),
LinterError::OneSpace {} => "Use only one space".to_string(),
}
}
}
pub trait Lintable<T> {
fn errors(&self) -> Vec<Error>;
fn lint(&self) -> T;

View File

@ -15,5 +15,8 @@
* limitations under the License.
*
*/
pub mod core;
pub mod rules;
pub use self::core::{Error, Lintable, LinterError};
mod core;
mod rules;

View File

@ -15,8 +15,7 @@
* limitations under the License.
*
*/
use crate::core::ast::*;
use crate::core::common::SourceInfo;
use crate::ast::*;
use super::core::{Error, Lintable, LinterError};

View File

@ -15,7 +15,7 @@
* limitations under the License.
*
*/
use crate::core::ast::*;
use crate::ast::*;
use super::base64;
use super::combinators::*;
@ -86,8 +86,6 @@ 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::super::error::*;
use super::*;
@ -98,22 +96,22 @@ mod tests {
assert_eq!(
bytes(&mut reader).unwrap(),
Bytes::Json {
value: json::Value::List {
value: JsonValue::List {
space0: "".to_string(),
elements: vec![
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("1".to_string()),
value: JsonValue::Number("1".to_string()),
space1: "".to_string()
},
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("2".to_string()),
value: JsonValue::Number("2".to_string()),
space1: "".to_string()
},
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("3".to_string()),
value: JsonValue::Number("3".to_string()),
space1: "".to_string()
},
],
@ -126,7 +124,7 @@ mod tests {
assert_eq!(
bytes(&mut reader).unwrap(),
Bytes::Json {
value: json::Value::Object {
value: JsonValue::Object {
space0: " ".to_string(),
elements: vec![],
}
@ -138,7 +136,7 @@ mod tests {
assert_eq!(
bytes(&mut reader).unwrap(),
Bytes::Json {
value: json::Value::Boolean(true)
value: JsonValue::Boolean(true)
}
);
assert_eq!(reader.state.cursor, 4);
@ -147,7 +145,7 @@ mod tests {
assert_eq!(
bytes(&mut reader).unwrap(),
Bytes::Json {
value: json::Value::String(Template {
value: JsonValue::String(Template {
quotes: true,
elements: vec![],
source_info: SourceInfo::init(1, 2, 1, 2),
@ -236,7 +234,7 @@ mod tests {
assert_eq!(
json_bytes(&mut reader).unwrap(),
Bytes::Json {
value: json::Value::Number("100".to_string())
value: JsonValue::Number("100".to_string())
}
);
}

View File

@ -1,4 +1,22 @@
use crate::core::ast::*;
/*
* hurl (https://hurl.dev)
* Copyright (C) 2020 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use crate::ast::*;
use super::combinators::*;
use super::error::*;
@ -64,7 +82,7 @@ fn cookiepath_attribute_name(reader: &mut Reader) -> ParseResult<'static, Cookie
#[cfg(test)]
mod tests {
use crate::core::common::{Pos, SourceInfo};
use crate::ast::{Pos, SourceInfo};
use super::*;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*
*/
use crate::core::common::{FormatError, Pos, SourceInfo};
use crate::ast::Pos;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
@ -58,68 +58,3 @@ pub enum ParseError {
InvalidCookieAttribute,
}
impl FormatError for Error {
fn source_info(&self) -> SourceInfo {
SourceInfo {
start: self.pos.clone(),
end: self.pos.clone(),
}
}
fn description(&self) -> String {
match self.clone().inner {
ParseError::Method { .. } => "Parsing Method".to_string(),
ParseError::Version { .. } => "Parsing Version".to_string(),
ParseError::Status { .. } => "Parsing Status".to_string(),
ParseError::Filename { .. } => "Parsing Filename".to_string(),
ParseError::Expecting { .. } => "Parsing literal".to_string(),
ParseError::Space { .. } => "Parsing space".to_string(),
ParseError::SectionName { .. } => "Parsing section name".to_string(),
ParseError::JsonpathExpr { .. } => "Parsing jsonpath expression".to_string(),
ParseError::XPathExpr { .. } => "Parsing xpath expression".to_string(),
ParseError::TemplateVariable { .. } => "Parsing template variable".to_string(),
ParseError::Json { .. } => "Parsing json".to_string(),
ParseError::Predicate { .. } => "Parsing predicate".to_string(),
ParseError::PredicateValue { .. } => "Parsing predicate value".to_string(),
ParseError::RegexExpr { .. } => "Parsing regex".to_string(),
ParseError::DuplicateSection { .. } => "Parsing section".to_string(),
ParseError::RequestSection { .. } => "Parsing section".to_string(),
ParseError::ResponseSection { .. } => "Parsing section".to_string(),
ParseError::EscapeChar { .. } => "Parsing escape character".to_string(),
ParseError::InvalidCookieAttribute { .. } => "Parsing cookie attribute".to_string(),
_ => format!("{:?}", self),
}
}
fn fixme(&self) -> String {
match self.inner.clone() {
ParseError::Method { .. } => "Available HTTP Method GET, POST, ...".to_string(),
ParseError::Version { .. } => "The http version must be 1.0, 1.1, 2 or *".to_string(),
ParseError::Status { .. } => "The http status is not valid".to_string(),
ParseError::Filename { .. } => "expecting a filename".to_string(),
ParseError::Expecting { value } => format!("expecting '{}'", value),
ParseError::Space { .. } => "expecting a space".to_string(),
ParseError::SectionName { name } => format!("the section {} is not valid", name),
ParseError::JsonpathExpr { .. } => "expecting a jsonpath expression".to_string(),
ParseError::XPathExpr { .. } => "expecting a xpath expression".to_string(),
ParseError::TemplateVariable { .. } => "expecting a variable".to_string(),
ParseError::Json { .. } => "json error".to_string(),
ParseError::Predicate { .. } => "expecting a predicate".to_string(),
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::EscapeChar { .. } => "The escaping sequence is not valid".to_string(),
ParseError::InvalidCookieAttribute { .. } => {
"The cookie attribute is not valid".to_string()
}
_ => format!("{:?}", self),
}
}
}

View File

@ -15,8 +15,7 @@
* limitations under the License.
*
*/
use crate::core::ast::*;
use crate::core::common::SourceInfo;
use crate::ast::*;
use super::error::*;
use super::primitives::*;
@ -80,7 +79,7 @@ fn variable_name(reader: &mut Reader) -> ParseResult<'static, Variable> {
#[cfg(test)]
mod tests {
use crate::core::common::Pos;
use crate::ast::Pos;
use super::*;

View File

@ -15,10 +15,7 @@
* limitations under the License.
*
*/
use crate::core::ast::Template;
use crate::core::common::Pos;
use crate::core::common::SourceInfo;
use crate::core::json;
use crate::ast::{JsonListElement, JsonObjectElement, JsonValue, Pos, SourceInfo, Template};
use super::combinators::*;
use super::error;
@ -27,7 +24,7 @@ use super::reader::*;
use super::template::*;
use super::ParseResult;
pub fn parse(reader: &mut Reader) -> ParseResult<'static, json::Value> {
pub fn parse(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
choice(
vec![
null_value,
@ -41,17 +38,17 @@ pub fn parse(reader: &mut Reader) -> ParseResult<'static, json::Value> {
)
}
fn null_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
fn null_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
try_literal("null", reader)?;
Ok(json::Value::Null {})
Ok(JsonValue::Null {})
}
fn boolean_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
fn boolean_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
let value = boolean(reader)?;
Ok(json::Value::Boolean(value))
Ok(JsonValue::Boolean(value))
}
fn string_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
fn string_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
try_literal("\"", reader)?;
let quotes = true;
let mut chars = vec![];
@ -80,7 +77,7 @@ fn string_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
elements,
source_info: SourceInfo { start, end },
};
Ok(json::Value::String(template))
Ok(JsonValue::String(template))
}
fn any_char(reader: &mut Reader) -> ParseResult<'static, (char, String, Pos)> {
@ -164,7 +161,7 @@ fn hex_value(reader: &mut Reader) -> ParseResult<'static, u32> {
Ok(value)
}
fn number_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
fn number_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
let start = reader.state.pos.clone();
let sign = match try_literal("-", reader) {
@ -223,13 +220,13 @@ fn number_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
"".to_string()
};
Ok(json::Value::Number(format!(
Ok(JsonValue::Number(format!(
"{}{}{}{}",
sign, integer, fraction, exponent
)))
}
fn list_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
fn list_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
try_literal("[", reader)?;
let space0 = whitespace(reader);
let mut elements = vec![];
@ -253,13 +250,13 @@ fn list_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
}
literal("]", reader)?;
Ok(json::Value::List { space0, elements })
Ok(JsonValue::List { space0, elements })
}
fn list_element(
_type: Option<String>,
reader: &mut Reader,
) -> ParseResult<'static, json::ListElement> {
) -> ParseResult<'static, JsonListElement> {
let save = reader.state.pos.clone();
let space0 = whitespace(reader);
let pos = reader.state.pos.clone();
@ -283,14 +280,14 @@ fn list_element(
}
}
let space1 = whitespace(reader);
Ok(json::ListElement {
Ok(JsonListElement {
space0,
value,
space1,
})
}
fn object_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
fn object_value(reader: &mut Reader) -> ParseResult<'static, JsonValue> {
try_literal("{", reader)?;
let space0 = whitespace(reader);
let mut elements = vec![];
@ -315,10 +312,10 @@ fn object_value(reader: &mut Reader) -> ParseResult<'static, json::Value> {
literal("}", reader)?;
Ok(json::Value::Object { space0, elements })
Ok(JsonValue::Object { space0, elements })
}
fn object_element(reader: &mut Reader) -> ParseResult<'static, json::ObjectElement> {
fn object_element(reader: &mut Reader) -> ParseResult<'static, JsonObjectElement> {
let space0 = whitespace(reader);
literal("\"", reader)?;
let name = key(reader)?;
@ -338,7 +335,7 @@ fn object_element(reader: &mut Reader) -> ParseResult<'static, json::ObjectEleme
}
};
let space3 = whitespace(reader);
Ok(json::ObjectElement {
Ok(JsonObjectElement {
space0,
name,
space1,
@ -371,9 +368,8 @@ fn whitespace(reader: &mut Reader) -> String {
#[cfg(test)]
mod tests {
use crate::core::ast::TemplateElement;
use super::*;
use crate::ast::*;
#[test]
fn test_parse_error() {
@ -393,7 +389,7 @@ mod tests {
#[test]
fn test_null_value() {
let mut reader = Reader::init("null");
assert_eq!(null_value(&mut reader).unwrap(), json::Value::Null {});
assert_eq!(null_value(&mut reader).unwrap(), JsonValue::Null {});
assert_eq!(reader.state.cursor, 4);
let mut reader = Reader::init("true");
@ -413,7 +409,7 @@ mod tests {
let mut reader = Reader::init("true");
assert_eq!(
boolean_value(&mut reader).unwrap(),
json::Value::Boolean(true)
JsonValue::Boolean(true)
);
assert_eq!(reader.state.cursor, 4);
@ -434,7 +430,7 @@ mod tests {
let mut reader = Reader::init("\"\"");
assert_eq!(
string_value(&mut reader).unwrap(),
json::Value::String(Template {
JsonValue::String(Template {
quotes: true,
elements: vec![],
source_info: SourceInfo::init(1, 2, 1, 2),
@ -443,16 +439,13 @@ mod tests {
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_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 {
JsonValue::String(Template {
quotes: true,
elements: vec![TemplateElement::String {
value: "{}".to_string(),
@ -598,49 +591,49 @@ mod tests {
let mut reader = Reader::init("100");
assert_eq!(
number_value(&mut reader).unwrap(),
json::Value::Number("100".to_string())
JsonValue::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())
JsonValue::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())
JsonValue::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())
JsonValue::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())
JsonValue::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())
JsonValue::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())
JsonValue::Number("1e-005".to_string())
);
assert_eq!(reader.state.cursor, 6);
}
@ -675,7 +668,7 @@ mod tests {
let mut reader = Reader::init("[]");
assert_eq!(
list_value(&mut reader).unwrap(),
json::Value::List {
JsonValue::List {
space0: "".to_string(),
elements: vec![]
}
@ -685,7 +678,7 @@ mod tests {
let mut reader = Reader::init("[ ]");
assert_eq!(
list_value(&mut reader).unwrap(),
json::Value::List {
JsonValue::List {
space0: " ".to_string(),
elements: vec![]
}
@ -695,11 +688,11 @@ mod tests {
let mut reader = Reader::init("[true]");
assert_eq!(
list_value(&mut reader).unwrap(),
json::Value::List {
JsonValue::List {
space0: "".to_string(),
elements: vec![json::ListElement {
elements: vec![JsonListElement {
space0: "".to_string(),
value: json::Value::Boolean(true),
value: JsonValue::Boolean(true),
space1: "".to_string(),
}],
}
@ -743,9 +736,9 @@ mod tests {
let mut reader = Reader::init("true");
assert_eq!(
list_element(None, &mut reader).unwrap(),
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Boolean(true),
value: JsonValue::Boolean(true),
space1: "".to_string(),
}
);
@ -781,7 +774,7 @@ mod tests {
let mut reader = Reader::init("{}");
assert_eq!(
object_value(&mut reader).unwrap(),
json::Value::Object {
JsonValue::Object {
space0: "".to_string(),
elements: vec![]
}
@ -791,7 +784,7 @@ mod tests {
let mut reader = Reader::init("{ }");
assert_eq!(
object_value(&mut reader).unwrap(),
json::Value::Object {
JsonValue::Object {
space0: " ".to_string(),
elements: vec![]
}
@ -801,14 +794,14 @@ mod tests {
let mut reader = Reader::init("{\n \"a\": true\n}");
assert_eq!(
object_value(&mut reader).unwrap(),
json::Value::Object {
JsonValue::Object {
space0: "\n ".to_string(),
elements: vec![json::ObjectElement {
elements: vec![JsonObjectElement {
space0: "".to_string(),
name: "a".to_string(),
space1: "".to_string(),
space2: " ".to_string(),
value: json::Value::Boolean(true),
value: JsonValue::Boolean(true),
space3: "\n".to_string(),
}],
}
@ -841,12 +834,12 @@ mod tests {
let mut reader = Reader::init("\"a\": true");
assert_eq!(
object_element(&mut reader).unwrap(),
json::ObjectElement {
JsonObjectElement {
space0: "".to_string(),
name: "a".to_string(),
space1: "".to_string(),
space2: " ".to_string(),
value: json::Value::Boolean(true),
value: JsonValue::Boolean(true),
space3: "".to_string(),
}
);

View File

@ -15,25 +15,7 @@
* limitations under the License.
*
*/
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 query;
pub mod reader;
mod sections;
mod string;
mod template;
mod xml;
use crate::ast::HurlFile;
pub type ParseResult<'a, T> = std::result::Result<T, Error>;
pub type ParseFunc<'a, T> = fn(&mut reader::Reader) -> ParseResult<'a, T>;
@ -42,3 +24,25 @@ pub fn parse_hurl_file(s: &str) -> ParseResult<'static, HurlFile> {
let mut reader = reader::Reader::init(s);
parsers::hurl_file(&mut reader)
}
pub use self::error::{Error, ParseError};
pub use self::json::parse as parse_json;
pub use self::reader::Reader;
pub use self::template::templatize;
mod base64;
mod bytes;
mod combinators;
mod cookiepath;
mod error;
mod expr;
mod json;
mod parsers;
mod predicate;
mod primitives;
mod query;
mod reader;
mod sections;
mod string;
mod template;
mod xml;

View File

@ -15,8 +15,7 @@
* limitations under the License.
*
*/
use crate::core::ast::*;
use crate::core::common::SourceInfo;
use crate::ast::*;
use super::bytes::*;
use super::combinators::*;
@ -300,9 +299,6 @@ fn body(reader: &mut Reader) -> ParseResult<'static, Body> {
#[cfg(test)]
mod tests {
use crate::core::common::Pos;
use crate::core::json;
use super::*;
#[test]
@ -499,22 +495,22 @@ mod tests {
assert_eq!(
r.body.unwrap().value,
Bytes::Json {
value: json::Value::List {
value: JsonValue::List {
space0: "".to_string(),
elements: vec![
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("1".to_string()),
value: JsonValue::Number("1".to_string()),
space1: "".to_string(),
},
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("2".to_string()),
value: JsonValue::Number("2".to_string()),
space1: "".to_string(),
},
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("3".to_string()),
value: JsonValue::Number("3".to_string()),
space1: "".to_string(),
},
],
@ -528,7 +524,7 @@ mod tests {
assert_eq!(
r.body.unwrap().value,
Bytes::Json {
value: json::Value::String(Template {
value: JsonValue::String(Template {
quotes: true,
elements: vec![TemplateElement::String {
value: "Hello".to_string(),
@ -545,7 +541,7 @@ mod tests {
assert_eq!(
r.body.unwrap().value,
Bytes::Json {
value: json::Value::Number("100".to_string())
value: JsonValue::Number("100".to_string())
}
);
}
@ -737,22 +733,22 @@ mod tests {
assert_eq!(
b.value,
Bytes::Json {
value: json::Value::List {
value: JsonValue::List {
space0: "".to_string(),
elements: vec![
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("1".to_string()),
value: JsonValue::Number("1".to_string()),
space1: "".to_string(),
},
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("2".to_string()),
value: JsonValue::Number("2".to_string()),
space1: "".to_string(),
},
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("3".to_string()),
value: JsonValue::Number("3".to_string()),
space1: "".to_string(),
},
],
@ -767,7 +763,7 @@ mod tests {
assert_eq!(
b.value,
Bytes::Json {
value: json::Value::Object {
value: JsonValue::Object {
space0: "".to_string(),
elements: vec![],
}
@ -781,7 +777,7 @@ mod tests {
assert_eq!(
b.value,
Bytes::Json {
value: json::Value::Object {
value: JsonValue::Object {
space0: "".to_string(),
elements: vec![],
}

View File

@ -1,5 +1,22 @@
use crate::core::ast::*;
use crate::core::common::SourceInfo;
/*
* hurl (https://hurl.dev)
* Copyright (C) 2020 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use crate::ast::*;
use super::combinators::*;
use super::error::*;
@ -210,7 +227,7 @@ fn predicate_value(reader: &mut Reader) -> ParseResult<'static, PredicateValue>
#[cfg(test)]
mod tests {
use crate::core::common::Pos;
use crate::ast::Pos;
use super::*;

View File

@ -15,8 +15,7 @@
* limitations under the License.
*
*/
use crate::core::ast::*;
use crate::core::common::SourceInfo;
use crate::ast::*;
use super::combinators::*;
use super::error::*;
@ -519,7 +518,7 @@ pub fn hex_digit(reader: &mut Reader) -> ParseResult<'static, u32> {
#[cfg(test)]
mod tests {
use crate::core::common::Pos;
use crate::ast::Pos;
use super::*;

View File

@ -1,6 +1,21 @@
use crate::core::ast::*;
use crate::core::common::Pos;
use crate::core::common::SourceInfo;
/*
* hurl (https://hurl.dev)
* Copyright (C) 2020 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use crate::ast::*;
use super::combinators::*;
use super::cookiepath::cookiepath;

View File

@ -15,7 +15,7 @@
* limitations under the License.
*
*/
use crate::core::common::Pos;
use crate::ast::Pos;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Reader {

View File

@ -15,9 +15,7 @@
* limitations under the License.
*
*/
use crate::core::ast::*;
use crate::core::common::Pos;
use crate::core::common::SourceInfo;
use crate::ast::*;
use super::combinators::*;
use super::error::*;
@ -368,7 +366,7 @@ fn assert(reader: &mut Reader) -> ParseResult<'static, Assert> {
#[cfg(test)]
mod tests {
use super::*;
use crate::core::common::Pos;
use crate::ast::Pos;
#[test]
fn test_section_name() {

View File

@ -15,9 +15,7 @@
* limitations under the License.
*
*/
use crate::core::ast::*;
use crate::core::common::Pos;
use crate::core::common::SourceInfo;
use crate::ast::*;
use super::combinators::*;
use super::error::*;

View File

@ -16,9 +16,7 @@
*
*/
use crate::core::ast::TemplateElement;
use crate::core::common::Pos;
use crate::core::common::SourceInfo;
use crate::ast::{Pos, SourceInfo, TemplateElement};
use super::error;
use super::expr;
@ -132,8 +130,7 @@ pub fn templatize(encoded_string: EncodedString) -> ParseResult<'static, Vec<Tem
#[cfg(test)]
mod tests {
use crate::core::ast::Expr;
use crate::core::ast::{Variable, Whitespace};
use crate::ast::{Expr, Variable, Whitespace};
use super::*;

View File

@ -17,7 +17,7 @@
*/
use sxd_document::parser;
use crate::core::common::Pos;
use crate::ast::Pos;
use super::error::*;
use super::reader::Reader;

View File

@ -18,12 +18,12 @@
use std::collections::HashMap;
use crate::core::common::Value;
use crate::ast::*;
use crate::http;
use super::super::core::ast::*;
use super::core::*;
use super::core::{Error, RunnerError};
use super::value::Value;
impl AssertResult {
pub fn fail(self) -> bool {
@ -149,7 +149,7 @@ impl Assert {
#[cfg(test)]
pub mod tests {
use crate::core::common::SourceInfo;
use crate::ast::SourceInfo;
use super::super::query;
use super::*;

View File

@ -21,10 +21,10 @@ use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use crate::core::common::Value;
use crate::ast::*;
use super::super::core::ast::*;
use super::core::{Error, RunnerError};
use super::value::Value;
impl Body {
pub fn eval(
@ -87,7 +87,7 @@ impl Bytes {
#[cfg(test)]
mod tests {
use crate::core::common::SourceInfo;
use crate::ast::SourceInfo;
use super::*;

View File

@ -15,16 +15,15 @@
* limitations under the License.
*
*/
use regex::Regex;
use std::collections::HashMap;
use regex::Regex;
use crate::core::common::Value;
use crate::ast::*;
use crate::http;
use super::super::core::ast::*;
use super::core::RunnerError;
use super::core::{CaptureResult, Error};
use super::value::Value;
impl Capture {
pub fn eval(
@ -101,7 +100,7 @@ impl Subquery {
#[cfg(test)]
pub mod tests {
use crate::core::common::{Pos, SourceInfo};
use crate::ast::{Pos, SourceInfo};
use super::*;

View File

@ -17,14 +17,17 @@
*/
use std::collections::HashMap;
use crate::core::common::{FormatError, SourceInfo, Value};
use crate::ast::SourceInfo;
use crate::http;
use super::value::Value;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RunnerOptions {
pub fail_fast: bool,
pub variables: HashMap<String, String>,
pub to_entry: Option<usize>,
pub context_dir: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -179,118 +182,4 @@ pub enum RunnerError {
},
}
impl FormatError for Error {
fn source_info(&self) -> SourceInfo {
self.clone().source_info
}
fn description(&self) -> String {
match &self.inner {
RunnerError::InvalidURL(..) => "Invalid url".to_string(),
RunnerError::TemplateVariableNotDefined { .. } => "Undefined Variable".to_string(),
RunnerError::VariableNotDefined { .. } => "Undefined Variable".to_string(),
RunnerError::HttpConnection { .. } => "Http Connection".to_string(),
RunnerError::CouldNotResolveProxyName => "Http Connection".to_string(),
RunnerError::CouldNotResolveHost => "Http Connection".to_string(),
RunnerError::FailToConnect => "Http Connection".to_string(),
RunnerError::Timeout => "Http Connection".to_string(),
RunnerError::TooManyRedirect => "Http Connection".to_string(),
RunnerError::CouldNotParseResponse => "Http Connection".to_string(),
RunnerError::SSLCertificate => "Http Connection".to_string(),
RunnerError::PredicateValue { .. } => "Assert - Predicate Value Failed".to_string(),
RunnerError::InvalidRegex {} => "Invalid regex".to_string(),
RunnerError::FileReadAccess { .. } => "File ReadAccess".to_string(),
RunnerError::QueryInvalidXml { .. } => "Invalid XML".to_string(),
RunnerError::QueryInvalidXpathEval {} => "Invalid xpath expression".to_string(),
RunnerError::QueryHeaderNotFound {} => "Header not Found".to_string(),
RunnerError::QueryCookieNotFound {} => "Cookie not Found".to_string(),
RunnerError::AssertHeaderValueError { .. } => "Assert Header Value".to_string(),
RunnerError::AssertBodyValueError { .. } => "Assert Body Value".to_string(),
RunnerError::AssertVersion { .. } => "Assert Http Version".to_string(),
RunnerError::AssertStatus { .. } => "Assert Status".to_string(),
RunnerError::QueryInvalidJson { .. } => "Invalid Json".to_string(),
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::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::HttpConnection { url, message } => {
format!("can not connect to {} ({})", url, message)
}
RunnerError::CouldNotResolveProxyName => "Could not resolve proxy name".to_string(),
RunnerError::CouldNotResolveHost => "Could not resolve host".to_string(),
RunnerError::FailToConnect => "Fail to connect".to_string(),
RunnerError::Timeout => "Timeout has been reached".to_string(),
RunnerError::TooManyRedirect => "Too many redirect".to_string(),
RunnerError::CouldNotParseResponse => "Could not parse response".to_string(),
RunnerError::SSLCertificate => "SSl Certificate".to_string(),
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::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::NoQueryResult { .. } => "The query didn't return any result".to_string(),
}
}
}
// endregion

View File

@ -18,15 +18,13 @@
use std::collections::HashMap;
use std::time::Instant;
use crate::core::ast::*;
use crate::core::common::SourceInfo;
use crate::core::common::Value;
use crate::ast::*;
use crate::http;
use crate::http::HttpError;
use super::core::*;
use super::core::{Error, RunnerError};
use crate::format::logger::Logger;
use crate::http::HttpError;
use super::value::Value;
/// Run an entry with the hurl http client
///
@ -52,7 +50,8 @@ pub fn run(
entry_index: usize,
variables: &mut HashMap<String, Value>,
context_dir: String,
logger: &Logger,
log_verbose: &impl Fn(&str),
log_error_message: &impl Fn(bool, &str),
) -> EntryResult {
let http_request = match entry.clone().request.eval(variables, context_dir.clone()) {
Ok(r) => r,
@ -63,15 +62,13 @@ pub fn run(
captures: vec![],
asserts: vec![],
errors: vec![error],
time_in_ms: 0,
};
}
};
logger
.verbose("------------------------------------------------------------------------------");
logger.verbose(format!("executing entry {}", entry_index + 1).as_str());
log_verbose("------------------------------------------------------------------------------");
log_verbose(format!("executing entry {}", entry_index + 1).as_str());
//
// Experimental features
@ -82,20 +79,23 @@ pub fn run(
if let Ok(cookie) = http::Cookie::from_str(s.as_str()) {
http_client.add_cookie(cookie);
} else {
logger.verbose(format!("cookie string can not be parsed: '{}'", s).as_str());
log_error_message(
true,
format!("cookie string can not be parsed: '{}'", s).as_str(),
);
}
}
if entry.request.clear_cookie_storage() {
http_client.clear_cookie_storage();
}
logger.verbose("");
logger.verbose("Cookie store:");
log_verbose("");
log_verbose("Cookie store:");
for cookie in http_client.get_cookie_storage() {
logger.verbose(cookie.to_string().as_str());
log_verbose(cookie.to_string().as_str());
}
logger.verbose("");
log_request(logger, &http_request);
log_verbose("");
log_request(log_verbose, &http_request);
let start = Instant::now();
let http_response = match http_client.execute(&http_request, 0) {
@ -134,7 +134,7 @@ pub fn run(
};
let time_in_ms = start.elapsed().as_millis();
logger.verbose(format!("Response Time: {}ms", time_in_ms).as_str());
log_verbose(format!("Response Time: {}ms", time_in_ms).as_str());
let captures = match entry.response.clone() {
None => vec![],
@ -177,13 +177,13 @@ pub fn run(
.collect();
if !captures.is_empty() {
logger.verbose("Captures");
log_verbose("Captures");
for capture in captures.clone() {
logger.verbose(format!("{}: {}", capture.name, capture.value).as_str());
log_verbose(format!("{}: {}", capture.name, capture.value).as_str());
}
}
logger.verbose("");
log_verbose("");
EntryResult {
request: Some(http_request),
@ -195,39 +195,39 @@ pub fn run(
}
}
pub fn log_request(logger: &Logger, request: &http::Request) {
logger.verbose("Request");
logger.verbose(format!("{} {}", request.method, request.url).as_str());
pub fn log_request(log_verbose: impl Fn(&str), request: &http::Request) {
log_verbose("Request");
log_verbose(format!("{} {}", request.method, request.url).as_str());
for header in request.headers.clone() {
logger.verbose(header.to_string().as_str());
log_verbose(header.to_string().as_str());
}
if !request.querystring.is_empty() {
logger.verbose("[QueryStringParams]");
log_verbose("[QueryStringParams]");
for param in request.querystring.clone() {
logger.verbose(param.to_string().as_str());
log_verbose(param.to_string().as_str());
}
}
if !request.form.is_empty() {
logger.verbose("[FormParams]");
log_verbose("[FormParams]");
for param in request.form.clone() {
logger.verbose(param.to_string().as_str());
log_verbose(param.to_string().as_str());
}
}
if !request.multipart.is_empty() {
logger.verbose("[MultipartFormData]");
log_verbose("[MultipartFormData]");
for param in request.multipart.clone() {
logger.verbose(param.to_string().as_str());
log_verbose(param.to_string().as_str());
}
}
if !request.cookies.is_empty() {
logger.verbose("[Cookies]");
log_verbose("[Cookies]");
for cookie in request.cookies.clone() {
logger.verbose(cookie.to_string().as_str());
log_verbose(cookie.to_string().as_str());
}
}
if let Some(s) = request.content_type.clone() {
logger.verbose("");
logger.verbose(format!("implicit content-type={}", s).as_str());
log_verbose("");
log_verbose(format!("implicit content-type={}", s).as_str());
}
logger.verbose("");
log_verbose("");
}

View File

@ -17,10 +17,10 @@
*/
use std::collections::HashMap;
use crate::core::ast::Expr;
use crate::core::common::Value;
use crate::ast::Expr;
use super::core::{Error, RunnerError};
use super::value::Value;
impl Expr {
pub fn eval(self, variables: &HashMap<String, Value>) -> Result<Value, Error> {

View File

@ -16,10 +16,12 @@
*
*/
use encoding::{DecoderTrap, EncodingRef};
use crate::http::Response;
use super::cookie::ResponseCookie;
use super::core::RunnerError;
use crate::http::Response;
use encoding::{DecoderTrap, EncodingRef};
impl Response {
pub fn cookies(&self) -> Vec<ResponseCookie> {

View File

@ -18,14 +18,12 @@
use std::collections::HashMap;
use std::time::Instant;
use crate::core::ast::*;
use crate::core::common::Value;
use crate::ast::*;
use crate::http;
use super::super::format;
use super::core::*;
use super::entry;
use crate::core::common::FormatError;
use super::value::Value;
/// Run a Hurl file with the hurl http client
///
@ -33,8 +31,8 @@ use crate::core::common::FormatError;
///
/// ```
/// use hurl::http;
/// use hurl::parser;
/// use hurl::runner;
/// use hurl::format;
///
/// // Parse Hurl file
/// let filename = "sample.hurl".to_string();
@ -42,7 +40,15 @@ use crate::core::common::FormatError;
/// GET http://localhost:8000/hello
/// HTTP/1.0 200
/// "#;
/// let hurl_file = hurl::parser::parse_hurl_file(s).unwrap();
/// let hurl_file = parser::parse_hurl_file(s).unwrap();
///
/// // create loggers (function pointer or closure)
/// fn log_verbose(message: &str) { eprintln!("* {}", message); }
/// fn log_error_message(_warning:bool, message: &str) { eprintln!("{}", message); }
/// fn log_error(error: &runner::Error, _warning: bool) { eprintln!("* {:#?}", error); }
/// let log_verbose: fn(&str) = log_verbose;
/// let log_error_message: fn(bool, &str) = log_error_message;
/// let log_error: fn(&runner::Error, bool) = log_error;
///
/// // Create an http client
/// let options = http::ClientOptions {
@ -60,42 +66,35 @@ use crate::core::common::FormatError;
///
/// // Define runner options
/// let variables = std::collections::HashMap::new();
/// let options = runner::core::RunnerOptions {
/// let options = runner::RunnerOptions {
/// fail_fast: false,
/// variables,
/// to_entry: None,
/// context_dir: "current_dir".to_string(),
/// };
///
/// // create a logger
/// // It needs the text input as lines for reporting errors
/// let lines = regex::Regex::new(r"\n|\r\n").unwrap().split(&s).map(|l| l.to_string()).collect();
/// let logger = format::logger::Logger {
/// filename: Some(filename.clone()),
/// lines,
/// verbose: false,
/// color: false
/// };
///
/// // Run the hurl file
/// let context_dir = "current_dir".to_string();
/// let hurl_results = runner::file::run(
/// let hurl_results = runner::run_hurl_file(
/// hurl_file,
/// &mut client,
/// filename,
/// context_dir,
/// options,
/// logger
/// &log_verbose,
/// &log_error_message,
/// &log_error,
/// );
/// assert!(hurl_results.success);
///
/// ```
///
pub fn run(
hurl_file: HurlFile,
http_client: &mut http::Client,
filename: String,
context_dir: String,
options: RunnerOptions,
logger: format::logger::Logger,
log_verbose: &impl Fn(&str),
log_error_message: &impl Fn(bool, &str),
log_error: &impl Fn(&Error, bool),
) -> HurlResult {
let mut entries = vec![];
let mut variables = HashMap::default();
@ -124,23 +123,14 @@ pub fn run(
http_client,
entry_index,
&mut variables,
context_dir.clone(),
&logger,
options.context_dir.clone(),
&log_verbose,
&log_error_message,
);
entries.push(entry_result.clone());
for e in entry_result.errors.clone() {
let error = format::error::Error {
source_info: e.clone().source_info,
description: e.clone().description(),
fixme: e.fixme(),
lines: vec![],
filename: "".to_string(),
warning: false,
color: false,
};
logger.clone().error(&error);
log_error(&e, false);
}
if options.fail_fast && !entry_result.errors.is_empty() {
break;
}

View File

@ -17,22 +17,22 @@
*/
use std::collections::HashMap;
use crate::core::common::Value;
use crate::core::json;
use crate::ast::{JsonListElement, JsonObjectElement, JsonValue};
use super::core::Error;
use super::value::Value;
impl json::Value {
impl JsonValue {
pub fn eval(self, variables: &HashMap<String, Value>) -> Result<String, Error> {
match self {
json::Value::Null {} => Ok("null".to_string()),
json::Value::Number(s) => Ok(s),
json::Value::String(template) => {
JsonValue::Null {} => Ok("null".to_string()),
JsonValue::Number(s) => Ok(s),
JsonValue::String(template) => {
let s = template.eval(variables)?;
Ok(format!("\"{}\"", s))
}
json::Value::Boolean(v) => Ok(v.to_string()),
json::Value::List { space0, elements } => {
JsonValue::Boolean(v) => Ok(v.to_string()),
JsonValue::List { space0, elements } => {
let mut elems_string = vec![];
for element in elements {
let s = element.eval(variables)?;
@ -40,7 +40,7 @@ impl json::Value {
}
Ok(format!("[{}{}]", space0, elems_string.join(",")))
}
json::Value::Object { space0, elements } => {
JsonValue::Object { space0, elements } => {
let mut elems_string = vec![];
for element in elements {
let s = element.eval(variables)?;
@ -52,14 +52,14 @@ impl json::Value {
}
}
impl json::ListElement {
impl JsonListElement {
pub fn eval(self, variables: &HashMap<String, Value>) -> Result<String, Error> {
let s = self.value.eval(variables)?;
Ok(format!("{}{}{}", self.space0, s, self.space1))
}
}
impl json::ObjectElement {
impl JsonObjectElement {
pub fn eval(self, variables: &HashMap<String, Value>) -> Result<String, Error> {
let value = self.value.eval(variables)?;
Ok(format!(
@ -71,32 +71,30 @@ impl json::ObjectElement {
#[cfg(test)]
mod tests {
use crate::core::ast::{Template, TemplateElement};
use crate::core::common::SourceInfo;
use super::super::core::RunnerError;
use super::*;
use crate::ast::*;
#[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(),
JsonValue::Null {}.eval(&variables).unwrap(),
"null".to_string()
);
assert_eq!(
json::Value::Number("3.14".to_string())
JsonValue::Number("3.14".to_string())
.eval(&variables)
.unwrap(),
"3.14".to_string()
);
assert_eq!(
json::Value::Boolean(false).eval(&variables).unwrap(),
JsonValue::Boolean(false).eval(&variables).unwrap(),
"false".to_string()
);
assert_eq!(
json::tests::hello_world_value().eval(&variables).unwrap(),
json_hello_world_value().eval(&variables).unwrap(),
"\"Hello Bob!\"".to_string()
);
}
@ -104,10 +102,7 @@ mod tests {
#[test]
fn test_error() {
let variables = HashMap::new();
let error = json::tests::hello_world_value()
.eval(&variables)
.err()
.unwrap();
let error = json_hello_world_value().eval(&variables).err().unwrap();
assert_eq!(error.source_info, SourceInfo::init(1, 15, 1, 19));
assert_eq!(
error.inner,
@ -122,7 +117,7 @@ mod tests {
let mut variables = HashMap::new();
variables.insert("name".to_string(), Value::String("Bob".to_string()));
assert_eq!(
json::Value::List {
JsonValue::List {
space0: "".to_string(),
elements: vec![],
}
@ -132,22 +127,22 @@ mod tests {
);
assert_eq!(
json::Value::List {
JsonValue::List {
space0: "".to_string(),
elements: vec![
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::Number("1".to_string()),
value: JsonValue::Number("1".to_string()),
space1: "".to_string()
},
json::ListElement {
JsonListElement {
space0: " ".to_string(),
value: json::Value::Number("-2".to_string()),
value: JsonValue::Number("-2".to_string()),
space1: "".to_string()
},
json::ListElement {
JsonListElement {
space0: " ".to_string(),
value: json::Value::Number("3.0".to_string()),
value: JsonValue::Number("3.0".to_string()),
space1: "".to_string()
},
],
@ -166,17 +161,17 @@ mod tests {
source_info: SourceInfo::init(0, 0, 0, 0),
};
assert_eq!(
json::Value::List {
JsonValue::List {
space0: "".to_string(),
elements: vec![
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: json::Value::String(template),
value: JsonValue::String(template),
space1: "".to_string()
},
json::ListElement {
JsonListElement {
space0: " ".to_string(),
value: json::tests::hello_world_value(),
value: json_hello_world_value(),
space1: "".to_string()
},
],
@ -191,7 +186,7 @@ mod tests {
fn test_object_value() {
let variables = HashMap::new();
assert_eq!(
json::Value::Object {
JsonValue::Object {
space0: "".to_string(),
elements: vec![]
}
@ -200,7 +195,7 @@ mod tests {
"{}".to_string()
);
assert_eq!(
json::tests::person_value().eval(&variables).unwrap(),
json_person_value().eval(&variables).unwrap(),
r#"{
"firstName": "John"
}"#

View File

@ -16,9 +16,10 @@
*
*/
use crate::http::*;
use super::cookie::*;
use super::core::*;
use crate::http::*;
type ParseError = String;
@ -281,6 +282,7 @@ pub fn parse_request_cookie(value: serde_json::Value) -> Result<RequestCookie, P
}
}
#[allow(dead_code)]
pub fn parse_response_cookie(value: serde_json::Value) -> Result<ResponseCookie, ParseError> {
if let serde_json::Value::Object(map) = value {
let name = match map.get("name") {
@ -373,6 +375,7 @@ fn parse_version(s: String) -> Result<Version, ParseError> {
#[cfg(test)]
mod tests {
use super::*;
use crate::runner::value::Value;
#[test]
fn test_parse_request() {
@ -514,4 +517,24 @@ mod tests {
Version::Http10
);
}
#[test]
fn test_parse_value() {
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)
);
}
}

View File

@ -23,6 +23,7 @@ use crate::http::*;
use super::cookie::*;
use super::core::*;
use super::value::Value;
impl Serialize for HurlResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@ -225,3 +226,35 @@ impl Serialize for Cookie {
state.end()
}
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::Bool(v) => serializer.serialize_bool(*v),
Value::Integer(v) => serializer.serialize_i64(*v),
Value::Float(i, d) => {
let value = *i as f64 + (*d as f64) / 1_000_000_000_000_000_000.0;
serializer.serialize_f64(value)
}
Value::String(s) => serializer.serialize_str(s),
Value::List(values) => serializer.collect_seq(values),
Value::Object(values) => serializer.collect_map(values.iter().map(|(k, v)| (k, v))),
Value::Nodeset(size) => {
let size = *size as i64;
serializer.collect_map(vec![
("type", serde_json::Value::String("nodeset".to_string())),
("size", serde_json::Value::from(size)),
])
}
Value::Bytes(v) => {
let encoded = base64::encode(v);
serializer.serialize_str(&encoded)
}
Value::Null => serializer.serialize_none(),
Value::Unit => todo!("how to serialize that in json?"),
}
}
}

View File

@ -23,22 +23,27 @@
//!
//!
pub use self::core::{Error, HurlResult, RunnerError, RunnerOptions};
pub use self::hurl_file::run as run_hurl_file;
pub use self::log_deserialize::parse_results as deserialize_results;
mod assert;
mod body;
mod capture;
mod cookie;
pub mod core;
mod core;
mod entry;
mod expr;
pub mod file;
mod http_response;
mod hurl_file;
mod json;
pub mod log_deserialize;
pub mod log_serialize;
mod log_deserialize;
mod log_serialize;
mod multipart;
mod predicate;
mod query;
pub mod request;
mod request;
mod response;
mod template;
mod value;
mod xpath;

View File

@ -25,11 +25,11 @@ use std::io::prelude::*;
use std::io::Read;
use std::path::Path;
use crate::core::ast::*;
use crate::core::common::Value;
use crate::ast::*;
use crate::http;
use super::core::{Error, RunnerError};
use super::value::Value;
impl MultipartParam {
pub fn eval(
@ -140,7 +140,7 @@ impl FileValue {
#[cfg(test)]
mod tests {
use crate::core::common::SourceInfo;
use crate::ast::SourceInfo;
use super::*;

View File

@ -15,41 +15,14 @@
* limitations under the License.
*
*/
use regex::Regex;
use std::collections::HashMap;
use regex::Regex;
use crate::ast::*;
use crate::core::common::Value;
use crate::core::common::{Pos, SourceInfo};
use super::super::core::ast::*;
use super::core::*;
use super::core::{Error, RunnerError};
// equals 10 function return ()
// not equals 10
// countEquals 3 return () => ok PredicateExpectedError
// not countEquals nok
// 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
// ^^^^^^^^^^^^^ Type does not matched with value return by query
// xpath cont(//user) equals 10
// ^^^^^^^^^^^^^ actual value is 9
// xpath cont(//user) greaterThan 10
// ^^^^^^^^^^^^^^ actual value is 9
// Predicate
// 2 evals
// 1) eval template
// 2) eval predicate
// equals template becomes and equals string
use super::value::Value;
impl Predicate {
pub fn eval(self, variables: &HashMap<String, Value>, value: Option<Value>) -> PredicateResult {

View File

@ -16,17 +16,16 @@
*
*/
use regex::Regex;
use std::collections::HashMap;
use regex::Regex;
use crate::core::common::Value;
use crate::ast::*;
use crate::http;
use crate::jsonpath;
use super::super::core::ast::*;
use super::cookie;
use super::core::{Error, RunnerError};
use super::value::Value;
use super::xpath;
pub type QueryResult = Result<Option<Value>, Error>;
@ -142,7 +141,7 @@ impl Query {
// }
// };
// Using your own json implem
let query = match jsonpath::parser::parse::parse(value.as_str()) {
let query = match jsonpath::parse(value.as_str()) {
Ok(q) => q,
Err(_) => {
return Err(Error {
@ -249,10 +248,38 @@ impl CookieAttributeName {
}
}
impl Value {
pub fn from_json(value: &serde_json::Value) -> Value {
match value {
serde_json::Value::Null => Value::Null,
serde_json::Value::Bool(bool) => Value::Bool(*bool),
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::Object(map) => {
let mut elements = vec![];
for (key, value) in map {
elements.push((key.to_string(), Value::from_json(value)));
//
}
Value::Object(elements)
}
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::core::common::{Pos, SourceInfo};
use crate::ast::{Pos, SourceInfo};
pub fn xpath_invalid_query() -> Query {
// xpath ???

View File

@ -23,11 +23,11 @@ use std::collections::HashMap;
#[allow(unused)]
use std::io::prelude::*;
use crate::core::ast::*;
use crate::core::common::Value;
use crate::ast::*;
use crate::http;
use super::core::Error;
use super::value::Value;
impl Request {
pub fn eval(
@ -61,7 +61,7 @@ impl Request {
form.push(http::Param { name, value });
}
// if !self.clone().form_params().is_empty() {
// headers.push(http::core::Header {
// headers.push(http::ast::Header {
// name: String::from("Content-Type"),
// value: String::from("application/x-www-form-urlencoded"),
// });
@ -105,7 +105,7 @@ impl Request {
// if self.content_type().is_none() {
// if let Some(body) = self.body {
// if let Bytes::Json { .. } = body.value {
// headers.push(http::core::Header {
// headers.push(http::ast::Header {
// name: String::from("Content-Type"),
// value: String::from("application/json"),
// });
@ -208,7 +208,7 @@ impl Method {
#[cfg(test)]
mod tests {
use crate::core::common::SourceInfo;
use crate::ast::SourceInfo;
use super::super::core::RunnerError;
use super::*;

View File

@ -17,14 +17,11 @@
*/
use std::collections::HashMap;
use crate::core::common::Value;
use crate::core::common::{Pos, SourceInfo};
use crate::ast::*;
use crate::http;
use crate::runner::core::RunnerError;
use super::super::core::ast::*;
use super::core::Error;
use super::core::*;
use super::value::Value;
impl Response {
pub fn eval_asserts(

View File

@ -17,10 +17,10 @@
*/
use std::collections::HashMap;
use crate::core::ast::*;
use crate::core::common::Value;
use crate::ast::*;
use super::core::{Error, RunnerError};
use super::value::Value;
impl Template {
pub fn eval(self, variables: &HashMap<String, Value>) -> Result<String, Error> {
@ -80,7 +80,7 @@ impl Value {
#[cfg(test)]
mod tests {
use crate::core::common::SourceInfo;
use crate::ast::SourceInfo;
use super::*;

126
src/runner/value.rs Normal file
View File

@ -0,0 +1,126 @@
/*
* hurl (https://hurl.dev)
* Copyright (C) 2020 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use std::fmt;
///
/// Type system used in hurl
/// Values are used by queries, captures, asserts and predicates
///
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Value {
Unit,
Bool(bool),
Integer(i64),
// can not use simply Float(f64)
// the trait `std::cmp::Eq` is not implemented for `f64`
// integer part, decimal part (9 digits) TODO Clarify your custom type
Float(i64, u64),
String(String),
List(Vec<Value>),
Object(Vec<(String, Value)>),
Nodeset(usize),
Bytes(Vec<u8>),
Null,
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let value = match self {
Value::Integer(x) => x.to_string(),
Value::Bool(x) => x.to_string(),
Value::Float(int, dec) => format!("{}.{}", int, dec),
Value::String(x) => x.clone(),
Value::List(values) => {
let values: Vec<String> = values.iter().map(|e| e.to_string()).collect();
format!("[{}]", values.join(","))
}
Value::Object(_) => "Object()".to_string(),
Value::Nodeset(x) => format!("Nodeset{:?}", x),
Value::Bytes(x) => format!("Bytes({:x?})", x),
Value::Null => "Null".to_string(),
Value::Unit => "Unit".to_string(),
};
write!(f, "{}", value)
}
}
impl Value {
pub fn _type(&self) -> String {
match self {
Value::Integer(_) => "integer".to_string(),
Value::Bool(_) => "boolean".to_string(),
Value::Float(_, _) => "float".to_string(),
Value::String(_) => "string".to_string(),
Value::List(_) => "list".to_string(),
Value::Object(_) => "object".to_string(),
Value::Nodeset(_) => "nodeset".to_string(),
Value::Bytes(_) => "bytes".to_string(),
Value::Null => "null".to_string(),
Value::Unit => "unit".to_string(),
}
}
pub fn from_f64(value: f64) -> Value {
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)
}
pub fn is_scalar(&self) -> bool {
match self {
Value::Nodeset(_) | Value::List(_) => false,
_ => true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
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)
);
}
#[test]
fn test_is_scalar() {
assert_eq!(Value::Integer(1).is_scalar(), true);
assert_eq!(Value::List(vec![]).is_scalar(), false);
}
}

View File

@ -15,12 +15,13 @@
* limitations under the License.
*
*/
// unique entry point to libxml
extern crate libxml;
use std::ffi::CStr;
use super::super::core::common::Value;
use super::value::Value;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum XpathError {

View File

@ -23,10 +23,9 @@ use std::fs;
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::ast::*;
use hurl::format::{Token, Tokenizable};
use hurl::parser::{parse_json, Reader};
fn whitespace() -> BoxedStrategy<String> {
prop_oneof![
@ -39,37 +38,37 @@ fn whitespace() -> BoxedStrategy<String> {
// region strategy scalar/leaves
fn value_number() -> BoxedStrategy<json::Value> {
fn value_number() -> BoxedStrategy<JsonValue> {
prop_oneof![
Just(json::Value::Number("0".to_string())),
Just(json::Value::Number("1".to_string())),
Just(json::Value::Number("1.33".to_string())),
Just(json::Value::Number("-100".to_string()))
Just(JsonValue::Number("0".to_string())),
Just(JsonValue::Number("1".to_string())),
Just(JsonValue::Number("1.33".to_string())),
Just(JsonValue::Number("-100".to_string()))
]
.boxed()
}
fn value_boolean() -> BoxedStrategy<json::Value> {
fn value_boolean() -> BoxedStrategy<JsonValue> {
prop_oneof![
Just(json::Value::Boolean(true)),
Just(json::Value::Boolean(false)),
Just(JsonValue::Boolean(true)),
Just(JsonValue::Boolean(false)),
]
.boxed()
}
fn value_string() -> BoxedStrategy<json::Value> {
fn value_string() -> BoxedStrategy<JsonValue> {
let source_info = SourceInfo::init(0, 0, 0, 0);
let variable = Variable {
name: "name".to_string(),
source_info: source_info.clone(),
};
prop_oneof![
Just(json::Value::String(Template {
Just(JsonValue::String(Template {
elements: vec![],
quotes: true,
source_info: source_info.clone()
})),
Just(json::Value::String(Template {
Just(JsonValue::String(Template {
elements: vec![TemplateElement::String {
encoded: "Hello".to_string(),
value: "Hello".to_string(),
@ -77,7 +76,7 @@ fn value_string() -> BoxedStrategy<json::Value> {
quotes: true,
source_info: source_info.clone()
})),
Just(json::Value::String(Template {
Just(JsonValue::String(Template {
elements: vec![
TemplateElement::String {
encoded: "Hello\\u0020 ".to_string(),
@ -106,7 +105,7 @@ fn value_string() -> BoxedStrategy<json::Value> {
// region strategy value
fn value() -> BoxedStrategy<json::Value> {
fn value() -> BoxedStrategy<JsonValue> {
let leaf = prop_oneof![value_boolean(), value_string(), value_number(),];
leaf.prop_recursive(
8, // 8 levels deep
@ -115,14 +114,14 @@ fn value() -> BoxedStrategy<json::Value> {
|value| {
prop_oneof![
// Lists
(whitespace()).prop_map(|space0| json::Value::List {
(whitespace()).prop_map(|space0| JsonValue::List {
space0,
elements: vec![]
}),
(whitespace(), whitespace(), value.clone()).prop_map(|(space0, space1, value)| {
json::Value::List {
JsonValue::List {
space0,
elements: vec![json::ListElement {
elements: vec![JsonListElement {
space0: "".to_string(),
value,
space1,
@ -138,15 +137,15 @@ fn value() -> BoxedStrategy<json::Value> {
value_number()
)
.prop_map(
|(space00, space01, value0, space10, space11, value1)| json::Value::List {
|(space00, space01, value0, space10, space11, value1)| JsonValue::List {
space0: space00,
elements: vec![
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: value0,
space1: space01
},
json::ListElement {
JsonListElement {
space0: space10,
value: value1,
space1: space11
@ -163,15 +162,15 @@ fn value() -> BoxedStrategy<json::Value> {
value_boolean()
)
.prop_map(
|(space00, space01, value0, space10, space11, value1)| json::Value::List {
|(space00, space01, value0, space10, space11, value1)| JsonValue::List {
space0: space00,
elements: vec![
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: value0,
space1: space01
},
json::ListElement {
JsonListElement {
space0: space10,
value: value1,
space1: space11
@ -188,15 +187,15 @@ fn value() -> BoxedStrategy<json::Value> {
value_string()
)
.prop_map(
|(space00, space01, value0, space10, space11, value1)| json::Value::List {
|(space00, space01, value0, space10, space11, value1)| JsonValue::List {
space0: space00,
elements: vec![
json::ListElement {
JsonListElement {
space0: "".to_string(),
value: value0,
space1: space01
},
json::ListElement {
JsonListElement {
space0: space10,
value: value1,
space1: space11
@ -205,7 +204,7 @@ fn value() -> BoxedStrategy<json::Value> {
}
),
// Object
(whitespace()).prop_map(|space0| json::Value::Object {
(whitespace()).prop_map(|space0| JsonValue::Object {
space0,
elements: vec![]
}),
@ -217,9 +216,9 @@ fn value() -> BoxedStrategy<json::Value> {
whitespace()
)
.prop_map(|(space0, space1, space2, value, space3)| {
json::Value::Object {
JsonValue::Object {
space0,
elements: vec![json::ObjectElement {
elements: vec![JsonObjectElement {
space0: "".to_string(),
name: "key1".to_string(),
space1,
@ -253,7 +252,7 @@ fn format_token(token: Token) -> String {
}
}
fn format_value(value: json::Value) -> String {
fn format_value(value: JsonValue) -> String {
let tokens = value.tokenize();
//eprintln!("{:?}", tokens);
tokens
@ -272,8 +271,8 @@ fn test_echo() {
//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();
let mut reader = Reader::init(s.as_str());
let parsed_value = parse_json(&mut reader).unwrap();
assert_eq!(format_value(parsed_value), s);
Ok(())
@ -290,8 +289,8 @@ fn test_parse_files() {
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 mut reader = hurl::parser::reader::Reader::init(s.as_str());
let parsed_value = hurl::parser::json::parse(&mut reader).unwrap();
let mut reader = Reader::init(s.as_str());
let parsed_value = parse_json(&mut reader).unwrap();
assert_eq!(format_value(parsed_value), s);
}

View File

@ -25,7 +25,7 @@ use serde_json::json;
use hurl::jsonpath;
fn test_ok(s: &str, value: serde_json::Value) -> Vec<serde_json::Value> {
return match jsonpath::parser::parse::parse(s) {
return match jsonpath::parse(s) {
Ok(expr) => expr.eval(value),
Err(e) => panic!("{:?}", e),
};

View File

@ -17,15 +17,22 @@
*/
extern crate hurl;
use hurl::core::ast;
use hurl::core::ast::{EncodedString, Template, TemplateElement};
use hurl::core::common::{Pos, SourceInfo};
use hurl::format;
use hurl::ast::*;
use hurl::http;
use hurl::runner;
use hurl::runner::core::RunnerOptions;
use hurl::runner::RunnerOptions;
use std::collections::HashMap;
pub fn log_verbose(message: &str) {
eprintln!("* {}", message);
}
pub fn log_error_message(_warning: bool, message: &str) {
eprintln!("{}", message);
}
pub fn log_runner_error(error: &runner::Error, _warning: bool) {
eprintln!("* {:#?}", error);
}
// can be used for debugging
#[test]
fn test_hurl_file() {
@ -54,52 +61,51 @@ fn test_hurl_file() {
.collect();
// edd an empty line at the end?
lines.push("");
let lines: Vec<String> = lines.iter().map(|s| s.to_string()).collect();
let options = RunnerOptions {
fail_fast: false,
variables,
to_entry: None,
};
let logger = format::logger::Logger {
filename: Some(filename.to_string()),
lines: lines,
verbose: false,
color: false,
context_dir: "current_dir".to_string(),
};
let _hurl_log = runner::file::run(
let log_verbose: fn(&str) = log_verbose;
let log_error_message: fn(bool, &str) = log_error_message;
let log_runner_error: fn(&runner::Error, bool) = log_runner_error;
let _hurl_log = runner::run_hurl_file(
hurl_file,
&mut client,
//&mut variables,
filename.to_string(),
"current_dir".to_string(),
options,
logger,
&log_verbose,
&log_error_message,
&log_runner_error,
);
// assert_eq!(1,2)
}
#[cfg(test)]
fn hello_request() -> ast::Request {
fn hello_request() -> Request {
// GET http://localhost;8000/hello
let source_info = SourceInfo {
start: Pos { line: 1, column: 1 },
end: Pos { line: 1, column: 1 },
};
let whitespace = ast::Whitespace {
let whitespace = Whitespace {
value: "".to_string(),
source_info: source_info.clone(),
};
let line_terminator = ast::LineTerminator {
let line_terminator = LineTerminator {
space0: whitespace.clone(),
comment: None,
newline: whitespace.clone(),
};
ast::Request {
Request {
line_terminators: vec![],
space0: whitespace.clone(),
method: ast::Method::Get,
method: Method::Get,
space1: whitespace.clone(),
url: Template {
quotes: false,
@ -109,12 +115,12 @@ fn hello_request() -> ast::Request {
}],
source_info: source_info.clone(),
},
line_terminator0: ast::LineTerminator {
line_terminator0: LineTerminator {
space0: whitespace.clone(),
comment: None,
newline: whitespace.clone(),
},
headers: vec![ast::KeyValue {
headers: vec![KeyValue {
line_terminators: vec![],
space0: whitespace.clone(),
key: EncodedString {
@ -159,27 +165,27 @@ fn test_hello() {
start: Pos { line: 1, column: 1 },
end: Pos { line: 1, column: 1 },
};
let whitespace = ast::Whitespace {
let whitespace = Whitespace {
value: "".to_string(),
source_info: source_info.clone(),
};
let request = hello_request();
let hurl_file = ast::HurlFile {
entries: vec![ast::Entry {
let hurl_file = HurlFile {
entries: vec![Entry {
request,
response: Some(ast::Response {
response: Some(Response {
line_terminators: vec![],
version: ast::Version {
value: ast::VersionValue::Version11,
version: Version {
value: VersionValue::Version11,
source_info: source_info.clone(),
},
space0: whitespace.clone(),
status: ast::Status {
status: Status {
value: 200,
source_info: source_info.clone(),
},
space1: whitespace.clone(),
line_terminator0: ast::LineTerminator {
line_terminator0: LineTerminator {
space0: whitespace.clone(),
comment: None,
newline: whitespace.clone(),
@ -192,26 +198,24 @@ fn test_hello() {
}],
line_terminators: vec![],
};
let lines = vec![String::from("line1")];
let variables = HashMap::new();
let options = RunnerOptions {
fail_fast: true,
variables,
to_entry: None,
context_dir: "current_dir".to_string(),
};
let logger = format::logger::Logger {
filename: None,
lines,
verbose: false,
color: false,
};
let _hurl_log = runner::file::run(
let log_verbose: fn(&str) = log_verbose;
let log_error_message: fn(bool, &str) = log_error_message;
let log_runner_error: fn(&runner::Error, bool) = log_runner_error;
let _hurl_log = runner::run_hurl_file(
hurl_file,
&mut client,
String::from("filename"),
"current_dir".to_string(),
options,
logger,
&log_verbose,
&log_error_message,
&log_runner_error,
);
//assert_eq!(hurl_log.entries.len(), 1);
//assert_eq!(hurl_log.entries.get(0).unwrap().response.status, 200);

View File

@ -1,4 +1,20 @@
#![allow(dead_code)]
/*
* hurl (https://hurl.dev)
* Copyright (C) 2020 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
use std::collections::HashSet;
use std::io::prelude::*;