Refacto JSON Serializer/Deserializer

This commit is contained in:
Fabrice Reix 2021-10-14 13:40:20 +02:00 committed by Fabrice Reix
parent 852a9cb06e
commit 925a5c08a7
9 changed files with 456 additions and 922 deletions

View File

@ -0,0 +1,69 @@
/*
* 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::cli::CliError;
use std::io::prelude::*;
use std::path::PathBuf;
mod result;
mod value;
pub fn write_json_report(
file_path: PathBuf,
results: Vec<serde_json::Value>,
) -> Result<(), CliError> {
let mut file = match std::fs::File::create(&file_path) {
Err(why) => {
return Err(CliError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
})
}
Ok(file) => file,
};
let serialized = serde_json::to_string_pretty(&results).unwrap();
if let Err(why) = file.write_all(serialized.as_bytes()) {
Err(CliError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
})
} else {
Ok(())
}
}
pub fn parse_json(path: PathBuf) -> Result<Vec<serde_json::Value>, CliError> {
if path.exists() {
let s = match std::fs::read_to_string(path.clone()) {
Ok(s) => s,
Err(why) => {
return Err(CliError {
message: format!("Issue reading {} to string to {:?}", path.display(), why),
});
}
};
match serde_json::from_str(s.as_str()) {
Ok(val) => Ok(val),
Err(_) => {
return Err(CliError {
message: format!("The file {} is not a valid json file", path.display()),
})
}
}
} else {
Ok(vec![])
}
}

View File

@ -0,0 +1,302 @@
/*
* 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::http::{Cookie, Header, Param, Request, RequestCookie, Response, Version};
use crate::runner::{AssertResult, CaptureResult, EntryResult, HurlResult, ResponseCookie};
impl HurlResult {
pub fn to_json(&self, lines: &[String]) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert(
"filename".to_string(),
serde_json::Value::String(self.filename.clone()),
);
let entries = self
.entries
.iter()
.map(|e| e.clone().to_json(lines, self.filename.clone()))
.collect();
map.insert("entries".to_string(), serde_json::Value::Array(entries));
map.insert(
"success".to_string(),
serde_json::Value::Bool(self.clone().success),
);
map.insert(
"time".to_string(),
serde_json::Value::Number(serde_json::Number::from(self.time_in_ms as u64)),
);
let cookies = self.cookies.iter().map(|e| e.clone().to_json()).collect();
map.insert("cookies".to_string(), serde_json::Value::Array(cookies));
serde_json::Value::Object(map)
}
}
impl EntryResult {
fn to_json(&self, lines: &[String], filename: String) -> serde_json::Value {
let mut map = serde_json::Map::new();
if let Some(request) = &self.request {
map.insert("request".to_string(), request.to_json());
}
if let Some(response) = &self.response {
map.insert("response".to_string(), response.to_json());
}
let captures = self.captures.iter().map(|c| c.clone().to_json()).collect();
map.insert("captures".to_string(), captures);
let asserts = self
.asserts
.iter()
.map(|a| a.clone().to_json(lines, filename.clone()))
.collect();
map.insert("asserts".to_string(), asserts);
map.insert(
"time".to_string(),
serde_json::Value::Number(serde_json::Number::from(self.time_in_ms as u64)),
);
serde_json::Value::Object(map)
}
}
impl Request {
fn to_json(&self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert(
"method".to_string(),
serde_json::Value::String(self.method.clone()),
);
map.insert(
"url".to_string(),
serde_json::Value::String(self.url.clone()),
);
let headers = self.headers.iter().map(|h| h.clone().to_json()).collect();
map.insert("headers".to_string(), headers);
let cookies = self
.clone()
.cookies()
.iter()
.map(|e| e.clone().to_json())
.collect();
map.insert("cookies".to_string(), serde_json::Value::Array(cookies));
let query_string = self
.clone()
.query_string_params()
.iter()
.map(|e| e.clone().to_json())
.collect();
map.insert(
"queryString".to_string(),
serde_json::Value::Array(query_string),
);
serde_json::Value::Object(map)
}
}
impl Response {
fn to_json(&self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert("httpVersion".to_string(), self.version.to_json());
map.insert(
"status".to_string(),
serde_json::Value::Number(serde_json::Number::from(self.status)),
);
let headers = self.headers.iter().map(|h| h.clone().to_json()).collect();
map.insert("headers".to_string(), headers);
let cookies = self
.clone()
.cookies()
.iter()
.map(|e| e.clone().to_json())
.collect();
map.insert("cookies".to_string(), serde_json::Value::Array(cookies));
serde_json::Value::Object(map)
}
}
impl Header {
fn to_json(&self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert(
"name".to_string(),
serde_json::Value::String(self.name.clone()),
);
map.insert(
"value".to_string(),
serde_json::Value::String(self.value.clone()),
);
serde_json::Value::Object(map)
}
}
impl Version {
fn to_json(&self) -> serde_json::Value {
let value = match self {
Version::Http10 => "HTTP/1.0",
Version::Http11 => "HTTP/1.1",
Version::Http2 => "HTTP/2",
};
serde_json::Value::String(value.to_string())
}
}
impl Param {
fn to_json(&self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert(
"name".to_string(),
serde_json::Value::String(self.name.clone()),
);
map.insert(
"value".to_string(),
serde_json::Value::String(self.value.clone()),
);
serde_json::Value::Object(map)
}
}
impl RequestCookie {
fn to_json(&self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert(
"name".to_string(),
serde_json::Value::String(self.name.clone()),
);
map.insert(
"value".to_string(),
serde_json::Value::String(self.value.clone()),
);
serde_json::Value::Object(map)
}
}
impl ResponseCookie {
fn to_json(&self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert(
"name".to_string(),
serde_json::Value::String(self.name.clone()),
);
map.insert(
"value".to_string(),
serde_json::Value::String(self.value.clone()),
);
if let Some(expires) = &self.clone().expires() {
map.insert(
"expires".to_string(),
serde_json::Value::String(expires.to_string()),
);
}
if let Some(max_age) = &self.clone().max_age() {
map.insert(
"max_age".to_string(),
serde_json::Value::String(max_age.to_string()),
);
}
if let Some(domain) = &self.clone().domain() {
map.insert(
"domain".to_string(),
serde_json::Value::String(domain.to_string()),
);
}
if let Some(path) = &self.clone().path() {
map.insert(
"path".to_string(),
serde_json::Value::String(path.to_string()),
);
}
if self.clone().has_secure() {
map.insert("secure".to_string(), serde_json::Value::Bool(true));
}
if self.clone().has_httponly() {
map.insert("httponly".to_string(), serde_json::Value::Bool(true));
}
if let Some(samesite) = &self.clone().samesite() {
map.insert(
"samesite".to_string(),
serde_json::Value::String(samesite.to_string()),
);
}
serde_json::Value::Object(map)
}
}
impl CaptureResult {
fn to_json(&self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert(
"name".to_string(),
serde_json::Value::String(self.name.clone()),
);
map.insert("value".to_string(), self.value.to_json());
serde_json::Value::Object(map)
}
}
impl AssertResult {
fn to_json(&self, _lines: &[String], _filename: String) -> serde_json::Value {
let mut map = serde_json::Map::new();
if let AssertResult::Version {
actual, expected, ..
} = self
{
map.insert(
"actual".to_string(),
serde_json::Value::String(actual.clone()),
);
map.insert(
"expected".to_string(),
serde_json::Value::String(expected.clone()),
);
};
serde_json::Value::Object(map)
}
}
impl Cookie {
fn to_json(&self) -> serde_json::Value {
let mut map = serde_json::Map::new();
map.insert(
"domain".to_string(),
serde_json::Value::String(self.domain.clone()),
);
map.insert(
"include_subdomain".to_string(),
serde_json::Value::String(self.include_subdomain.clone()),
);
map.insert(
"path".to_string(),
serde_json::Value::String(self.path.clone()),
);
map.insert(
"https".to_string(),
serde_json::Value::String(self.https.clone()),
);
map.insert(
"expires".to_string(),
serde_json::Value::String(self.expires.clone()),
);
map.insert(
"name".to_string(),
serde_json::Value::String(self.name.clone()),
);
map.insert(
"value".to_string(),
serde_json::Value::String(self.value.clone()),
);
serde_json::Value::Object(map)
}
}

View File

@ -0,0 +1,60 @@
/*
* 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::runner::Value;
impl Value {
pub fn to_json(&self) -> serde_json::Value {
match self {
Value::Bool(v) => serde_json::Value::Bool(*v),
Value::Integer(v) => serde_json::Value::Number(serde_json::Number::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::Number(serde_json::Number::from_f64(value).unwrap())
}
Value::String(s) => serde_json::Value::String(s.clone()),
Value::List(values) => {
let values = values.iter().map(|v| v.to_json()).collect();
serde_json::Value::Array(values)
}
Value::Object(key_values) => {
let mut map = serde_json::Map::new();
for (key, value) in key_values {
map.insert(key.to_string(), value.to_json());
}
serde_json::Value::Object(map)
}
Value::Nodeset(size) => {
let mut map = serde_json::Map::new();
let size = *size as i64;
map.insert(
"type".to_string(),
serde_json::Value::String("nodeset".to_string()),
);
map.insert("size".to_string(), serde_json::Value::from(size));
serde_json::Value::Object(map)
}
Value::Bytes(v) => {
let encoded = base64::encode(v);
serde_json::Value::String(encoded)
}
Value::Null => serde_json::Value::Null,
Value::Unit => todo!("how to serialize that in json?"),
}
}
}

View File

@ -22,6 +22,7 @@ extern crate float_cmp;
pub mod cli;
pub mod http;
pub mod json;
pub mod jsonpath;
pub mod report;
pub mod runner;

View File

@ -27,6 +27,7 @@ use colored::*;
use hurl::cli;
use hurl::cli::{CliError, CliOptions};
use hurl::http;
use hurl::json;
use hurl::report;
use hurl::runner;
use hurl::runner::{HurlResult, RunnerOptions};
@ -262,6 +263,12 @@ fn main() {
};
let start = Instant::now();
let mut json_results = vec![];
if let Some(file_path) = cli_options.json_file.clone() {
json_results = unwrap_or_exit(&log_error_message, json::parse_json(file_path));
}
for (current, filename) in filenames.iter().enumerate() {
let contents = match cli::read_to_string(filename) {
Ok(v) => v,
@ -281,7 +288,7 @@ fn main() {
};
let hurl_result = execute(
filename,
contents,
contents.clone(),
current_dir,
cli_options.clone(),
&log_verbose,
@ -353,14 +360,24 @@ fn main() {
}
hurl_results.push(hurl_result.clone());
if cli_options.json_file.is_some() {
let lines: Vec<String> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(&contents)
.map(|l| l.to_string())
.collect();
let json_result = hurl_result.to_json(&lines);
json_results.push(json_result);
}
}
let duration = start.elapsed().as_millis();
if let Some(file_path) = cli_options.json_file {
if let Some(file_path) = cli_options.json_file.clone() {
log_verbose(format!("Writing json report to {}", file_path.display()).as_str());
unwrap_or_exit(
&log_error_message,
report::write_json_report(file_path, hurl_results.clone()),
json::write_json_report(file_path, json_results),
);
}

View File

@ -20,66 +20,10 @@ use std::io::prelude::*;
use std::path::PathBuf;
use super::cli::CliError;
use super::runner;
use super::runner::HurlResult;
mod html;
pub fn write_json_report(
file_path: PathBuf,
hurl_results: Vec<HurlResult>,
) -> Result<(), CliError> {
let mut results = parse_json(file_path.clone())?;
for result in hurl_results {
results.push(result);
}
let mut file = match std::fs::File::create(&file_path) {
Err(why) => {
return Err(CliError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
})
}
Ok(file) => file,
};
let serialized = serde_json::to_string_pretty(&results).unwrap();
if let Err(why) = file.write_all(serialized.as_bytes()) {
Err(CliError {
message: format!("Issue writing to {}: {:?}", file_path.display(), why),
})
} else {
Ok(())
}
}
pub fn parse_json(path: PathBuf) -> Result<Vec<HurlResult>, CliError> {
if path.exists() {
let s = match std::fs::read_to_string(path.clone()) {
Ok(s) => s,
Err(why) => {
return Err(CliError {
message: format!("Issue reading {} to string to {:?}", path.display(), why),
});
}
};
let v: serde_json::Value = match serde_json::from_str(s.as_str()) {
Ok(val) => val,
Err(_) => {
return Err(CliError {
message: format!("The file {} is not a valid json file", path.display()),
})
}
};
match runner::deserialize_results(v) {
Ok(results) => Ok(results),
Err(_) => Err(CliError {
message: "Existing Hurl json can not be parsed!".to_string(),
}),
}
} else {
Ok(vec![])
}
}
pub fn parse_html(path: PathBuf) -> Result<Vec<HurlResult>, CliError> {
if path.exists() {
let s = match std::fs::read_to_string(path.clone()) {

View File

@ -1,578 +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::http::*;
use super::cookie::*;
use super::core::*;
use std::time::Duration;
type ParseError = String;
pub fn parse_results(value: serde_json::Value) -> Result<Vec<HurlResult>, ParseError> {
if let serde_json::Value::Array(values) = value {
let mut results = vec![];
for value in values {
let result = parse_result(value)?;
results.push(result);
}
Ok(results)
} else {
Err("expecting an array of session".to_string())
}
}
fn parse_result(value: serde_json::Value) -> Result<HurlResult, ParseError> {
if let serde_json::Value::Object(map) = value.clone() {
let filename = map.get("filename").unwrap().as_str().unwrap().to_string();
let mut entries = vec![];
let entries = if let Some(serde_json::Value::Array(values)) = map.get("entries") {
for value in values {
let entry = parse_entry_result(value.clone())?;
entries.push(entry);
}
entries
} else {
return Err("expecting an array of entries".to_string());
};
let time_in_ms = match value.get("time") {
Some(serde_json::Value::Number(n)) => match n.as_u64() {
Some(x) => x as u128,
None => return Err("expecting an integer for the time".to_string()),
},
_ => return Err("expecting an integer for the time".to_string()),
};
let success = match value.get("success") {
Some(serde_json::Value::Bool(v)) => *v,
_ => return Err("expecting a bool for the status".to_string()),
};
let cookies = vec![];
Ok(HurlResult {
filename,
entries,
time_in_ms,
success,
cookies,
})
} else {
Err("expecting an object for the result".to_string())
}
}
fn parse_entry_result(value: serde_json::Value) -> Result<EntryResult, String> {
let request = match value.get("request") {
None => None,
Some(v) => {
let r = parse_request(v.clone())?;
Some(r)
}
};
let response = match value.get("response") {
None => None,
Some(v) => {
let r = parse_response(v.clone())?;
Some(r)
}
};
Ok(EntryResult {
request,
response,
captures: vec![],
asserts: vec![],
errors: vec![],
time_in_ms: 0,
})
}
// pub fn _parse_request(value: serde_json::Value) -> Result<Request, ParseError> {
// if let serde_json::Value::Object(map) = value {
// let method = match map.get("method") {
// Some(serde_json::Value::String(s)) => parse_method(s.clone())?,
// _ => return Err("expecting a string for the method".to_string()),
// };
// let url = match map.get("url") {
// Some(serde_json::Value::String(s)) => s.to_string(),
// _ => return Err("expecting a string for the url".to_string()),
// };
//
// let headers = match map.get("headers") {
// Some(serde_json::Value::Array(values)) => {
// let mut headers = vec![];
// for value in values {
// let header = parse_header(value.clone())?;
// headers.push(header);
// }
// headers
// }
// _ => vec![],
// };
//
// let querystring = match map.get("queryString") {
// Some(serde_json::Value::Array(values)) => {
// let mut params = vec![];
// for value in values {
// let param = parse_param(value.clone())?;
// params.push(param);
// }
// params
// }
// _ => vec![],
// };
//
// let form = match map.get("form") {
// Some(serde_json::Value::Array(values)) => {
// let mut params = vec![];
// for value in values {
// let param = parse_param(value.clone())?;
// params.push(param);
// }
// params
// }
// _ => vec![],
// };
//
// let cookies = match map.get("cookies") {
// Some(serde_json::Value::Array(values)) => {
// let mut headers = vec![];
// for value in values {
// let header = parse_request_cookie(value.clone())?;
// headers.push(header);
// }
// headers
// }
// _ => vec![],
// };
//
// // TODO
// let multipart = vec![];
// let body = http::Body::Binary(vec![]);
// let content_type = None;
//
// Ok(Request {
// method,
// url,
// headers,
// querystring,
// form,
// multipart,
// cookies,
// body,
// content_type,
// })
// } else {
// Err("expecting an object for the request".to_string())
// }
// }
pub fn parse_request(value: serde_json::Value) -> Result<Request, ParseError> {
if let serde_json::Value::Object(map) = value {
let method = match map.get("method") {
Some(serde_json::Value::String(s)) => parse_method(s.clone())?.to_string(),
_ => return Err("expecting a string for the method".to_string()),
};
let url = match map.get("url") {
Some(serde_json::Value::String(s)) => s.to_string(),
_ => return Err("expecting a string for the url".to_string()),
};
let headers = match map.get("headers") {
Some(serde_json::Value::Array(values)) => {
let mut headers = vec![];
for value in values {
let header = parse_header(value.clone())?;
headers.push(header);
}
headers
}
_ => vec![],
};
Ok(Request {
url,
method,
headers,
})
} else {
Err("expecting an object for the request".to_string())
}
}
pub fn parse_response(value: serde_json::Value) -> Result<Response, ParseError> {
if let serde_json::Value::Object(map) = value {
let status = match map.get("status") {
Some(serde_json::Value::Number(x)) => {
if let Some(x) = x.as_u64() {
x as u32
} else {
return Err("expecting a integer for the status".to_string());
}
}
_ => return Err("expecting a number for the status".to_string()),
};
let version = match map.get("httpVersion") {
Some(serde_json::Value::String(s)) => parse_version(s.clone())?,
_ => return Err("expecting a string for the version".to_string()),
};
let headers = match map.get("headers") {
Some(serde_json::Value::Array(values)) => {
let mut headers = vec![];
for value in values {
let header = parse_header(value.clone())?;
headers.push(header);
}
headers
}
_ => vec![],
};
// TODO: Check if you need it
let duration = Duration::new(0, 0);
Ok(Response {
version,
status,
headers,
body: vec![],
duration,
})
} else {
Err("expecting an object for the response".to_string())
}
}
fn parse_method(s: String) -> Result<Method, ParseError> {
match s.as_str() {
"GET" => Ok(Method::Get),
"HEAD" => Ok(Method::Head),
"POST" => Ok(Method::Post),
"PUT" => Ok(Method::Put),
"DELETE" => Ok(Method::Delete),
"CONNECT" => Ok(Method::Connect),
"OPTIONS" => Ok(Method::Options),
"TRACE" => Ok(Method::Trace),
"PATCH" => Ok(Method::Patch),
_ => Err(format!("Invalid method <{}>", s)),
}
}
fn parse_header(value: serde_json::Value) -> Result<Header, ParseError> {
if let serde_json::Value::Object(map) = value {
let name = match map.get("name") {
Some(serde_json::Value::String(s)) => s.to_string(),
_ => return Err("expecting a string for the header name".to_string()),
};
let value = match map.get("value") {
Some(serde_json::Value::String(s)) => s.to_string(),
_ => return Err("expecting a string for the header value".to_string()),
};
Ok(Header { name, value })
} else {
Err("Expecting object for one header".to_string())
}
}
// pub fn parse_param(value: serde_json::Value) -> Result<Param, ParseError> {
// if let serde_json::Value::Object(map) = value {
// let name = match map.get("name") {
// Some(serde_json::Value::String(s)) => s.to_string(),
// _ => return Err("expecting a string for the cookie name".to_string()),
// };
// let value = match map.get("value") {
// Some(serde_json::Value::String(s)) => s.to_string(),
// _ => return Err("expecting a string for the cookie value".to_string()),
// };
// Ok(Param { name, value })
// } else {
// Err("Expecting object for the param".to_string())
// }
// }
// pub fn parse_request_cookie(value: serde_json::Value) -> Result<RequestCookie, ParseError> {
// if let serde_json::Value::Object(map) = value {
// let name = match map.get("name") {
// Some(serde_json::Value::String(s)) => s.to_string(),
// _ => return Err("expecting a string for the cookie name".to_string()),
// };
// let value = match map.get("value") {
// Some(serde_json::Value::String(s)) => s.to_string(),
// _ => return Err("expecting a string for the cookie value".to_string()),
// };
// Ok(RequestCookie { name, value })
// } else {
// Err("Expecting object for the request cookie".to_string())
// }
// }
#[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") {
Some(serde_json::Value::String(s)) => s.to_string(),
_ => return Err("expecting a string for the cookie name".to_string()),
};
let value = match map.get("value") {
Some(serde_json::Value::String(s)) => s.to_string(),
_ => return Err("expecting a string for the cookie value".to_string()),
};
let mut attributes = vec![];
match map.get("expires") {
None => {}
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute {
name: "Expires".to_string(),
value: Some(s.to_string()),
}),
_ => return Err("expecting a string for the cookie expires".to_string()),
};
match map.get("max_age") {
None => {}
Some(serde_json::Value::Number(n)) => attributes.push(CookieAttribute {
name: "Max-Age".to_string(),
value: Some(n.to_string()),
}),
_ => return Err("expecting an integer for the cookie max_age".to_string()),
};
match map.get("domain") {
None => {}
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute {
name: "Domain".to_string(),
value: Some(s.to_string()),
}),
_ => return Err("expecting a string for the cookie domain".to_string()),
};
match map.get("path") {
None => {}
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute {
name: "Path".to_string(),
value: Some(s.to_string()),
}),
_ => return Err("expecting a string for the cookie path".to_string()),
};
match map.get("secure") {
None => {}
Some(serde_json::Value::Bool(true)) => attributes.push(CookieAttribute {
name: "Secure".to_string(),
value: None,
}),
_ => return Err("expecting a true for the cookie secure flag".to_string()),
};
match map.get("http_only") {
None => {}
Some(serde_json::Value::Bool(true)) => attributes.push(CookieAttribute {
name: "HttpOnly".to_string(),
value: None,
}),
_ => return Err("expecting a true for the cookie http_only flag".to_string()),
};
match map.get("same_site") {
None => {}
Some(serde_json::Value::String(s)) => attributes.push(CookieAttribute {
name: "SameSite".to_string(),
value: Some(s.to_string()),
}),
_ => return Err("expecting a string for the cookie same_site".to_string()),
};
Ok(ResponseCookie {
name,
value,
attributes,
})
} else {
Err("Expecting object for one cookie".to_string())
}
}
fn parse_version(s: String) -> Result<Version, ParseError> {
match s.as_str() {
"HTTP/1.0" => Ok(Version::Http10),
"HTTP/1.1" => Ok(Version::Http11),
"HTTP/2" => Ok(Version::Http2),
_ => Err("Expecting version HTTP/1.0, HTTP/1.2 or HTTP/2".to_string()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runner::value::Value;
// #[test]
// fn test_parse_request() {
// let v: serde_json::Value = serde_json::from_str(
// r#"{
// "method": "GET",
// "url": "http://localhost:8000/hello",
// "headers": []
// }"#,
// )
// .unwrap();
// assert_eq!(_parse_request(v).unwrap(), hello_http_request());
//
// let v: serde_json::Value = serde_json::from_str(
// r#"{
// "method": "GET",
// "url": "http://localhost:8000/querystring-params?param1=value1&param2=a%20b",
// "headers": []
// }"#,
// )
// .unwrap();
// assert_eq!(
// _parse_request(v).unwrap(),
// http::Request {
// method: http::Method::Get,
// url: "http://localhost:8000/querystring-params?param1=value1&param2=a%20b"
// .to_string(),
// querystring: vec![],
// headers: vec![],
// cookies: vec![],
// body: http::Body::Binary(vec![]),
// form: vec![],
// multipart: vec![],
// content_type: None,
// }
// );
//
// let v: serde_json::Value = serde_json::from_str(
// r#"{
// "method": "GET",
// "url": "http://localhost/custom",
// "headers": [
// {"name": "User-Agent", "value": "iPhone"},
// {"name": "Foo", "value": "Bar"}
// ],
// "cookies": [
// {"name": "theme", "value": "light"},
// {"name": "sessionToken", "value": "abc123"}
// ]
// }"#,
// )
// .unwrap();
// assert_eq!(_parse_request(v).unwrap(), custom_http_request());
// }
#[test]
fn test_parse_response() {
let v: serde_json::Value = serde_json::from_str(
r#"{
"status": 200,
"httpVersion": "HTTP/1.0",
"headers": [
{"name": "Content-Type", "value": "text/html; charset=utf-8" },
{"name": "Content-Length", "value": "12" }
]
}"#,
)
.unwrap();
assert_eq!(
parse_response(v).unwrap(),
Response {
version: Version::Http10,
status: 200,
headers: vec![
Header {
name: String::from("Content-Type"),
value: String::from("text/html; charset=utf-8")
},
Header {
name: String::from("Content-Length"),
value: String::from("12")
},
],
body: vec![],
duration: Default::default()
}
);
}
#[test]
fn test_parse_method() {
assert_eq!(parse_method("GET".to_string()).unwrap(), Method::Get);
let error = parse_method("x".to_string()).err().unwrap();
assert_eq!(error, "Invalid method <x>");
}
#[test]
fn test_parse_header() {
let v: serde_json::Value = serde_json::from_str(
r#"{
"name": "name1",
"value": "value1"
}"#,
)
.unwrap();
assert_eq!(
parse_header(v).unwrap(),
Header {
name: "name1".to_string(),
value: "value1".to_string()
}
);
}
#[test]
fn test_parse_response_cookie() {
let v: serde_json::Value = serde_json::from_str(
r#"{
"name": "name1",
"value": "value1"
}"#,
)
.unwrap();
assert_eq!(
parse_response_cookie(v).unwrap(),
ResponseCookie {
name: "name1".to_string(),
value: "value1".to_string(),
attributes: vec![],
}
);
}
#[test]
fn test_parse_version() {
assert_eq!(
parse_version("HTTP/1.0".to_string()).unwrap(),
Version::Http10
);
}
#[test]
fn test_parse_value() {
assert_eq!(
Value::from_json(&serde_json::Value::String("hello".to_string())),
Value::String("hello".to_string())
);
assert_eq!(
Value::from_json(&serde_json::Value::Bool(true)),
Value::Bool(true)
);
assert_eq!(
Value::from_json(&serde_json::Value::from(1)),
Value::Integer(1)
);
assert_eq!(
Value::from_json(&serde_json::Value::from(1.5)),
Value::Float(1, 500_000_000_000_000_000)
);
}
}

View File

@ -1,281 +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 serde::ser::{SerializeStruct, Serializer};
use serde::Serialize;
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>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 3)?;
state.serialize_field("filename", &self.clone().filename)?;
state.serialize_field("entries", &self.clone().entries)?;
state.serialize_field("success", &self.clone().success)?;
state.serialize_field("time", &self.time_in_ms)?;
state.serialize_field("cookies", &self.cookies)?;
state.end()
}
}
impl Serialize for EntryResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("EntryResult", 3)?;
if let Some(request) = &self.request {
state.serialize_field("request", request)?;
}
if let Some(response) = &self.response {
state.serialize_field("response", response)?;
}
state.serialize_field("captures", &self.captures)?;
state.serialize_field("asserts", &self.asserts)?;
state.serialize_field("time", &self.time_in_ms)?;
state.end()
}
}
impl Serialize for AssertResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 3)?;
if let AssertResult::Version {
actual, expected, ..
} = self
{
//state.serialize_field("source_info", source_info)?;
state.serialize_field("actual", actual)?;
state.serialize_field("expected", expected)?;
};
state.end()
}
}
impl Serialize for CaptureResult {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("CaptureResult", 3)?;
state.serialize_field("name", self.name.as_str())?;
state.serialize_field("value", &self.value)?;
state.end()
}
}
impl Serialize for Request {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Request", 5)?;
state.serialize_field("method", &self.clone().method)?;
state.serialize_field("url", &self.clone().url)?;
state.serialize_field("headers", &self.clone().headers)?;
state.serialize_field("cookies", &self.clone().cookies())?;
state.serialize_field("queryString", &self.clone().query_string_params())?;
state.end()
}
}
impl Serialize for Response {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// 3 is the number of fields in the struct.
let mut state = serializer.serialize_struct("Response", 3)?;
state.serialize_field("httpVersion", &self.clone().version)?;
state.serialize_field("status", &self.clone().status)?;
state.serialize_field("cookies", &self.clone().cookies())?;
state.serialize_field("headers", &self.clone().headers)?;
// TODO Serialize body
state.end()
}
}
impl Serialize for Header {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 3)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("value", &self.value)?;
state.end()
}
}
impl Serialize for Param {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 3)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("value", &self.value)?;
state.end()
}
}
impl Serialize for RequestCookie {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("??", 2)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("value", &self.value)?;
state.end()
}
}
impl Serialize for Version {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Version::Http10 => serializer.serialize_str("HTTP/1.0"),
Version::Http11 => serializer.serialize_str("HTTP/1.1"),
Version::Http2 => serializer.serialize_str("HTTP/2"),
}
}
}
impl Serialize for ResponseCookie {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("ResponseCookie", 3)?;
state.serialize_field("name", &self.clone().name)?;
state.serialize_field("value", &self.clone().value)?;
if let Some(expires) = &self.clone().expires() {
state.serialize_field("expires", expires)?;
}
if let Some(max_age) = &self.clone().max_age() {
state.serialize_field("max_age", max_age)?;
}
if let Some(domain) = &self.clone().domain() {
state.serialize_field("domain", domain)?;
}
if let Some(path) = &self.clone().path() {
state.serialize_field("path", path)?;
}
if self.clone().has_secure() {
state.serialize_field("secure", &true)?;
}
if self.clone().has_httponly() {
state.serialize_field("httponly", &true)?;
}
if let Some(samesite) = &self.clone().samesite() {
state.serialize_field("samesite", samesite)?;
}
state.end()
}
}
impl Serialize for Cookie {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("Cookie", 3)?;
state.serialize_field("domain", &self.clone().domain)?;
state.serialize_field("include_subdomain", &self.clone().include_subdomain)?;
state.serialize_field("path", &self.clone().path)?;
state.serialize_field("https", &self.clone().https)?;
state.serialize_field("expires", &self.clone().expires)?;
state.serialize_field("name", &self.clone().name)?;
state.serialize_field("value", &self.clone().value)?;
state.end()
}
}
impl Serialize for MultipartParam {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self.clone() {
MultipartParam::Param(param) => param.serialize(serializer),
MultipartParam::FileParam(file_param) => file_param.serialize(serializer),
}
}
}
impl Serialize for FileParam {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state = serializer.serialize_struct("FileParam", 4)?;
state.serialize_field("name", &self.clone().name)?;
if let Ok(s) = std::str::from_utf8(&self.clone().data) {
state.serialize_field("value", s)?;
}
state.serialize_field("fileName", &self.clone().filename)?;
state.serialize_field("contentType", &self.clone().content_type)?;
state.end()
}
}
impl Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match self {
Value::Bool(v) => serializer.serialize_bool(*v),
Value::Integer(v) => serializer.serialize_i64(*v),
Value::Float(i, d) => {
let value = *i as f64 + (*d as f64) / 1_000_000_000_000_000_000.0;
serializer.serialize_f64(value)
}
Value::String(s) => serializer.serialize_str(s),
Value::List(values) => serializer.collect_seq(values),
Value::Object(values) => serializer.collect_map(values.iter().map(|(k, v)| (k, v))),
Value::Nodeset(size) => {
let size = *size as i64;
serializer.collect_map(vec![
("type", serde_json::Value::String("nodeset".to_string())),
("size", serde_json::Value::from(size)),
])
}
Value::Bytes(v) => {
let encoded = base64::encode(v);
serializer.serialize_str(&encoded)
}
Value::Null => serializer.serialize_none(),
Value::Unit => todo!("how to serialize that in json?"),
}
}
}

View File

@ -23,9 +23,11 @@
//!
//!
pub use self::core::{Error, HurlResult, RunnerError, RunnerOptions};
pub use self::cookie::ResponseCookie;
pub use self::core::{
AssertResult, CaptureResult, EntryResult, Error, HurlResult, RunnerError, RunnerOptions,
};
pub use self::hurl_file::run as run_hurl_file;
pub use self::log_deserialize::parse_results as deserialize_results;
pub use self::value::Value;
mod assert;
@ -40,8 +42,6 @@ mod expr;
mod http_response;
mod hurl_file;
mod json;
mod log_deserialize;
mod log_serialize;
mod multipart;
mod predicate;
mod predicate_value;