Add curl input to hurlfmt

This commit is contained in:
Fabrice Reix 2023-04-22 09:55:32 +02:00
parent 2f0fa3c016
commit b90dbf4925
No known key found for this signature in database
GPG Key ID: BF5213154B2E7155
10 changed files with 137 additions and 31 deletions

View File

@ -0,0 +1,3 @@
curl http://localhost:8000/hello
curl http://localhost:8000/custom-headers -H 'Fruit:Raspberry' -H 'Fruit:Apple' -H 'Fruit:Banana' -H 'Fruit: Grape' -H 'Color:Green'
curl --header 'Content-Type: application/json' --data $'{\n "name": "Bob",\n "password": "&secret\\\\\'<>",\n "age": 30,\n "strict": true,\n "spacing": "\\n",\n "g_clef": "\\uD834\\uDD1E",\n "items": [true, "true", 1],\n "variable": "\\\\"\n}' 'http://localhost:8000/post-json'

View File

@ -0,0 +1,24 @@
GET http://localhost:8000/hello
GET http://localhost:8000/custom-headers
Fruit: Raspberry
Fruit: Apple
Fruit: Banana
Fruit: Grape
Color: Green
POST http://localhost:8000/post-json
Content-Type: application/json
```
{
"name": "Bob",
"password": "&secret\\'<>",
"age": 30,
"strict": true,
"spacing": "\n",
"g_clef": "\uD834\uDD1E",
"items": [true, "true", 1],
"variable": "\\"
}
```

View File

@ -0,0 +1,4 @@
Set-StrictMode -Version latest
$ErrorActionPreference = 'Stop'
hurl tests_ok/import_curl.out > $null # Validate expected file
hurlfmt --in curl tests_ok/import_curl.in

View File

@ -0,0 +1,4 @@
#!/bin/bash
set -Eeuo pipefail
hurl tests_ok/import_curl.out >/dev/null # Validate expected file
hurlfmt --in curl tests_ok/import_curl.in

View File

