mirror of
https://github.com/Orange-OpenSource/hurl.git
synced 2024-11-23 09:44:22 +03:00
Support template in keys
This commit is contained in:
parent
af3f78e637
commit
aeca943cf7
7
integration/tests_failed/key_template.err
Normal file
7
integration/tests_failed/key_template.err
Normal file
@ -0,0 +1,7 @@
|
||||
error: Undefined variable
|
||||
--> tests_failed/key_template.hurl:4:3
|
||||
|
|
||||
4 | {{name}}: value
|
||||
| ^^^^ you must set the variable name
|
||||
|
|
||||
|
1
integration/tests_failed/key_template.exit
Normal file
1
integration/tests_failed/key_template.exit
Normal file
@ -0,0 +1 @@
|
||||
4
|
5
integration/tests_failed/key_template.hurl
Normal file
5
integration/tests_failed/key_template.hurl
Normal file
@ -0,0 +1,5 @@
|
||||
# Variable not found
|
||||
GET http://localhost:8000/error-key-template
|
||||
HTTP 200
|
||||
{{name}}: value
|
||||
|
3
integration/tests_failed/key_template.ps1
Normal file
3
integration/tests_failed/key_template.ps1
Normal file
@ -0,0 +1,3 @@
|
||||
Set-StrictMode -Version latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
hurl tests_failed/key_template.hurl
|
7
integration/tests_failed/key_template.py
Normal file
7
integration/tests_failed/key_template.py
Normal file
@ -0,0 +1,7 @@
|
||||
from app import app
|
||||
from flask import Response
|
||||
|
||||
|
||||
@app.route("/error-key-template")
|
||||
def error_key_template():
|
||||
return ""
|
3
integration/tests_failed/key_template.sh
Executable file
3
integration/tests_failed/key_template.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
hurl tests_failed/key_template.hurl
|
47
integration/tests_ok/key_template.hurl
Normal file
47
integration/tests_ok/key_template.hurl
Normal file
@ -0,0 +1,47 @@
|
||||
# Test template in the different Hurl "keys"
|
||||
|
||||
GET http://localhost:8000/key-template/header
|
||||
{{key}}: value
|
||||
[Options]
|
||||
variable: key=name
|
||||
HTTP 200
|
||||
|
||||
GET http://localhost:8000/key-template/querystring
|
||||
[QueryStringParams]
|
||||
{{key}}: value
|
||||
[Options]
|
||||
variable: key=name
|
||||
HTTP 200
|
||||
|
||||
POST http://localhost:8000/key-template/form
|
||||
[FormParams]
|
||||
{{key}}: value
|
||||
[Options]
|
||||
variable: key=name
|
||||
HTTP 200
|
||||
|
||||
POST http://localhost:8000/key-template/multipart-form-data
|
||||
[MultipartFormData]
|
||||
{{key1}}: value
|
||||
{{key2}}: file,data.txt;
|
||||
[Options]
|
||||
variable: key1=name
|
||||
variable: key2=file
|
||||
HTTP 200
|
||||
|
||||
GET http://localhost:8000/key-template/cookie
|
||||
[Cookies]
|
||||
{{key}}: value
|
||||
[Options]
|
||||
variable: key=name
|
||||
HTTP 200
|
||||
|
||||
GET http://localhost:8000/key-template/capture
|
||||
[Options]
|
||||
variable: key=name
|
||||
HTTP 200
|
||||
[Captures]
|
||||
{{key}}: body
|
||||
[Asserts]
|
||||
variable "name" == "Hello"
|
||||
|
3
integration/tests_ok/key_template.ps1
Normal file
3
integration/tests_ok/key_template.ps1
Normal file
@ -0,0 +1,3 @@
|
||||
Set-StrictMode -Version latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
hurl tests_ok/key_template.hurl
|
43
integration/tests_ok/key_template.py
Normal file
43
integration/tests_ok/key_template.py
Normal file
@ -0,0 +1,43 @@
|
||||
# coding=utf-8
|
||||
from flask import request
|
||||
from app import app
|
||||
|
||||
|
||||
@app.route("/key-template/header")
|
||||
def key_template_header():
|
||||
print(request.headers)
|
||||
assert request.headers["name"] == "value"
|
||||
return ""
|
||||
|
||||
|
||||
@app.route("/key-template/querystring")
|
||||
def key_template_querystring():
|
||||
assert request.args.get("name") == "value"
|
||||
return ""
|
||||
|
||||
|
||||
@app.route("/key-template/form", methods=["POST"])
|
||||
def key_template_form():
|
||||
assert request.form["name"] == "value"
|
||||
return ""
|
||||
|
||||
|
||||
@app.route("/key-template/multipart-form-data", methods=["POST"])
|
||||
def key_template_multipart_form_data():
|
||||
assert request.form["name"] == "value"
|
||||
upload = request.files["file"]
|
||||
assert upload.filename == "data.txt"
|
||||
assert upload.content_type == "text/plain"
|
||||
assert upload.read() == b"Hello World!"
|
||||
return ""
|
||||
|
||||
|
||||
@app.route("/key-template/cookie")
|
||||
def key_template_cookie():
|
||||
assert request.cookies["name"] == "value"
|
||||
return ""
|
||||
|
||||
|
||||
@app.route("/key-template/capture")
|
||||
def key_template_capture():
|
||||
return "Hello"
|
3
integration/tests_ok/key_template.sh
Executable file
3
integration/tests_ok/key_template.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
set -Eeuo pipefail
|
||||
hurl tests_ok/key_template.hurl
|
@ -66,29 +66,29 @@ pub fn pre_entry(entry: Entry) -> bool {
|
||||
fn log_request(request: Request) {
|
||||
eprintln!("\r\n{} {}", request.method, request.url);
|
||||
for header in request.headers {
|
||||
eprintln!("\r{}: {}", header.key.value, header.value);
|
||||
eprintln!("\r{}: {}", header.key, header.value);
|
||||
}
|
||||
for section in request.sections {
|
||||
eprintln!("\r[{}]", section.name());
|
||||
match section.value {
|
||||
SectionValue::QueryParams(key_values) => {
|
||||
for value in key_values {
|
||||
eprintln!("\r{}: {}", value.key.value, value.value);
|
||||
eprintln!("\r{}: {}", value.key, value.value);
|
||||
}
|
||||
}
|
||||
SectionValue::BasicAuth(Some(key_value)) => {
|
||||
eprintln!("\r{}: {}", key_value.key.value, key_value.value);
|
||||
eprintln!("\r{}: {}", key_value.key, key_value.value);
|
||||
}
|
||||
SectionValue::FormParams(key_values) => {
|
||||
for value in key_values {
|
||||
eprintln!("\r{}: {}", value.key.value, value.value);
|
||||
eprintln!("\r{}: {}", value.key, value.value);
|
||||
}
|
||||
}
|
||||
SectionValue::MultipartFormData(multipart_params) => {
|
||||
for param in multipart_params {
|
||||
match param {
|
||||
MultipartParam::Param(value) => {
|
||||
eprintln!("\r{}: {}", value.key.value, value.value)
|
||||
eprintln!("\r{}: {}", value.key, value.value)
|
||||
}
|
||||
MultipartParam::FileParam(file_param) => {
|
||||
let content_type =
|
||||
@ -99,7 +99,7 @@ fn log_request(request: Request) {
|
||||
};
|
||||
eprintln!(
|
||||
"\r{}: {}{}",
|
||||
file_param.key.value, file_param.value.filename.value, content_type
|
||||
file_param.key, file_param.value.filename.value, content_type
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -107,7 +107,7 @@ fn log_request(request: Request) {
|
||||
}
|
||||
SectionValue::Cookies(cookies) => {
|
||||
for cookie in cookies {
|
||||
eprintln!("\r{}: {}", cookie.name.value, cookie.value);
|
||||
eprintln!("\r{}: {}", cookie.name, cookie.value);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
|
@ -23,6 +23,7 @@ use crate::http;
|
||||
use crate::runner::core::{CaptureResult, Error, RunnerError};
|
||||
use crate::runner::filter::eval_filters;
|
||||
use crate::runner::query::eval_query;
|
||||
use crate::runner::template::eval_template;
|
||||
use crate::runner::value::Value;
|
||||
|
||||
/// Evaluates a `capture` with `variables` map and `http_response`, returns a
|
||||
@ -32,7 +33,7 @@ pub fn eval_capture(
|
||||
variables: &HashMap<String, Value>,
|
||||
http_response: &http::Response,
|
||||
) -> Result<CaptureResult, Error> {
|
||||
let name = &capture.name.value;
|
||||
let name = eval_template(&capture.name, variables)?;
|
||||
let value = eval_query(&capture.query, variables, http_response)?;
|
||||
let value = match value {
|
||||
None => {
|
||||
@ -79,10 +80,12 @@ pub mod tests {
|
||||
Capture {
|
||||
line_terminators: vec![],
|
||||
space0: whitespace.clone(),
|
||||
name: EncodedString {
|
||||
quotes: false,
|
||||
value: "UserCount".to_string(),
|
||||
encoded: "UserCount".to_string(),
|
||||
name: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "UserCount".to_string(),
|
||||
encoded: "UserCount".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(0, 0, 0, 0),
|
||||
},
|
||||
space1: whitespace.clone(),
|
||||
@ -108,10 +111,12 @@ pub mod tests {
|
||||
Capture {
|
||||
line_terminators: vec![],
|
||||
space0: whitespace.clone(),
|
||||
name: EncodedString {
|
||||
quotes: false,
|
||||
value: "duration".to_string(),
|
||||
encoded: "duration".to_string(),
|
||||
name: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "duration".to_string(),
|
||||
encoded: "duration".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(0, 0, 0, 0),
|
||||
},
|
||||
space1: whitespace.clone(),
|
||||
@ -138,10 +143,12 @@ pub mod tests {
|
||||
let capture = Capture {
|
||||
line_terminators: vec![],
|
||||
space0: whitespace.clone(),
|
||||
name: EncodedString {
|
||||
quotes: false,
|
||||
value: "count".to_string(),
|
||||
encoded: "count".to_string(),
|
||||
name: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "count".to_string(),
|
||||
encoded: "count".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(0, 0, 0, 0),
|
||||
},
|
||||
filters: vec![],
|
||||
@ -173,10 +180,12 @@ pub mod tests {
|
||||
let _capture = Capture {
|
||||
line_terminators: vec![],
|
||||
space0: whitespace.clone(),
|
||||
name: EncodedString {
|
||||
quotes: false,
|
||||
value: "???".to_string(),
|
||||
encoded: "???".to_string(),
|
||||
name: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "???".to_string(),
|
||||
encoded: "???".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(0, 0, 0, 0),
|
||||
},
|
||||
space1: whitespace.clone(),
|
||||
|
@ -35,12 +35,12 @@ pub fn eval_multipart_param(
|
||||
) -> Result<http::MultipartParam, Error> {
|
||||
match multipart_param {
|
||||
MultipartParam::Param(KeyValue { key, value, .. }) => {
|
||||
let name = key.value.clone();
|
||||
let name = eval_template(key, variables)?;
|
||||
let value = eval_template(value, variables)?;
|
||||
Ok(http::MultipartParam::Param(http::Param { name, value }))
|
||||
}
|
||||
MultipartParam::FileParam(param) => {
|
||||
let file_param = eval_file_param(param, context_dir)?;
|
||||
let file_param = eval_file_param(param, context_dir, variables)?;
|
||||
Ok(http::MultipartParam::FileParam(file_param))
|
||||
}
|
||||
}
|
||||
@ -49,8 +49,9 @@ pub fn eval_multipart_param(
|
||||
pub fn eval_file_param(
|
||||
file_param: &FileParam,
|
||||
context_dir: &ContextDir,
|
||||
variables: &HashMap<String, Value>,
|
||||
) -> Result<http::FileParam, Error> {
|
||||
let name = file_param.key.value.clone();
|
||||
let name = eval_template(&file_param.key, variables)?;
|
||||
let filename = file_param.value.filename.clone();
|
||||
let data = eval_file(&filename, context_dir)?;
|
||||
let content_type = file_value_content_type(&file_param.value);
|
||||
@ -107,14 +108,17 @@ mod tests {
|
||||
let current_dir = std::env::current_dir().unwrap();
|
||||
let file_root = Path::new("tests");
|
||||
let context_dir = ContextDir::new(current_dir.as_path(), file_root);
|
||||
let variables = HashMap::default();
|
||||
let param = eval_file_param(
|
||||
&FileParam {
|
||||
line_terminators: vec![],
|
||||
space0: whitespace(),
|
||||
key: EncodedString {
|
||||
value: "upload1".to_string(),
|
||||
encoded: "upload1".to_string(),
|
||||
quotes: false,
|
||||
key: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "upload1".to_string(),
|
||||
encoded: "upload1".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(0, 0, 0, 0),
|
||||
},
|
||||
space1: whitespace(),
|
||||
@ -132,6 +136,7 @@ mod tests {
|
||||
line_terminator0: line_terminator,
|
||||
},
|
||||
&context_dir,
|
||||
&variables,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
|
@ -41,16 +41,17 @@ pub fn eval_request(
|
||||
// Headers
|
||||
let mut headers: Vec<http::Header> = vec![];
|
||||
for header in &request.headers {
|
||||
let name = &header.key.value;
|
||||
let name = eval_template(&header.key, variables)?;
|
||||
let value = eval_template(&header.value, variables)?;
|
||||
let header = http::Header::new(name, &value);
|
||||
let header = http::Header::new(&name, &value);
|
||||
headers.push(header);
|
||||
}
|
||||
|
||||
// Basic auth
|
||||
if let Some(kv) = &request.basic_auth() {
|
||||
let name = eval_template(&kv.key, variables)?;
|
||||
let value = eval_template(&kv.value, variables)?;
|
||||
let user_password = format!("{}:{}", kv.key.value, value);
|
||||
let user_password = format!("{}:{}", name, value);
|
||||
let user_password = user_password.as_bytes();
|
||||
let authorization = general_purpose::STANDARD.encode(user_password);
|
||||
let value = format!("Basic {authorization}");
|
||||
@ -61,7 +62,7 @@ pub fn eval_request(
|
||||
// Query string params
|
||||
let mut querystring: Vec<http::Param> = vec![];
|
||||
for param in &request.querystring_params() {
|
||||
let name = param.key.value.clone();
|
||||
let name = eval_template(¶m.key, variables)?;
|
||||
let value = eval_template(¶m.value, variables)?;
|
||||
let param = http::Param { name, value };
|
||||
querystring.push(param);
|
||||
@ -70,7 +71,7 @@ pub fn eval_request(
|
||||
// Form params
|
||||
let mut form: Vec<http::Param> = vec![];
|
||||
for param in &request.form_params() {
|
||||
let name = param.key.value.clone();
|
||||
let name = eval_template(¶m.key, variables)?;
|
||||
let value = eval_template(¶m.value, variables)?;
|
||||
let param = http::Param { name, value };
|
||||
form.push(param);
|
||||
@ -79,8 +80,8 @@ pub fn eval_request(
|
||||
// Cookies
|
||||
let mut cookies = vec![];
|
||||
for cookie in &request.cookies() {
|
||||
let name = eval_template(&cookie.name, variables)?;
|
||||
let value = eval_template(&cookie.value, variables)?;
|
||||
let name = cookie.name.value.clone();
|
||||
let cookie = http::RequestCookie { name, value };
|
||||
cookies.push(cookie);
|
||||
}
|
||||
@ -215,7 +216,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn simple_key_value(key: EncodedString, value: Template) -> KeyValue {
|
||||
fn simple_key_value(key: Template, value: Template) -> KeyValue {
|
||||
let line_terminator = LineTerminator {
|
||||
space0: whitespace(),
|
||||
comment: None,
|
||||
@ -259,10 +260,12 @@ mod tests {
|
||||
line_terminator0: line_terminator,
|
||||
value: SectionValue::QueryParams(vec![
|
||||
simple_key_value(
|
||||
EncodedString {
|
||||
quotes: false,
|
||||
value: "param1".to_string(),
|
||||
encoded: "param1".to_string(),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "param1".to_string(),
|
||||
encoded: "param1".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(0, 0, 0, 0),
|
||||
},
|
||||
Template {
|
||||
@ -279,10 +282,12 @@ mod tests {
|
||||
},
|
||||
),
|
||||
simple_key_value(
|
||||
EncodedString {
|
||||
quotes: false,
|
||||
value: "param2".to_string(),
|
||||
encoded: "param2".to_string(),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "param2".to_string(),
|
||||
encoded: "param2".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(0, 0, 0, 0),
|
||||
},
|
||||
Template {
|
||||
|
@ -78,48 +78,58 @@ pub fn eval_asserts(
|
||||
});
|
||||
}
|
||||
Ok(expected) => {
|
||||
let header_name = &header.key.value;
|
||||
let actuals = http_response.get_header_values(header_name);
|
||||
if actuals.is_empty() {
|
||||
asserts.push(AssertResult::Header {
|
||||
actual: Err(Error {
|
||||
source_info: header.key.source_info.clone(),
|
||||
inner: RunnerError::QueryHeaderNotFound,
|
||||
assert: false,
|
||||
}),
|
||||
expected,
|
||||
source_info: header.key.source_info.clone(),
|
||||
});
|
||||
} else if actuals.len() == 1 {
|
||||
let actual = actuals.first().unwrap().to_string();
|
||||
asserts.push(AssertResult::Header {
|
||||
actual: Ok(actual),
|
||||
expected,
|
||||
source_info: header.value.clone().source_info,
|
||||
});
|
||||
} else {
|
||||
// failure by default
|
||||
// expected value not found in the list
|
||||
// actual is therefore the full list
|
||||
let mut actual = format!(
|
||||
"[{}]",
|
||||
actuals
|
||||
.iter()
|
||||
.map(|v| format!("\"{v}\""))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
for value in actuals {
|
||||
if value == expected {
|
||||
actual = value;
|
||||
break;
|
||||
match eval_template(&header.key, variables) {
|
||||
Ok(header_name) => {
|
||||
let actuals = http_response.get_header_values(&header_name);
|
||||
if actuals.is_empty() {
|
||||
asserts.push(AssertResult::Header {
|
||||
actual: Err(Error {
|
||||
source_info: header.key.source_info.clone(),
|
||||
inner: RunnerError::QueryHeaderNotFound,
|
||||
assert: false,
|
||||
}),
|
||||
expected,
|
||||
source_info: header.key.source_info.clone(),
|
||||
});
|
||||
} else if actuals.len() == 1 {
|
||||
let actual = actuals.first().unwrap().to_string();
|
||||
asserts.push(AssertResult::Header {
|
||||
actual: Ok(actual),
|
||||
expected,
|
||||
source_info: header.value.clone().source_info,
|
||||
});
|
||||
} else {
|
||||
// failure by default
|
||||
// expected value not found in the list
|
||||
// actual is therefore the full list
|
||||
let mut actual = format!(
|
||||
"[{}]",
|
||||
actuals
|
||||
.iter()
|
||||
.map(|v| format!("\"{v}\""))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
);
|
||||
for value in actuals {
|
||||
if value == expected {
|
||||
actual = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
asserts.push(AssertResult::Header {
|
||||
actual: Ok(actual),
|
||||
expected,
|
||||
source_info: header.value.clone().source_info,
|
||||
});
|
||||
}
|
||||
}
|
||||
asserts.push(AssertResult::Header {
|
||||
actual: Ok(actual),
|
||||
expected,
|
||||
source_info: header.value.clone().source_info,
|
||||
});
|
||||
Err(e) => {
|
||||
asserts.push(AssertResult::Header {
|
||||
actual: Err(e),
|
||||
expected,
|
||||
source_info: header.value.clone().source_info,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ pub enum SectionValue {
|
||||
pub struct Cookie {
|
||||
pub line_terminators: Vec<LineTerminator>,
|
||||
pub space0: Whitespace,
|
||||
pub name: EncodedString,
|
||||
pub name: Template,
|
||||
pub space1: Whitespace,
|
||||
pub space2: Whitespace,
|
||||
pub value: Template,
|
||||
@ -232,7 +232,7 @@ pub struct Cookie {
|
||||
pub struct KeyValue {
|
||||
pub line_terminators: Vec<LineTerminator>,
|
||||
pub space0: Whitespace,
|
||||
pub key: EncodedString,
|
||||
pub key: Template,
|
||||
pub space1: Whitespace,
|
||||
pub space2: Whitespace,
|
||||
pub value: Template,
|
||||
@ -249,7 +249,7 @@ pub enum MultipartParam {
|
||||
pub struct FileParam {
|
||||
pub line_terminators: Vec<LineTerminator>,
|
||||
pub space0: Whitespace,
|
||||
pub key: EncodedString,
|
||||
pub key: Template,
|
||||
pub space1: Whitespace,
|
||||
pub space2: Whitespace,
|
||||
pub value: FileValue,
|
||||
@ -269,7 +269,7 @@ pub struct FileValue {
|
||||
pub struct Capture {
|
||||
pub line_terminators: Vec<LineTerminator>,
|
||||
pub space0: Whitespace,
|
||||
pub name: EncodedString,
|
||||
pub name: Template,
|
||||
pub space1: Whitespace,
|
||||
pub space2: Whitespace,
|
||||
pub query: Query,
|
||||
|
@ -195,7 +195,7 @@ impl HtmlFormatter {
|
||||
self.fmt_lts(&kv.line_terminators);
|
||||
self.fmt_span_open("line");
|
||||
self.fmt_space(&kv.space0);
|
||||
self.fmt_string(&kv.key.encoded);
|
||||
self.fmt_template(&kv.key);
|
||||
self.fmt_space(&kv.space1);
|
||||
self.buffer.push(':');
|
||||
self.fmt_space(&kv.space2);
|
||||
@ -274,7 +274,7 @@ impl HtmlFormatter {
|
||||
self.fmt_lts(¶m.line_terminators);
|
||||
self.fmt_span_open("line");
|
||||
self.fmt_space(¶m.space0);
|
||||
self.fmt_string(¶m.key.encoded);
|
||||
self.fmt_template(¶m.key);
|
||||
self.fmt_space(¶m.space1);
|
||||
self.buffer.push(':');
|
||||
self.fmt_space(¶m.space2);
|
||||
@ -306,7 +306,7 @@ impl HtmlFormatter {
|
||||
self.fmt_lts(&cookie.line_terminators);
|
||||
self.fmt_span_open("line");
|
||||
self.fmt_space(&cookie.space0);
|
||||
self.fmt_span("name", &cookie.name.value);
|
||||
self.fmt_template(&cookie.name);
|
||||
self.fmt_space(&cookie.space1);
|
||||
self.buffer.push(':');
|
||||
self.fmt_space(&cookie.space2);
|
||||
@ -319,7 +319,7 @@ impl HtmlFormatter {
|
||||
self.fmt_lts(&capture.line_terminators);
|
||||
self.fmt_span_open("line");
|
||||
self.fmt_space(&capture.space0);
|
||||
self.fmt_span("name", &capture.name.value);
|
||||
self.fmt_template(&capture.name);
|
||||
self.fmt_space(&capture.space1);
|
||||
self.buffer.push(':');
|
||||
self.fmt_space(&capture.space2);
|
||||
|
319
packages/hurl_core/src/parser/key_string.rs
Normal file
319
packages/hurl_core/src/parser/key_string.rs
Normal file
@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Hurl (https://hurl.dev)
|
||||
* Copyright (C) 2023 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 crate::parser::error::*;
|
||||
use crate::parser::primitives::*;
|
||||
use crate::parser::reader::Reader;
|
||||
use crate::parser::template::template;
|
||||
use crate::parser::{string, ParseResult};
|
||||
|
||||
pub fn parse(reader: &mut Reader) -> ParseResult<Template> {
|
||||
let start = reader.state.clone();
|
||||
|
||||
let mut elements = vec![];
|
||||
loop {
|
||||
match template(reader) {
|
||||
Ok(expr) => {
|
||||
let element = TemplateElement::Expression(expr);
|
||||
elements.push(element);
|
||||
}
|
||||
Err(e) => {
|
||||
if e.recoverable {
|
||||
let value = key_string_content(reader)?;
|
||||
if value.is_empty() {
|
||||
break;
|
||||
}
|
||||
let encoded: String = reader.buffer[start.cursor..reader.state.cursor]
|
||||
.iter()
|
||||
.collect();
|
||||
let element = TemplateElement::String { value, encoded };
|
||||
elements.push(element);
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if elements.is_empty() {
|
||||
return Err(Error {
|
||||
pos: start.pos,
|
||||
recoverable: false,
|
||||
inner: ParseError::Expecting {
|
||||
value: "key-string".to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
if let Some(TemplateElement::String { encoded, .. }) = elements.first() {
|
||||
if encoded.starts_with('[') {
|
||||
return Err(Error {
|
||||
pos: start.pos,
|
||||
recoverable: false,
|
||||
inner: ParseError::Expecting {
|
||||
value: "key-string".to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let end = reader.state.clone();
|
||||
Ok(Template {
|
||||
delimiter: None,
|
||||
elements,
|
||||
source_info: SourceInfo {
|
||||
start: start.pos,
|
||||
end: end.pos,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn key_string_content(reader: &mut Reader) -> ParseResult<String> {
|
||||
let mut s = String::new();
|
||||
loop {
|
||||
match key_string_escaped_char(reader) {
|
||||
Ok(c) => {
|
||||
s.push(c);
|
||||
}
|
||||
Err(e) => {
|
||||
if e.recoverable {
|
||||
let s2 = key_string_text(reader);
|
||||
if s2.is_empty() {
|
||||
break;
|
||||
} else {
|
||||
s.push_str(&s2);
|
||||
}
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn key_string_text(reader: &mut Reader) -> String {
|
||||
let mut s = String::new();
|
||||
loop {
|
||||
let save = reader.state.clone();
|
||||
match reader.read() {
|
||||
None => break,
|
||||
Some(c) => {
|
||||
if c.is_alphanumeric()
|
||||
|| c == '_'
|
||||
|| c == '-'
|
||||
|| c == '.'
|
||||
|| c == '['
|
||||
|| c == ']'
|
||||
|| c == '@'
|
||||
|| c == '$'
|
||||
{
|
||||
s.push(c);
|
||||
} else {
|
||||
reader.state = save;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s
|
||||
}
|
||||
|
||||
fn key_string_escaped_char(reader: &mut Reader) -> ParseResult<char> {
|
||||
try_literal("\\", reader)?;
|
||||
let start = reader.state.clone();
|
||||
match reader.read() {
|
||||
Some('#') => Ok('#'),
|
||||
Some(':') => Ok(':'),
|
||||
Some('\\') => Ok('\\'),
|
||||
Some('/') => Ok('/'),
|
||||
Some('b') => Ok('\x08'),
|
||||
Some('f') => Ok('\x0c'),
|
||||
Some('n') => Ok('\n'),
|
||||
Some('r') => Ok('\r'),
|
||||
Some('t') => Ok('\t'),
|
||||
Some('u') => string::unicode(reader),
|
||||
_ => Err(Error {
|
||||
pos: start.pos,
|
||||
recoverable: false,
|
||||
inner: ParseError::EscapeChar,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_key_string() {
|
||||
let mut reader = Reader::new("aaa\\: ");
|
||||
assert_eq!(
|
||||
parse(&mut reader).unwrap(),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "aaa:".to_string(),
|
||||
encoded: "aaa\\:".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(1, 1, 1, 6),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 5);
|
||||
|
||||
let mut reader = Reader::new("$top:");
|
||||
assert_eq!(
|
||||
parse(&mut reader).unwrap(),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "$top".to_string(),
|
||||
encoded: "$top".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(1, 1, 1, 5),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 4);
|
||||
|
||||
let mut reader = Reader::new("key\\u{20}\\u{3a} :");
|
||||
assert_eq!(
|
||||
parse(&mut reader).unwrap(),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "key :".to_string(),
|
||||
encoded: "key\\u{20}\\u{3a}".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(1, 1, 1, 16),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 15);
|
||||
|
||||
let mut reader = Reader::new("values\\u{5b}0\\u{5d} :");
|
||||
assert_eq!(
|
||||
parse(&mut reader).unwrap(),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "values[0]".to_string(),
|
||||
encoded: "values\\u{5b}0\\u{5d}".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(1, 1, 1, 20),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 19);
|
||||
|
||||
let mut reader = Reader::new("values[0] :");
|
||||
assert_eq!(
|
||||
parse(&mut reader).unwrap(),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "values[0]".to_string(),
|
||||
encoded: "values[0]".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(1, 1, 1, 10),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 9);
|
||||
|
||||
let mut reader = Reader::new("\\u{5b}0\\u{5d}");
|
||||
assert_eq!(
|
||||
parse(&mut reader).unwrap(),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "[0]".to_string(),
|
||||
encoded: "\\u{5b}0\\u{5d}".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(1, 1, 1, 14),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_string_error() {
|
||||
let mut reader = Reader::new("");
|
||||
let error = parse(&mut reader).err().unwrap();
|
||||
assert!(!error.recoverable);
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 1 });
|
||||
|
||||
let mut reader = Reader::new("{{key");
|
||||
let error = parse(&mut reader).err().unwrap();
|
||||
assert!(!error.recoverable);
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 6 });
|
||||
|
||||
// key string can not start with a '[' (reserved for section)
|
||||
let mut reader = Reader::new("[0]:");
|
||||
let error = parse(&mut reader).err().unwrap();
|
||||
assert!(!error.recoverable);
|
||||
assert_eq!(reader.state.cursor, 3);
|
||||
|
||||
let mut reader = Reader::new("\\l");
|
||||
let error = parse(&mut reader).err().unwrap();
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 2 });
|
||||
assert_eq!(error.inner, ParseError::EscapeChar);
|
||||
|
||||
let mut reader = Reader::new(r#"{"id":1}"#);
|
||||
let error = parse(&mut reader).err().unwrap();
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 1 });
|
||||
assert_eq!(
|
||||
error.inner,
|
||||
ParseError::Expecting {
|
||||
value: "key-string".to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_string_content() {
|
||||
let mut reader = Reader::new("aaa\\:");
|
||||
assert_eq!(key_string_content(&mut reader).unwrap(), "aaa:");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_string_text() {
|
||||
let mut reader = Reader::new("aaa\\:");
|
||||
assert_eq!(key_string_text(&mut reader), "aaa");
|
||||
assert_eq!(reader.state.cursor, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_string_escaped_char() {
|
||||
let mut reader = Reader::new("\\u{0a}");
|
||||
assert_eq!(key_string_escaped_char(&mut reader).unwrap(), '\n');
|
||||
assert_eq!(reader.state.cursor, 6);
|
||||
|
||||
let mut reader = Reader::new("\\:");
|
||||
assert_eq!(key_string_escaped_char(&mut reader).unwrap(), ':');
|
||||
assert_eq!(reader.state.cursor, 2);
|
||||
|
||||
let mut reader = Reader::new("x");
|
||||
let error = key_string_escaped_char(&mut reader).err().unwrap();
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 1 });
|
||||
assert_eq!(
|
||||
error.inner,
|
||||
ParseError::Expecting {
|
||||
value: "\\".to_string()
|
||||
}
|
||||
);
|
||||
assert!(error.recoverable);
|
||||
assert_eq!(reader.state.cursor, 0);
|
||||
}
|
||||
}
|
@ -41,6 +41,7 @@ mod expr;
|
||||
mod filename;
|
||||
mod filter;
|
||||
mod json;
|
||||
mod key_string;
|
||||
mod multiline;
|
||||
mod parsers;
|
||||
mod predicate;
|
||||
|
@ -323,7 +323,8 @@ mod tests {
|
||||
body: None,
|
||||
source_info: SourceInfo::new(1, 1, 1, 21),
|
||||
};
|
||||
assert_eq!(request(&mut reader), Ok(default_request));
|
||||
assert_eq!(request(&mut reader).unwrap(), default_request);
|
||||
assert_eq!(reader.state.cursor, 20);
|
||||
|
||||
let mut reader = Reader::new("GET http://google.fr # comment");
|
||||
let default_request = Request {
|
||||
@ -363,11 +364,12 @@ mod tests {
|
||||
body: None,
|
||||
source_info: SourceInfo::new(1, 1, 1, 32),
|
||||
};
|
||||
assert_eq!(request(&mut reader), Ok(default_request));
|
||||
assert_eq!(request(&mut reader).unwrap(), default_request);
|
||||
assert_eq!(reader.state.cursor, 31);
|
||||
|
||||
let mut reader = Reader::new("GET http://google.fr\nGET http://google.fr");
|
||||
let r = request(&mut reader);
|
||||
assert_eq!(r.unwrap().method, Method("GET".to_string()));
|
||||
let r = request(&mut reader).unwrap();
|
||||
assert_eq!(r.method, Method("GET".to_string()));
|
||||
assert_eq!(reader.state.cursor, 21);
|
||||
let r = request(&mut reader).unwrap();
|
||||
assert_eq!(r.method, Method("GET".to_string()));
|
||||
|
@ -20,7 +20,7 @@ use crate::parser::combinators::*;
|
||||
use crate::parser::error::*;
|
||||
use crate::parser::reader::Reader;
|
||||
use crate::parser::string::*;
|
||||
use crate::parser::{base64, filename, ParseResult};
|
||||
use crate::parser::{base64, filename, key_string, ParseResult};
|
||||
|
||||
pub fn space(reader: &mut Reader) -> ParseResult<Whitespace> {
|
||||
let start = reader.state.clone();
|
||||
@ -268,7 +268,7 @@ pub fn newline(reader: &mut Reader) -> ParseResult<Whitespace> {
|
||||
pub fn key_value(reader: &mut Reader) -> ParseResult<KeyValue> {
|
||||
let line_terminators = optional_line_terminators(reader)?;
|
||||
let space0 = zero_or_more_spaces(reader)?;
|
||||
let key = recover(unquoted_string_key, reader)?;
|
||||
let key = recover(key_string::parse, reader)?;
|
||||
let space1 = zero_or_more_spaces(reader)?;
|
||||
recover(|reader1| literal(":", reader1), reader)?;
|
||||
let space2 = zero_or_more_spaces(reader)?;
|
||||
@ -759,10 +759,12 @@ mod tests {
|
||||
value: String::new(),
|
||||
source_info: SourceInfo::new(1, 1, 1, 1),
|
||||
},
|
||||
key: EncodedString {
|
||||
quotes: false,
|
||||
value: "message".to_string(),
|
||||
encoded: "message".to_string(),
|
||||
key: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "message".to_string(),
|
||||
encoded: "message".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(1, 1, 1, 8),
|
||||
},
|
||||
space1: Whitespace {
|
||||
@ -818,6 +820,82 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_value_template() {
|
||||
let mut reader = Reader::new("{{key}}: value");
|
||||
assert_eq!(
|
||||
key_value(&mut reader).unwrap(),
|
||||
KeyValue {
|
||||
line_terminators: vec![],
|
||||
space0: Whitespace {
|
||||
value: String::new(),
|
||||
source_info: SourceInfo::new(1, 1, 1, 1),
|
||||
},
|
||||
key: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::Expression(Expr {
|
||||
space0: Whitespace {
|
||||
value: String::new(),
|
||||
source_info: SourceInfo::new(1, 3, 1, 3),
|
||||
},
|
||||
variable: Variable {
|
||||
name: "key".to_string(),
|
||||
source_info: SourceInfo::new(1, 3, 1, 6)
|
||||
},
|
||||
space1: Whitespace {
|
||||
value: String::new(),
|
||||
source_info: SourceInfo::new(1, 6, 1, 6),
|
||||
},
|
||||
})],
|
||||
source_info: SourceInfo::new(1, 1, 1, 8),
|
||||
},
|
||||
space1: Whitespace {
|
||||
value: String::new(),
|
||||
source_info: SourceInfo::new(1, 8, 1, 8),
|
||||
},
|
||||
space2: Whitespace {
|
||||
value: " ".to_string(),
|
||||
source_info: SourceInfo::new(1, 9, 1, 10),
|
||||
},
|
||||
value: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "value".to_string(),
|
||||
encoded: "value".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(1, 10, 1, 15),
|
||||
},
|
||||
line_terminator0: LineTerminator {
|
||||
space0: Whitespace {
|
||||
value: "".to_string(),
|
||||
source_info: SourceInfo::new(1, 15, 1, 15),
|
||||
},
|
||||
comment: None,
|
||||
newline: Whitespace {
|
||||
value: String::new(),
|
||||
source_info: SourceInfo::new(1, 15, 1, 15),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_key_value_recover() {
|
||||
let mut reader = Reader::new("{{key");
|
||||
let error = key_value(&mut reader).err().unwrap();
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 6 });
|
||||
assert!(error.recoverable);
|
||||
assert_eq!(reader.state.cursor, 5); // does not reset cursor
|
||||
|
||||
let mut reader = Reader::new("GET http://google.fr");
|
||||
let error = key_value(&mut reader).err().unwrap();
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 5 });
|
||||
assert!(error.recoverable);
|
||||
assert_eq!(reader.state.cursor, 5); // does not reset cursor
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean() {
|
||||
let mut reader = Reader::new("true");
|
||||
|
@ -24,7 +24,7 @@ use crate::parser::primitives::*;
|
||||
use crate::parser::query::query;
|
||||
use crate::parser::reader::Reader;
|
||||
use crate::parser::string::*;
|
||||
use crate::parser::{filename, ParseResult};
|
||||
use crate::parser::{filename, key_string, ParseResult};
|
||||
|
||||
pub fn request_sections(reader: &mut Reader) -> ParseResult<Vec<Section>> {
|
||||
let sections = zero_or_more(request_section, reader)?;
|
||||
@ -170,7 +170,7 @@ fn cookie(reader: &mut Reader) -> ParseResult<Cookie> {
|
||||
// let start = reader.state.clone();
|
||||
let line_terminators = optional_line_terminators(reader)?;
|
||||
let space0 = zero_or_more_spaces(reader)?;
|
||||
let name = unquoted_string_key(reader)?;
|
||||
let name = recover(key_string::parse, reader)?;
|
||||
let space1 = zero_or_more_spaces(reader)?;
|
||||
recover(|p1| literal(":", p1), reader)?;
|
||||
let space2 = zero_or_more_spaces(reader)?;
|
||||
@ -206,7 +206,7 @@ fn multipart_param(reader: &mut Reader) -> ParseResult<MultipartParam> {
|
||||
fn file_param(reader: &mut Reader) -> ParseResult<FileParam> {
|
||||
let line_terminators = optional_line_terminators(reader)?;
|
||||
let space0 = zero_or_more_spaces(reader)?;
|
||||
let key = recover(unquoted_string_key, reader)?;
|
||||
let key = recover(key_string::parse, reader)?;
|
||||
let space1 = zero_or_more_spaces(reader)?;
|
||||
recover(|reader1| literal(":", reader1), reader)?;
|
||||
let space2 = zero_or_more_spaces(reader)?;
|
||||
@ -291,7 +291,7 @@ fn file_content_type(reader: &mut Reader) -> ParseResult<String> {
|
||||
fn capture(reader: &mut Reader) -> ParseResult<Capture> {
|
||||
let line_terminators = optional_line_terminators(reader)?;
|
||||
let space0 = zero_or_more_spaces(reader)?;
|
||||
let name = unquoted_string_key(reader)?;
|
||||
let name = recover(key_string::parse, reader)?;
|
||||
let space1 = zero_or_more_spaces(reader)?;
|
||||
recover(|p1| literal(":", p1), reader)?;
|
||||
let space2 = zero_or_more_spaces(reader)?;
|
||||
@ -808,7 +808,7 @@ mod tests {
|
||||
fn test_cookie() {
|
||||
let mut reader = Reader::new("Foo: Bar");
|
||||
let c = cookie(&mut reader).unwrap();
|
||||
assert_eq!(c.name.value, String::from("Foo"));
|
||||
assert_eq!(c.name.to_string(), String::from("Foo"));
|
||||
assert_eq!(
|
||||
c.value,
|
||||
Template {
|
||||
@ -1153,12 +1153,14 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
capture0.name,
|
||||
EncodedString {
|
||||
quotes: false,
|
||||
value: String::from("url"),
|
||||
encoded: String::from("url"),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "url".to_string(),
|
||||
encoded: "url".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(1, 1, 1, 4),
|
||||
}
|
||||
},
|
||||
);
|
||||
assert_eq!(
|
||||
capture0.query,
|
||||
@ -1330,10 +1332,12 @@ mod tests {
|
||||
value: String::new(),
|
||||
source_info: SourceInfo::new(2, 1, 2, 1)
|
||||
},
|
||||
key: EncodedString {
|
||||
value: "user".to_string(),
|
||||
encoded: "user".to_string(),
|
||||
quotes: false,
|
||||
key: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "user".to_string(),
|
||||
encoded: "user".to_string()
|
||||
}],
|
||||
source_info: SourceInfo::new(2, 1, 2, 5),
|
||||
},
|
||||
space1: Whitespace {
|
||||
|
@ -23,7 +23,7 @@ use crate::parser::reader::Reader;
|
||||
use crate::parser::{template, ParseResult};
|
||||
|
||||
/// Steps:
|
||||
/// 1- parse String until end of stream or end of line or #
|
||||
/// 1- parse String until end of stream, end of line
|
||||
/// the string does not contain trailing space
|
||||
/// 2- templatize
|
||||
pub fn unquoted_template(reader: &mut Reader) -> ParseResult<Template> {
|
||||
@ -77,69 +77,6 @@ pub fn unquoted_template(reader: &mut Reader) -> ParseResult<Template> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unquoted_string_key(reader: &mut Reader) -> ParseResult<EncodedString> {
|
||||
let start = reader.state.pos.clone();
|
||||
let mut value = String::new();
|
||||
let mut encoded = String::new();
|
||||
loop {
|
||||
let save = reader.state.clone();
|
||||
match escape_char(reader) {
|
||||
Ok(c) => {
|
||||
value.push(c);
|
||||
encoded.push_str(reader.peek_back(save.cursor).as_str())
|
||||
}
|
||||
Err(e) => {
|
||||
if e.recoverable {
|
||||
reader.state = save.clone();
|
||||
match reader.read() {
|
||||
None => break,
|
||||
Some(c) => {
|
||||
if c.is_alphanumeric()
|
||||
|| c == '_'
|
||||
|| c == '-'
|
||||
|| c == '.'
|
||||
|| c == '['
|
||||
|| c == ']'
|
||||
|| c == '@'
|
||||
|| c == '$'
|
||||
{
|
||||
value.push(c);
|
||||
encoded.push_str(reader.peek_back(save.cursor).as_str())
|
||||
} else {
|
||||
reader.state = save;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check nonempty/ starts with [
|
||||
if value.is_empty() || encoded.starts_with('[') {
|
||||
return Err(Error {
|
||||
pos: start,
|
||||
recoverable: true,
|
||||
inner: ParseError::Expecting {
|
||||
value: "key string".to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let quotes = false;
|
||||
let end = reader.state.pos.clone();
|
||||
let source_info = SourceInfo { start, end };
|
||||
Ok(EncodedString {
|
||||
value,
|
||||
encoded,
|
||||
quotes,
|
||||
source_info,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: should return an EncodedString
|
||||
// (decoding escape sequence)
|
||||
pub fn quoted_oneline_string(reader: &mut Reader) -> ParseResult<String> {
|
||||
@ -295,7 +232,7 @@ fn escape_char(reader: &mut Reader) -> ParseResult<char> {
|
||||
}
|
||||
}
|
||||
|
||||
fn unicode(reader: &mut Reader) -> ParseResult<char> {
|
||||
pub(crate) fn unicode(reader: &mut Reader) -> ParseResult<char> {
|
||||
literal("{", reader)?;
|
||||
let v = hex_value(reader)?;
|
||||
let c = match std::char::from_u32(v) {
|
||||
@ -432,147 +369,6 @@ mod tests {
|
||||
assert_eq!(reader.state.cursor, 20);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unquoted_template_trailing_space() {
|
||||
let mut reader = Reader::new("hello world # comment");
|
||||
assert_eq!(
|
||||
unquoted_template(&mut reader).unwrap(),
|
||||
Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "hello world".to_string(),
|
||||
encoded: "hello world".to_string(),
|
||||
},],
|
||||
source_info: SourceInfo::new(1, 1, 1, 12),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 11);
|
||||
assert_eq!(
|
||||
reader.state.pos,
|
||||
Pos {
|
||||
line: 1,
|
||||
column: 12
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unquoted_key() {
|
||||
let mut reader = Reader::new("key");
|
||||
assert_eq!(
|
||||
unquoted_string_key(&mut reader).unwrap(),
|
||||
EncodedString {
|
||||
value: "key".to_string(),
|
||||
encoded: "key".to_string(),
|
||||
quotes: false,
|
||||
source_info: SourceInfo::new(1, 1, 1, 4),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 3);
|
||||
|
||||
let mut reader = Reader::new("key\\u{20}\\u{3a} :");
|
||||
assert_eq!(
|
||||
unquoted_string_key(&mut reader).unwrap(),
|
||||
EncodedString {
|
||||
value: "key :".to_string(),
|
||||
encoded: "key\\u{20}\\u{3a}".to_string(),
|
||||
quotes: false,
|
||||
source_info: SourceInfo::new(1, 1, 1, 16),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 15);
|
||||
|
||||
let mut reader = Reader::new("$top:");
|
||||
assert_eq!(
|
||||
unquoted_string_key(&mut reader).unwrap(),
|
||||
EncodedString {
|
||||
value: "$top".to_string(),
|
||||
encoded: "$top".to_string(),
|
||||
quotes: false,
|
||||
source_info: SourceInfo::new(1, 1, 1, 5),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unquoted_key_with_square_bracket() {
|
||||
let mut reader = Reader::new("values\\u{5b}0\\u{5d} :");
|
||||
assert_eq!(
|
||||
unquoted_string_key(&mut reader).unwrap(),
|
||||
EncodedString {
|
||||
value: "values[0]".to_string(),
|
||||
encoded: "values\\u{5b}0\\u{5d}".to_string(),
|
||||
quotes: false,
|
||||
source_info: SourceInfo::new(1, 1, 1, 20),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 19);
|
||||
|
||||
let mut reader = Reader::new("values[0] :");
|
||||
assert_eq!(
|
||||
unquoted_string_key(&mut reader).unwrap(),
|
||||
EncodedString {
|
||||
value: "values[0]".to_string(),
|
||||
encoded: "values[0]".to_string(),
|
||||
quotes: false,
|
||||
source_info: SourceInfo::new(1, 1, 1, 10),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unquoted_keys_ignore_start_square_bracket() {
|
||||
let mut reader = Reader::new("[0]:");
|
||||
let error = unquoted_string_key(&mut reader).err().unwrap();
|
||||
assert!(error.recoverable);
|
||||
assert_eq!(reader.state.cursor, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unquoted_keys_accept_start_escape_square_bracket() {
|
||||
let mut reader = Reader::new("\\u{5b}0\\u{5d}");
|
||||
assert_eq!(
|
||||
unquoted_string_key(&mut reader).unwrap(),
|
||||
EncodedString {
|
||||
value: "[0]".to_string(),
|
||||
encoded: "\\u{5b}0\\u{5d}".to_string(),
|
||||
quotes: false,
|
||||
source_info: SourceInfo::new(1, 1, 1, 14),
|
||||
}
|
||||
);
|
||||
assert_eq!(reader.state.cursor, 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unquoted_key_error() {
|
||||
let mut reader = Reader::new("");
|
||||
let error = unquoted_string_key(&mut reader).err().unwrap();
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 1 });
|
||||
assert_eq!(
|
||||
error.inner,
|
||||
ParseError::Expecting {
|
||||
value: "key string".to_string()
|
||||
}
|
||||
);
|
||||
|
||||
let mut reader = Reader::new("\\l");
|
||||
let error = unquoted_string_key(&mut reader).err().unwrap();
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 2 });
|
||||
assert_eq!(error.inner, ParseError::EscapeChar);
|
||||
|
||||
let mut reader = Reader::new(r#"{"id":1}"#);
|
||||
let error = unquoted_string_key(&mut reader).err().unwrap();
|
||||
assert_eq!(error.pos, Pos { line: 1, column: 1 });
|
||||
assert_eq!(
|
||||
error.inner,
|
||||
ParseError::Expecting {
|
||||
value: "key string".to_string()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_quoted_template() {
|
||||
let mut reader = Reader::new("\"\"");
|
||||
|
@ -15,7 +15,8 @@
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
use crate::ast::{Pos, SourceInfo, TemplateElement};
|
||||
use crate::ast::{Expr, Pos, SourceInfo, TemplateElement};
|
||||
use crate::parser::primitives::{literal, try_literal};
|
||||
use crate::parser::reader::*;
|
||||
use crate::parser::{error, expr, ParseResult};
|
||||
|
||||
@ -24,6 +25,13 @@ pub struct EncodedString {
|
||||
pub chars: Vec<(char, String, Pos)>,
|
||||
}
|
||||
|
||||
pub fn template(reader: &mut Reader) -> ParseResult<Expr> {
|
||||
try_literal("{{", reader)?;
|
||||
let expression = expr::parse2(reader)?;
|
||||
literal("}}", reader)?;
|
||||
Ok(expression)
|
||||
}
|
||||
|
||||
pub fn templatize(encoded_string: EncodedString) -> ParseResult<Vec<TemplateElement>> {
|
||||
enum State {
|
||||
String,
|
||||
|
@ -262,7 +262,7 @@ mod tests {
|
||||
// A valid complete XML
|
||||
let input = xml;
|
||||
let output = xml;
|
||||
let mut reader = Reader::new(&input);
|
||||
let mut reader = Reader::new(input);
|
||||
assert_eq!(parse(&mut reader).unwrap(), String::from(output),);
|
||||
assert_eq!(reader.state.cursor, 520);
|
||||
|
||||
|
@ -227,7 +227,7 @@ fn get_json_version(version_value: &VersionValue) -> Option<String> {
|
||||
impl ToJson for KeyValue {
|
||||
fn to_json(&self) -> JValue {
|
||||
let attributes = vec![
|
||||
("name".to_string(), JValue::String(self.key.value.clone())),
|
||||
("name".to_string(), JValue::String(self.key.to_string())),
|
||||
("value".to_string(), JValue::String(self.value.to_string())),
|
||||
];
|
||||
JValue::Object(attributes)
|
||||
@ -246,7 +246,7 @@ impl ToJson for MultipartParam {
|
||||
impl ToJson for FileParam {
|
||||
fn to_json(&self) -> JValue {
|
||||
let mut attributes = vec![
|
||||
("name".to_string(), JValue::String(self.key.value.clone())),
|
||||
("name".to_string(), JValue::String(self.key.to_string())),
|
||||
(
|
||||
"filename".to_string(),
|
||||
JValue::String(self.value.filename.value.clone()),
|
||||
@ -262,7 +262,7 @@ impl ToJson for FileParam {
|
||||
impl ToJson for Cookie {
|
||||
fn to_json(&self) -> JValue {
|
||||
let attributes = vec![
|
||||
("name".to_string(), JValue::String(self.name.value.clone())),
|
||||
("name".to_string(), JValue::String(self.name.to_string())),
|
||||
("value".to_string(), JValue::String(self.value.to_string())),
|
||||
];
|
||||
JValue::Object(attributes)
|
||||
@ -311,7 +311,7 @@ impl ToJson for EntryOption {
|
||||
impl ToJson for Capture {
|
||||
fn to_json(&self) -> JValue {
|
||||
let mut attributes = vec![
|
||||
("name".to_string(), JValue::String(self.name.value.clone())),
|
||||
("name".to_string(), JValue::String(self.name.to_string())),
|
||||
("query".to_string(), self.query.to_json()),
|
||||
];
|
||||
if !self.filters.is_empty() {
|
||||
@ -709,10 +709,12 @@ pub mod tests {
|
||||
headers: vec![KeyValue {
|
||||
line_terminators: vec![],
|
||||
space0: whitespace(),
|
||||
key: EncodedString {
|
||||
value: "Foo".to_string(),
|
||||
encoded: "unused".to_string(),
|
||||
quotes: false,
|
||||
key: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "Foo".to_string(),
|
||||
encoded: "unused".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(0, 0, 0, 0),
|
||||
},
|
||||
space1: whitespace(),
|
||||
@ -824,10 +826,12 @@ pub mod tests {
|
||||
Capture {
|
||||
line_terminators: vec![],
|
||||
space0: whitespace(),
|
||||
name: EncodedString {
|
||||
value: "size".to_string(),
|
||||
encoded: "unused".to_string(),
|
||||
quotes: false,
|
||||
name: Template {
|
||||
delimiter: None,
|
||||
elements: vec![TemplateElement::String {
|
||||
value: "size".to_string(),
|
||||
encoded: "unused".to_string(),
|
||||
}],
|
||||
source_info: SourceInfo::new(0, 0, 0, 0),
|
||||
},
|
||||
space1: whitespace(),
|
||||
|
Loading…
Reference in New Issue
Block a user