Skip to content

Commit

Permalink
Merge nested operator
Browse files Browse the repository at this point in the history
  • Loading branch information
SummerGram committed Feb 24, 2024
2 parents 6971058 + 6814b06 commit 5159dca
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 9 deletions.
6 changes: 6 additions & 0 deletions src/app/services/request/entities/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ impl From<String> for BodyPayload {
}
}

impl From<&str> for BodyPayload {
fn from(value: &str) -> Self {
BodyPayload::from_str(value)
}
}

#[derive(Default)]
pub struct RequestEntity {
current_request: Box<NodeHistoryRequest>,
Expand Down
7 changes: 7 additions & 0 deletions src/utils/regexes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ pub mod request_items {
BODY_VALUE_REGEX.get_or_init(|| Regex::new(r"^(?<key>[ -~]+)=(?<value>[ -~]+)$").unwrap())
}

static NESTED_BODY_KEYS_REGEX: OnceLock<Regex> = OnceLock::new();
pub fn nested_body_keys() -> &'static Regex {
NESTED_BODY_KEYS_REGEX.get_or_init(|| {
Regex::new(r"^(?<root_key>[^\[\]]+)(?<sub_keys>(\[([^\[\]]+)\])+)$").unwrap()
})
}

static HEADER_VALUE_REGEX: OnceLock<Regex> = OnceLock::new();
pub fn header_value() -> &'static Regex {
HEADER_VALUE_REGEX.get_or_init(|| Regex::new(r"^(?<key>[ -~]+):(?<value>[ -~]+)$").unwrap())
Expand Down
166 changes: 157 additions & 9 deletions src/view/input_parsers/request_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub fn parse_inputs_to_request_data(input: &CliInput) -> Result<PartialRequestDa
.fold(base_request, |req_data, item| {
[
parsers_request_items::query_param_value,
parsers_request_items::nested_body_value,
parsers_request_items::non_string_body_value,
parsers_request_items::body_value,
parsers_request_items::header_value,
Expand All @@ -59,6 +60,8 @@ pub fn parse_inputs_to_request_data(input: &CliInput) -> Result<PartialRequestDa
}

mod parsers_request_items {
use serde_json::Map;

use super::*;
use crate::utils::regexes;

Expand Down Expand Up @@ -188,6 +191,74 @@ mod parsers_request_items {

Some(request)
}

pub fn nested_body_value(
s: &str,
base_request: &PartialRequestData,
) -> Option<PartialRequestData> {
let re = regexes::request_items::body_value();
let matcher = re.captures(s)?;

// Key=Value
let key = matcher.name("key")?.as_str();
let value = matcher.name("value")?.as_str();

// Extract the root key and sub keys from "key" input
// ----
// Key[SubKey1][SubKey2] => Key, [SubKey1, SubKey2]
// ----
let (root_key, sub_keys) = {
let re = regexes::request_items::nested_body_keys();
let matcher = re.captures(key)?;

(
matcher.name("root_key")?.as_str(),
matcher
.name("sub_keys")?
.as_str()
.split(['[', ']'])
.filter(|s| !s.is_empty())
.collect::<Vec<_>>(),
)
};

let mut request = base_request.clone();

let mut root_map: Map<String, Value> = match request.body {
Some(BodyPayload::Json(serde_json::Value::Object(v))) => v,
_ => Map::new(),
};

let root_value: &mut Value = root_map
.entry(root_key)
.or_insert(Value::Object(Map::new()));

let target_value: &mut Value =
sub_keys
.iter()
.fold(root_value, |map_value, key| match map_value {
Value::Object(map) => map
.entry(key.to_string())
.or_insert(Value::Object(Map::new())),
_ => {
*map_value = Value::Object(Map::new());
match map_value {
Value::Object(map) => map
.entry(key.to_string())
.or_insert(Value::Object(Map::new())),
_ => unreachable!(),
}
}
});

*target_value = serde_json::from_str(value)
.ok()
.unwrap_or(Value::String(value.to_string()));

request.body = BodyPayload::Json(serde_json::Value::Object(root_map)).into();

Some(request)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -358,11 +429,11 @@ pub mod tests_parsers_request_items {
#[test]
fn test_body_value_append() {
let input = "password=123";
let base_request = PartialRequestData::default()
.with_body(r#"{ "email": "[email protected]" }"#.to_string());
let base_request =
PartialRequestData::default().with_body(r#"{ "email": "[email protected]" }"#);

let expected_request = PartialRequestData::default()
.with_body(r#"{ "email": "[email protected]", "password": "123" }"#.to_string());
.with_body(r#"{ "email": "[email protected]", "password": "123" }"#);

assert_eq!(
Some(expected_request),
Expand All @@ -374,10 +445,10 @@ pub mod tests_parsers_request_items {
fn test_body_value_append_overwrite() {
let input = "password=123456";
let base_request = PartialRequestData::default()
.with_body(r#"{ "email": "[email protected]", "password": "123" }"#.to_string());
.with_body(r#"{ "email": "[email protected]", "password": "123" }"#);

let expected_request = PartialRequestData::default()
.with_body(r#"{ "email": "[email protected]", "password": "123456" }"#.to_string());
.with_body(r#"{ "email": "[email protected]", "password": "123456" }"#);

assert_eq!(
Some(expected_request),
Expand All @@ -395,24 +466,101 @@ pub mod tests_parsers_request_items {

for request in base_requests {
let expected_request =
PartialRequestData::default().with_body(r#"{ "password": "123" }"#.to_string());
PartialRequestData::default().with_body(r#"{ "password": "123" }"#);
assert_eq!(
Some(expected_request),
parsers_request_items::body_value(input, &request)
)
}
}
#[test]

#[test]
fn test_body_value_with_base_body_none() {
let input = "password=123";
let base_request = PartialRequestData::default();

let expected_request =
PartialRequestData::default().with_body(r#"{ "password": "123" }"#.to_string());
let expected_request = PartialRequestData::default().with_body(r#"{ "password": "123" }"#);
assert_eq!(
Some(expected_request),
parsers_request_items::body_value(input, &base_request)
)
}

#[test]
fn test_nested_body_value_basic() {
let cases = [
("user[name]=John", r#"{ "user": { "name": "John"} }"#),
(
"user[name][first]=John",
r#"{ "user": { "name": { "first": "John"} } }"#,
),
];

for (input, expected_json_output) in cases {
let base_request = PartialRequestData::default();
let expected_request = PartialRequestData::default().with_body(expected_json_output);
assert_eq!(
Some(expected_request),
parsers_request_items::nested_body_value(input, &base_request)
)
}
}

#[test]
fn test_nested_body_value_overwriting_fields() {
// 1. appending in a nested object
let input = "user[name]=John";
let base_json = r#"{ "user": {} }"#;
let expected_output_json = r#"{ "user": { "name": "John" } }"#;
let base_request = PartialRequestData::default().with_body(base_json);
let expected_request = PartialRequestData::default().with_body(expected_output_json);
assert_eq!(
Some(expected_request),
parsers_request_items::nested_body_value(input, &base_request)
);

// 2. overwriting existing field
let input = "user[name]=Mary";
let base_json = r#"{ "user": { "name": "John" } }"#;
let expected_output_json = r#"{ "user": { "name": "Mary" } }"#;
let base_request = PartialRequestData::default().with_body(base_json);
let expected_request = PartialRequestData::default().with_body(expected_output_json);
assert_eq!(
Some(expected_request),
parsers_request_items::nested_body_value(input, &base_request)
);

// 3. appending in a nested object with another fields
let input = "user[name][first]=John";
let base_json = r#"{ "user": { "name": { "last": "Doe"} } }"#;
let expected_output_json = r#"{ "user": { "name": { "first": "John", "last": "Doe"} } }"#;
let base_request = PartialRequestData::default().with_body(base_json);
let expected_request = PartialRequestData::default().with_body(expected_output_json);
assert_eq!(
Some(expected_request),
parsers_request_items::nested_body_value(input, &base_request)
);

// 4. replacing fields to object
let input = "user[name]=Mary";
let base_json = r#"{ "user": "a-string-to-be-replaced" }"#;
let expected_output_json = r#"{ "user": { "name": "Mary" } }"#;
let base_request = PartialRequestData::default().with_body(base_json);
let expected_request = PartialRequestData::default().with_body(expected_output_json);
assert_eq!(
Some(expected_request),
parsers_request_items::nested_body_value(input, &base_request)
);

// 5. replacing fields to nested object
let input = "user[name][first]=John";
let base_json = r#"{ "user": "a-string-to-be-replaced" }"#;
let expected_output_json = r#"{ "user": { "name": { "first": "John" } } }"#;
let base_request = PartialRequestData::default().with_body(base_json);
let expected_request = PartialRequestData::default().with_body(expected_output_json);
assert_eq!(
Some(expected_request),
parsers_request_items::nested_body_value(input, &base_request)
);
}
}
25 changes: 25 additions & 0 deletions tests/e2e/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,31 @@ fn should_assert_process_return_with_no_saved_requests_call() {
cmd.assert().success();
}

#[test]
fn should_merge_nested_body_values_with_saved() {
let input = format!(
"treq POST {}/post id=1 user[name]=John user[age]=30 user[address][city]=Tokio --save-as create-user",
host()
);
let mut cmd = run_cmd(&input);
cmd.assert().success();

let input = "treq run create-user Hello=World";
let mut cmd = run_cmd(&input);
cmd.assert().success();
cmd.assert().stdout(
predicate::str::contains("Hello")
.and(predicate::str::contains("World"))
// previues saved nested values
.and(predicate::str::contains("user"))
.and(predicate::str::contains("address"))
.and(predicate::str::contains("city"))
.and(predicate::str::contains("Tokio"))
.and(predicate::str::contains("age"))
.and(predicate::str::contains("30")),
);
}

#[test]
fn should_assert_list_saved_requests() {
let requests_to_save = ["simple-get", "some-put", "a-great-post"];
Expand Down
18 changes: 18 additions & 0 deletions tests/view/map_input_to_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,24 @@ fn should_raw_flag_work_equal_param_body_definition() {
assert_eq!(output1.unwrap(), output2.unwrap());
}

#[test]
fn should_parse_body_values_with_nested_json_too() {
let input = [
"treq",
"POST",
"url.com",
"id=1",
"user[name]=John",
"user[age]=30",
"user[address][city]=NY",
"type=user",
];
let output = process(input);

debug_assert!(output.is_ok(), "{:?}", output);
assert_snapshot!(output.unwrap());
}

#[test]
fn should_merge_inputs_of_raw_flag_and_param_body() {
let input = [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
source: tests/view/map_input_to_commands.rs
expression: output.unwrap()
---
- SubmitRequest:
request:
url:
ValidatedUrl:
protocol: ~
host: url.com
port: ~
paths: []
query_params: []
anchor: ~
method: POST
headers: {}
body:
Json:
id: "1"
type: user
user:
address:
city: NY
age: 30
name: John

0 comments on commit 5159dca

Please sign in to comment.