mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2025-01-02 07:56:31 +03:00
Merge pull request #32 from Orange-OpenSource/feature/clean-and-isolate-modules
Clean and isolate modules
This commit is contained in:
commit
87b7353df8
@ -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
|
||||
|
@ -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
@ -6,7 +6,7 @@ upload1: file,hello.txt;
|
||||
upload2: file,hello.html;
|
||||
upload3: file,hello.txt; text/html
|
||||
|
||||
HTTP/1.0 200
|
||||
HTTP/* 200
|
||||
|
||||
|
||||
|
||||
|
@ -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
|
||||
//
|
@ -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,
|
@ -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;
|
409
src/bin/hurl.rs
409
src/bin/hurl.rs
@ -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
|
||||
))],
|
||||
|
@ -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
227
src/cli/error.rs
Normal 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
175
src/cli/logger.rs
Normal 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));
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
use super::super::core::ast::*;
|
||||
use crate::ast::*;
|
||||
|
||||
pub trait Htmlable {
|
||||
fn to_html(&self) -> String;
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -15,7 +15,8 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
use super::super::core::ast::*;
|
||||
use crate::ast::*;
|
||||
|
||||
use super::color::TerminalColor;
|
||||
use super::token::*;
|
||||
|
||||
|
@ -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()));
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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};
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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(),
|
||||
}
|
||||
);
|
||||
|
@ -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;
|
||||
|
@ -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![],
|
||||
}
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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() {
|
||||
|
@ -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::*;
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
*/
|
||||
use sxd_document::parser;
|
||||
|
||||
use crate::core::common::Pos;
|
||||
use crate::ast::Pos;
|
||||
|
||||
use super::error::*;
|
||||
use super::reader::Reader;
|
||||
|
@ -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::*;
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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("");
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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> {
|
||||
|
@ -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;
|
||||
}
|
@ -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"
|
||||
}"#
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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?"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 ???
|
||||
|
@ -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::*;
|
||||
|
@ -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(
|
||||
|
@ -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
126
src/runner/value.rs
Normal 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);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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),
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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::*;
|
||||
|
Loading…
Reference in New Issue
Block a user