From fa5077a3f50f93b8c98797ec0312165810858997 Mon Sep 17 00:00:00 2001 From: Fabrice Reix Date: Mon, 14 Sep 2020 13:41:25 +0200 Subject: [PATCH] Serialize/Deserialize new http request/response --- src/bin/hurl.rs | 4 +- src/http/libcurl/core.rs | 58 +++++ src/runner/cookie.rs | 12 + src/runner/log.rs | 180 -------------- src/runner/log_deserialize.rs | 455 ++++++++++++++++++++++++++++++++++ src/runner/log_serialize.rs | 237 ++++++++++++++++++ src/runner/mod.rs | 3 +- 7 files changed, 766 insertions(+), 183 deletions(-) delete mode 100644 src/runner/log.rs create mode 100644 src/runner/log_deserialize.rs create mode 100644 src/runner/log_serialize.rs diff --git a/src/bin/hurl.rs b/src/bin/hurl.rs index ffef2025b..323b23169 100644 --- a/src/bin/hurl.rs +++ b/src/bin/hurl.rs @@ -34,7 +34,7 @@ use hurl::http; use hurl::parser; use hurl::runner; use hurl::runner::core::*; -use hurl::runner::log; +use hurl::runner::{log_deserialize}; use hurl::format; @@ -207,7 +207,7 @@ fn json_file(matches: ArgMatches, logger: format::logger::Logger) -> (Vec { logger.error_message(format!("Existing Hurl json can not be parsed! - {}", msg)); std::process::exit(127); diff --git a/src/http/libcurl/core.rs b/src/http/libcurl/core.rs index e7016a227..4974e95a0 100644 --- a/src/http/libcurl/core.rs +++ b/src/http/libcurl/core.rs @@ -52,6 +52,23 @@ pub enum Method { Patch, } +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let value = match self { + Method::Get => "GET", + Method::Head => "HEAD", + Method::Post => "POST", + Method::Put => "PUT", + Method::Delete => "DELETE", + Method::Connect => "CONNECT", + Method::Options => "OPTIONS", + Method::Trace => "TRACE", + Method::Patch => "PATCH" + }; + write!(f, "{}", value) + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum Version { Http10, @@ -171,4 +188,45 @@ pub mod tests { form: vec![], } } + + pub fn custom_http_request() -> Request { + Request { + method: Method::Get, + url: "http://localhost/custom".to_string(), + querystring: vec![], + headers: vec![ + Header { name: String::from("User-Agent"), value: String::from("iPhone") }, + Header { name: String::from("Foo"), value: String::from("Bar") }, + ], + cookies: vec![ + RequestCookie { + name: String::from("theme"), + value: String::from("light"), + }, + RequestCookie { + name: String::from("sessionToken"), + value: String::from("abc123"), + } + ], + body: vec![], + multipart: vec![], + form: vec![], + } + } + + + pub fn form_http_request() -> Request { + Request { + method: Method::Post, + url: "http://localhost/form-params".to_string(), + querystring: vec![], + headers: vec![ + Header { name: String::from("Content-Type"), value: String::from("application/x-www-form-urlencoded") }, + ], + cookies: vec![], + body: "param1=value1¶m2=¶m3=a%3db¶m4=a%253db".to_string().into_bytes(), + multipart: vec![], + form: vec![] + } + } } \ No newline at end of file diff --git a/src/runner/cookie.rs b/src/runner/cookie.rs index d81c0a3be..46cecadb1 100644 --- a/src/runner/cookie.rs +++ b/src/runner/cookie.rs @@ -25,6 +25,18 @@ /// and not by the http client. /// +use crate::http::libcurl::core::Response; + +impl Response { + pub fn cookies(&self) -> Vec { + self.headers + .iter() + .filter(|&h| h.name.to_lowercase() == "set-cookie") + .filter_map(|h| ResponseCookie::parse(h.value.clone())) + .collect() + } +} + /// /// Cookie return from HTTP Response /// It contains arbitrary attributes. diff --git a/src/runner/log.rs b/src/runner/log.rs deleted file mode 100644 index e0be18231..000000000 --- a/src/runner/log.rs +++ /dev/null @@ -1,180 +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::{Serializer, SerializeStruct}; -use serde::Serialize; - -use crate::http; - -use super::core::*; - -impl Serialize for HurlResult { - fn serialize(&self, serializer: S) -> Result - 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(&self, serializer: S) -> Result - 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(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("??", 3)?; - if let AssertResult::Version { source_info, 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(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct("CaptureResult", 3)?; - state.serialize_field("name", self.name.as_str())?; - //let (_type, value) = self.value.to_json_value(); - //state.serialize_field("type", &_type)?; - state.serialize_field("value", &self.value)?; - state.end() - } -} - - -// http-specific - - -type ParseError = String; - - -pub fn parse_results(value: serde_json::Value) -> Result, 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 { - 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 = match value.get("cookies") { - None => vec![], - Some(serde_json::Value::Array(values)) => { - let mut cookies: Vec = vec![]; - for value in values { - let cookie = http::import::parse_cookie(value.clone())?; - cookies.push(cookie); - } - cookies - } - Some(_) => return Err("expecting list for cookies".to_string()), - }; - - 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 { - let request = match value.get("request") { - None => None, - Some(v) => { - let r = http::import::parse_request(v.clone())?; - Some(r) - } - }; - let response = match value.get("response") { - None => None, - Some(v) => { - let r = http::import::parse_response(v.clone())?; - Some(r) - } - }; - Ok(EntryResult { - request, - response, - captures: vec![], - asserts: vec![], - errors: vec![], - time_in_ms: 0, - }) -} - - diff --git a/src/runner/log_deserialize.rs b/src/runner/log_deserialize.rs new file mode 100644 index 000000000..aaf843860 --- /dev/null +++ b/src/runner/log_deserialize.rs @@ -0,0 +1,455 @@ +/* + * 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 crate::http::libcurl::core::*; + +use super::cookie::*; +use super::core::*; + +type ParseError = String; + + +pub fn parse_results(value: serde_json::Value) -> Result, 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 { + 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 { + let request = match value.get("request") { + None => None, + Some(v) => { + let r = http::import::parse_request(v.clone())?; + Some(r) + } + }; + let response = match value.get("response") { + None => None, + Some(v) => { + let r = http::import::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 { + 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 = vec![]; + + Ok(Request { + method, + url, + querystring, + headers, + cookies, + body, + multipart, + form, + }) + } else { + Err("expecting an object for the request".to_string()) + } +} + +pub fn parse_response(value: serde_json::Value) -> Result { + 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![], + }; + + Ok(Response { + version, + status, + headers, + body: vec![], + }) + } else { + Err("expecting an object for the response".to_string()) + } +} + +fn parse_method(s: String) -> Result { + 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 { + 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 { + 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 { + 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()) + } +} + +pub fn parse_response_cookie(value: serde_json::Value) -> Result { + 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 { + 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 crate::http::libcurl::core::tests::*; + + use super::*; + + #[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¶m2=a%20b", + "headers": [] +}"#).unwrap(); + assert_eq!(parse_request(v).unwrap(), Request { + method: Method::Get, + url: "http://localhost:8000/querystring-params?param1=value1¶m2=a%20b".to_string(), + querystring: vec![], + headers: vec![], + cookies: vec![], + body: vec![], + form: vec![], + multipart: vec![], + }); + + + 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![], + }); + } + + #[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 "); + } + + #[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); + } +} diff --git a/src/runner/log_serialize.rs b/src/runner/log_serialize.rs new file mode 100644 index 000000000..39cb89918 --- /dev/null +++ b/src/runner/log_serialize.rs @@ -0,0 +1,237 @@ +/* + * 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::{Serializer, SerializeStruct}; +use serde::Serialize; + +use crate::http::libcurl::core::*; + +use super::cookie::*; +use super::core::*; + +impl Serialize for HurlResult { + fn serialize(&self, serializer: S) -> Result + 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(&self, serializer: S) -> Result + 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(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct("??", 3)?; + if let AssertResult::Version { source_info, 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(&self, serializer: S) -> Result + 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(&self, serializer: S) -> Result + where + S: Serializer, + { + // 3 is the number of fields in the struct. + let mut state = serializer.serialize_struct("??", 3)?; + state.serialize_field("method", &self.clone().method.to_string())?; + state.serialize_field("url", &self.clone().url)?; + state.serialize_field("queryString", &self.clone().querystring)?; + state.serialize_field("headers", &self.clone().headers)?; + state.serialize_field("cookies", &self.clone().cookies)?; + + if !self.clone().form.is_empty() { + state.serialize_field("form", &self.clone().form)?; + } + state.serialize_field("body", &base64::encode(&self.body))?; + + state.end() + } +} + + +impl Serialize for Response { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // 3 is the number of fields in the struct. + let mut state = serializer.serialize_struct("??", 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 +// let content_type = self.get_header("content_type", true); +// if let Some(value) = content_type.first() { +// if value.as_str() == "application/json; charset=UTF-8" { +// let s = String::from_utf8(self.body.clone()).expect("Found invalid UTF-8"); +// let result: Result = serde_json::from_str(s.as_str()); +// if let Ok(v) = result { +// state.serialize_field("json", &v)?; +// } +// } +// } + state.end() + } +} + +impl Serialize for Header { + fn serialize(&self, serializer: S) -> Result + 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(&self, serializer: S) -> Result + 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(&self, serializer: S) -> Result + 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 Cookie { +// fn serialize(&self, serializer: S) -> Result +// where +// S: Serializer, +// { +// let mut state = serializer.serialize_struct("InternalCookie", 3)?; +// state.serialize_field("name", &self.clone().name)?; +// state.serialize_field("value", &self.clone().value)?; +// state.serialize_field("domain", &self.clone().domain)?; +// state.serialize_field("path", &self.clone().path)?; +// state.serialize_field("include_subdomain", &self.clone().subdomains)?; +// state.end() +// } +//} + + +impl Serialize for Version { + fn serialize(&self, serializer: S) -> Result + 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(&self, serializer: S) -> Result + 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() + } +} + diff --git a/src/runner/mod.rs b/src/runner/mod.rs index 0c650ad95..8acd59b86 100644 --- a/src/runner/mod.rs +++ b/src/runner/mod.rs @@ -32,7 +32,8 @@ mod entry; pub mod file; mod http_response; mod json; -pub mod log; +pub mod log_serialize; +pub mod log_deserialize; mod predicate; mod query; pub mod request;