@ -39,6 +39,7 @@ pub fn color(arg_matches: &ArgMatches) -> bool {
pub fn input_format(arg_matches: &ArgMatches) -> Result<InputFormat, OptionsError> {
match get_string(arg_matches, "input_format").unwrap().as_str() {
"hurl" => Ok(InputFormat::Hurl),
"curl" => Ok(InputFormat::Curl),
v => Err(OptionsError::Error(format!("Invalid input format {v}"))),
}
}

View File

@ -38,6 +38,7 @@ pub struct Options {
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InputFormat {
Curl,
Hurl,
}

View File

@ -94,7 +94,7 @@ impl Parser {
Some('t') => '\t',
Some('r') => '\r',
Some(c) => c,
_ => return Err(format!("Invalid escape at index {}", self.index)),
_ => return Err(format!("Invalid escape at column {}", self.index + 1)),
};
value.push(c2);
} else if c1 == delimiter {
@ -104,8 +104,8 @@ impl Parser {
}
}
Err(format!(
"Missing delimiter {delimiter} at index {}",
self.index
"Missing delimiter {delimiter} at column {}",
self.index + 1
))
} else {
loop {
@ -114,7 +114,7 @@ impl Parser {
if let Some(c) = self.read() {
value.push(c);
} else {
return Err(format!("Invalid escape at index {}", self.index));
return Err(format!("Invalid escape at column {}", self.index + 1));
}
}
Some(' ') => return Ok(Some(value)),
@ -150,7 +150,7 @@ mod test {
fn test_split_error() {
assert_eq!(
args::split(r#"AAA 'BBB"#).err().unwrap(),
"Missing delimiter ' at index 8".to_string()
"Missing delimiter ' at column 9".to_string()
);
}
@ -196,7 +196,7 @@ mod test {
let mut parser = Parser::new("'value");
assert_eq!(
parser.param().err().unwrap(),
"Missing delimiter ' at index 6".to_string()
"Missing delimiter ' at column 7".to_string()
);
assert_eq!(parser.index, 6);
}

View File

@ -24,7 +24,7 @@ pub fn body(arg_matches: &ArgMatches) -> Option<String> {
if let Some(filename) = v.strip_prefix('@') {
Some(format!("file, {filename};"))
} else {
Some(format!("```{v}```"))
Some(format!("```\n{v}\n```"))
}
}
}

View File

@ -21,6 +21,22 @@ mod commands;
mod matches;
pub fn parse(s: &str) -> Result<String, String> {
let lines: Vec<&str> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(s)
.filter(|s| !s.is_empty())
.collect();
let mut s = "".to_string();
for (i, line) in lines.iter().enumerate() {
let hurl_str = parse_line(line).map_err(|message| {
format!("Can not parse curl command at line {}: {message}", i + 1)
})?;
s.push_str(format!("{hurl_str}\n").as_str())
}
Ok(s)
}
fn parse_line(s: &str) -> Result<String, String> {
let mut command = clap::Command::new("curl")
.arg(commands::compressed())
.arg(commands::data())
@ -64,7 +80,8 @@ fn format(
}
}
if let Some(body) = body {
s.push_str(format!("\n{body}").as_str());
s.push('\n');
s.push_str(body.as_str());
}
s.push('\n');
s
@ -72,13 +89,35 @@ fn format(
#[cfg(test)]
mod test {
use crate::curl::parse;
use crate::curl::*;
#[test]
fn test_parse() {
let hurl_str = r#"GET http://localhost:8000/hello
GET http://localhost:8000/custom-headers
Fruit:Raspberry
"#;
assert_eq!(
parse(
r#"curl http://localhost:8000/hello
curl http://localhost:8000/custom-headers -H 'Fruit:Raspberry'
"#
)
.unwrap(),
hurl_str
);
}
#[test]
fn test_hello() {
let hurl_str = r#"GET http://locahost:8000/hello
let hurl_str = r#"GET http://localhost:8000/hello
"#;
assert_eq!(parse("curl http://locahost:8000/hello").unwrap(), hurl_str);
assert_eq!(
parse_line("curl http://localhost:8000/hello").unwrap(),
hurl_str
);
}
#[test]
@ -89,11 +128,25 @@ Fruit: Banana
Test: '
"#;
assert_eq!(
parse("curl http://localhost:8000/custom-headers -H 'Fruit:Raspberry' -H 'Fruit: Banana' -H $'Test: \\''").unwrap(),
parse_line("curl http://localhost:8000/custom-headers -H 'Fruit:Raspberry' -H 'Fruit: Banana' -H $'Test: \\''").unwrap(),
hurl_str
);
assert_eq!(
parse("curl http://localhost:8000/custom-headers --header Fruit:Raspberry -H 'Fruit: Banana' -H $'Test: \\'' ").unwrap(),
parse_line("curl http://localhost:8000/custom-headers --header Fruit:Raspberry -H 'Fruit: Banana' -H $'Test: \\'' ").unwrap(),
hurl_str
);
}
#[test]
fn test_post_hello() {
let hurl_str = r#"POST http://localhost:8000/hello
Content-Type: text/plain
```
hello
```
"#;
assert_eq!(
parse_line(r#"curl -d $'hello' -H 'Content-Type: text/plain' -X POST http://localhost:8000/hello"#).unwrap(),
hurl_str
);
}
@ -102,14 +155,16 @@ Test: '
fn test_post_format_params() {
let hurl_str = r#"POST http://localhost:3000/data
Content-Type: application/x-www-form-urlencoded
```param1=value1&param2=value2```
```
param1=value1&param2=value2
```
"#;
assert_eq!(
parse("curl http://localhost:3000/data -d 'param1=value1&param2=value2'").unwrap(),
parse_line("curl http://localhost:3000/data -d 'param1=value1&param2=value2'").unwrap(),
hurl_str
);
assert_eq!(
parse("curl -X POST http://localhost:3000/data -H 'Content-Type: application/x-www-form-urlencoded' --data 'param1=value1&param2=value2'").unwrap(),
parse_line("curl -X POST http://localhost:3000/data -H 'Content-Type: application/x-www-form-urlencoded' --data 'param1=value1&param2=value2'").unwrap(),
hurl_str
);
}
@ -118,23 +173,26 @@ Content-Type: application/x-www-form-urlencoded
fn test_post_json() {
let hurl_str = r#"POST http://localhost:3000/data
Content-Type: application/json
```{"key1":"value1", "key2":"value2"}```
```
{"key1":"value1", "key2":"value2"}
```
"#;
assert_eq!(
parse(r#"curl -d '{"key1":"value1", "key2":"value2"}' -H 'Content-Type: application/json' -X POST http://localhost:3000/data"#).unwrap(),
hurl_str
hurl_str,
parse_line(r#"curl -d '{"key1":"value1", "key2":"value2"}' -H 'Content-Type: application/json' -X POST http://localhost:3000/data"#).unwrap()
);
let hurl_str = r#"POST http://localhost:3000/data
Content-Type: application/json
```{
```
{
"key1": "value1",
"key2": "value2"
}
```
"#;
assert_eq!(
parse(r#"curl -d $'{\n "key1": "value1",\n "key2": "value2"\n}\n' -H 'Content-Type: application/json' -X POST http://localhost:3000/data"#).unwrap(),
parse_line(r#"curl -d $'{\n "key1": "value1",\n "key2": "value2"\n}' -H 'Content-Type: application/json' -X POST http://localhost:3000/data"#).unwrap(),
hurl_str
);
}
@ -145,7 +203,7 @@ Content-Type: application/json
file, filename;
"#;
assert_eq!(
parse(r#"curl --data @filename http://example.com/"#).unwrap(),
parse_line(r#"curl --data @filename http://example.com/"#).unwrap(),
hurl_str
);
}
@ -157,7 +215,7 @@ file, filename;
location: true
"#;
assert_eq!(
parse(r#"curl -L http://localhost:8000/redirect-absolute"#).unwrap(),
parse_line(r#"curl -L http://localhost:8000/redirect-absolute"#).unwrap(),
hurl_str
);
}
@ -169,7 +227,7 @@ location: true
insecure: true
"#;
assert_eq!(
parse(r#"curl -k https://localhost:8001/hello"#).unwrap(),
parse_line(r#"curl -k https://localhost:8001/hello"#).unwrap(),
hurl_str
);
}
@ -181,7 +239,7 @@ insecure: true
max-redirs: 10
"#;
assert_eq!(
parse(r#"curl https://localhost:8001/hello --max-redirs 10"#).unwrap(),
parse_line(r#"curl https://localhost:8001/hello --max-redirs 10"#).unwrap(),
hurl_str
);
}

View File

@ -20,8 +20,8 @@ use std::path::PathBuf;
use std::process;
use hurl_core::parser;
use hurlfmt::cli::options::{OptionsError, OutputFormat};
use hurlfmt::{cli, format, linter};
use hurlfmt::cli::options::{InputFormat, OptionsError, OutputFormat};
use hurlfmt::{cli, curl, format, linter};
#[cfg(target_family = "unix")]
pub fn init_colored() {
@ -80,17 +80,28 @@ fn main() {
contents
};
let input = match opts.input_format {
InputFormat::Hurl => contents,
InputFormat::Curl => match curl::parse(&contents) {
Ok(s) => s,
Err(e) => {
eprintln!("{}", e);
process::exit(2);
}
},
};
let lines: Vec<&str> = regex::Regex::new(r"\n|\r\n")
.unwrap()
.split(&contents)
.split(&input)
.collect();
let lines: Vec<String> = lines.iter().map(|s| (*s).to_string()).collect();
let log_parser_error =
cli::make_logger_parser_error(lines.clone(), opts.color, opts.input_file.clone());
let log_linter_error =
cli::make_logger_linter_error(lines, opts.color, opts.input_file.clone());
match parser::parse_hurl_file(&contents) {
match parser::parse_hurl_file(&input) {
Err(e) => {
log_parser_error(&e, false);
process::exit(2);