Merge pull request #17 from Orange-OpenSource/feature/text-decoding

Get Http response body as text
This commit is contained in:
Fabrice Reix 2020-09-14 13:23:54 +02:00 committed by GitHub
commit 1adaf37b08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 188 additions and 1 deletions

View File

@ -105,6 +105,7 @@ pub enum RunnerError {
//??CaptureError {}, //??CaptureError {},
InvalidUtf8, InvalidUtf8,
InvalidDecoding { charset: String }, InvalidDecoding { charset: String },
InvalidCharset { charset: String },
// Query // Query
QueryHeaderNotFound, QueryHeaderNotFound,
@ -161,9 +162,11 @@ impl FormatError for Error {
RunnerError::PredicateType { .. } => "Assert - Inconsistent predicate type".to_string(), RunnerError::PredicateType { .. } => "Assert - Inconsistent predicate type".to_string(),
RunnerError::SubqueryInvalidInput { .. } => "Subquery error".to_string(), RunnerError::SubqueryInvalidInput { .. } => "Subquery error".to_string(),
RunnerError::InvalidDecoding { ..} => "Invalid Decoding".to_string(), RunnerError::InvalidDecoding { ..} => "Invalid Decoding".to_string(),
RunnerError::InvalidCharset { ..} => "Invalid Charset".to_string(),
RunnerError::AssertFailure { .. } => "Assert Failure".to_string(), RunnerError::AssertFailure { .. } => "Assert Failure".to_string(),
RunnerError::UnrenderableVariable { .. } => "Unrenderable Variable".to_string(), RunnerError::UnrenderableVariable { .. } => "Unrenderable Variable".to_string(),
RunnerError::NoQueryResult { .. } => "No query result".to_string(), RunnerError::NoQueryResult { .. } => "No query result".to_string(),
} }
} }
@ -189,6 +192,7 @@ impl FormatError for Error {
RunnerError::PredicateType { .. } => "predicate type inconsistent with value return by query".to_string(), 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::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::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::AssertFailure { actual, expected, .. } => format!("actual: {}\nexpected: {}", actual, expected),
RunnerError::VariableNotDefined { name } => format!("You must set the variable {}", name), RunnerError::VariableNotDefined { name } => format!("You must set the variable {}", name),
RunnerError::UnrenderableVariable { value } => format!("value {} can not be rendered", value), RunnerError::UnrenderableVariable { value } => format!("value {} can not be rendered", value),

182
src/runner/http_response.rs Normal file
View File

@ -0,0 +1,182 @@
/*
* 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::libcurl::core::Response;
use super::core::RunnerError;
use encoding::{EncodingRef, DecoderTrap};
///
/// get body content as text from http response
/// used by query
///
#[allow(dead_code)]
impl Response {
///
/// Return optional Content-type header value
///
fn content_type(&self) -> Option<String> {
for header in self.headers.clone() {
if header.name.to_lowercase() == "content-type" {
return Some(header.value);
}
}
None
}
///
/// Return encoding of the response
///
fn encoding(&self) -> Result<EncodingRef, RunnerError> {
match self.content_type() {
Some(content_type) => {
match mime_charset(content_type) {
Some(charset) => match encoding::label::encoding_from_whatwg_label(charset.as_str()) {
None => Err(RunnerError::InvalidCharset { charset }),
Some(enc) => Ok(enc)
},
None => Ok(encoding::all::UTF_8),
}
}
None => Ok(encoding::all::UTF_8)
}
}
pub fn text(&self) -> Result<String, RunnerError> {
let encoding = self.encoding()?;
match encoding.decode(&self.body, DecoderTrap::Strict) {
Ok(s) => Ok(s),
Err(_) => Err(RunnerError::InvalidDecoding { charset: encoding.name().to_string() })
}
}
}
///
/// Extract charset from mime-type String
///
fn mime_charset(mime_type: String) -> Option<String> {
match mime_type.find("charset=") {
None => None,
Some(index) => {
Some(mime_type[(index + 8)..].to_string())
}
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use crate::http::libcurl::core::{Version, Header};
#[test]
pub fn test_charset() {
assert_eq!(mime_charset("text/plain; charset=utf-8".to_string()), Some("utf-8".to_string()));
assert_eq!(mime_charset("text/plain; charset=ISO-8859-1".to_string()), Some("ISO-8859-1".to_string()));
assert_eq!(mime_charset("text/plain;".to_string()), None);
}
fn hello_response() -> Response {
Response {
version: Version::Http10,
status: 200,
headers: vec![],
body: b"Hello World!".to_vec(),
}
}
fn utf8_encoding_response() -> Response {
Response {
version: Version::Http10,
status: 200,
headers: vec![
Header { name: "Content-Type".to_string(), value: "text/plain; charset=utf-8".to_string() }
],
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
}
}
fn latin1_encoding_response() -> Response {
Response {
version: Version::Http10,
status: 200,
headers: vec![
Header { name: "Content-Type".to_string(), value: "text/plain; charset=ISO-8859-1".to_string() }
],
body: vec![0x63, 0x61, 0x66, 0xe9],
}
}
#[test]
pub fn test_content_type() {
assert_eq!(hello_response().content_type(), None);
assert_eq!(utf8_encoding_response().content_type(), Some("text/plain; charset=utf-8".to_string()));
assert_eq!(latin1_encoding_response().content_type(), Some("text/plain; charset=ISO-8859-1".to_string()));
}
#[test]
pub fn test_encoding() {
assert_eq!(hello_response().encoding().unwrap().name(), "utf-8");
assert_eq!(utf8_encoding_response().encoding().unwrap().name(), "utf-8");
assert_eq!(latin1_encoding_response().encoding().unwrap().name(), "windows-1252");
}
#[test]
pub fn test_text() {
assert_eq!(hello_response().text().unwrap(), "Hello World!".to_string());
assert_eq!(utf8_encoding_response().text().unwrap(), "café".to_string());
assert_eq!(latin1_encoding_response().text().unwrap(), "café".to_string());
}
#[test]
pub fn test_invalid_charset() {
assert_eq!(Response {
version: Version::Http10,
status: 200,
headers: vec![
Header { name: "Content-Type".to_string(), value:"test/plain; charset=xxx".to_string()}
],
body: b"Hello World!".to_vec(),
}.encoding().err().unwrap(), RunnerError::InvalidCharset { charset: "xxx".to_string()});
}
#[test]
pub fn test_invalid_decoding() {
assert_eq!(Response {
version: Version::Http10,
status: 200,
headers: vec![],
body: vec![0x63, 0x61, 0x66, 0xe9],
}.text().err().unwrap(), RunnerError::InvalidDecoding { charset: "utf-8".to_string()});
assert_eq!(Response {
version: Version::Http10,
status: 200,
headers: vec![
Header { name: "Content-Type".to_string(), value: "text/plain; charset=ISO-8859-1".to_string() }
],
body: vec![0x63, 0x61, 0x66, 0xc3, 0xa9],
}.text().unwrap(), "café".to_string());
}
}

View File

@ -30,6 +30,7 @@ mod cookie;
pub mod core; pub mod core;
mod entry; mod entry;
pub mod file; pub mod file;
mod http_response;
mod json; mod json;
pub mod log; pub mod log;
mod predicate; mod predicate;