use serde_json::{Value, json};
use std::collections::HashMap;
use sutures::stitch::Stitch;
fn parse_first(json: &str) -> sutures::v1::Suture {
sutures::v1::parse(json)
.unwrap()
.into_iter()
.next()
.unwrap()
.unwrap()
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct DynamicFields {
data: HashMap<String, i32>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct FlatDynamic {
fields: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct MixedStruct {
name: String,
data: HashMap<String, i32>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct TwoFamilies {
alpha: HashMap<String, i32>,
beta: HashMap<String, i32>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct NestedDynamic {
entries: HashMap<String, Entry>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct Entry {
name: String,
value: i32,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct TopLevelMap {
items: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct LongKeyStruct {
store: HashMap<String, i32>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct SingleCharKeys {
bucket: HashMap<String, i32>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct NumericKeys {
index: HashMap<String, String>,
}
#[test]
fn basic_pattern_field_digits() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "basic_pattern",
"capture": "request",
"sutures": [{
"data.`field_\\d+`": "/values/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("field_0".into(), 10),
("field_1".into(), 20),
("field_2".into(), 30),
]),
};
let result = suture.stitch(&input).unwrap();
let values = result["values"].as_array().unwrap();
assert_eq!(values.len(), 3);
let mut sorted: Vec<i64> = values.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![10, 20, 30]);
}
#[test]
fn basic_pattern_item_alpha() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "alpha_pattern",
"capture": "request",
"sutures": [{
"items.`item_[a-z]+`": "/items/[:]"
}]
}]
}"#,
);
let input = TopLevelMap {
items: HashMap::from([
("item_foo".into(), "hello".into()),
("item_bar".into(), "world".into()),
]),
};
let result = suture.stitch(&input).unwrap();
let items = result["items"].as_array().unwrap();
assert_eq!(items.len(), 2);
let mut sorted: Vec<&str> = items.iter().map(|v| v.as_str().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec!["hello", "world"]);
}
#[test]
fn pattern_no_matches_empty_output() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "no_match",
"capture": "request",
"sutures": [{
"data.`content_\\d+`": "/output/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([("other_0".into(), 1), ("something_else".into(), 2)]),
};
let result = suture.stitch(&input).unwrap();
assert!(
result.get("output").is_none()
|| result["output"].as_array().map_or(true, |a| a.is_empty())
);
}
#[test]
fn pattern_partial_match_ignores_non_matching() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "partial",
"capture": "request",
"sutures": [{
"data.`data_\\d+`": "/extracted/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("data_0".into(), 100),
("data_1".into(), 200),
("other_key".into(), 999),
("not_data".into(), 888),
]),
};
let result = suture.stitch(&input).unwrap();
let extracted = result["extracted"].as_array().unwrap();
assert_eq!(extracted.len(), 2);
let mut sorted: Vec<i64> = extracted.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![100, 200]);
}
#[test]
fn pattern_non_capturing_group() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "noncap",
"capture": "request",
"sutures": [{
"data.`(?:input|output)_\\d+`": "/signals/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("input_0".into(), 1),
("output_1".into(), 2),
("random_key".into(), 3),
]),
};
let result = suture.stitch(&input).unwrap();
let signals = result["signals"].as_array().unwrap();
assert_eq!(signals.len(), 2);
let mut sorted: Vec<i64> = signals.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![1, 2]);
}
#[test]
fn pattern_slice_first_only() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "first_only",
"capture": "request",
"sutures": [{
"data.`field_\\d+`[0]": "/first/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("field_0".into(), 10),
("field_1".into(), 20),
("field_2".into(), 30),
]),
};
let result = suture.stitch(&input).unwrap();
let first = result["first"].as_array().unwrap();
assert_eq!(first.len(), 1);
let val = first[0].as_i64().unwrap();
assert!([10, 20, 30].contains(&val));
}
#[test]
fn pattern_slice_first_two() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "first_two",
"capture": "request",
"sutures": [{
"data.`field_\\d+`[0:2]": "/two/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("field_0".into(), 10),
("field_1".into(), 20),
("field_2".into(), 30),
("field_3".into(), 40),
]),
};
let result = suture.stitch(&input).unwrap();
let two = result["two"].as_array().unwrap();
assert_eq!(two.len(), 2);
for v in two {
let n = v.as_i64().unwrap();
assert!([10, 20, 30, 40].contains(&n));
}
}
#[test]
fn pattern_slice_skip_first() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "skip_first",
"capture": "request",
"sutures": [{
"data.`field_\\d+`[1:]": "/rest/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("field_0".into(), 10),
("field_1".into(), 20),
("field_2".into(), 30),
]),
};
let result = suture.stitch(&input).unwrap();
let rest = result["rest"].as_array().unwrap();
assert_eq!(rest.len(), 2);
for v in rest {
let n = v.as_i64().unwrap();
assert!([10, 20, 30].contains(&n));
}
}
#[test]
fn pattern_slice_reverse() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "reverse",
"capture": "request",
"sutures": [{
"data.`field_\\d+`[::-1]": "/reversed/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("field_0".into(), 10),
("field_1".into(), 20),
("field_2".into(), 30),
]),
};
let result = suture.stitch(&input).unwrap();
let reversed = result["reversed"].as_array().unwrap();
assert_eq!(reversed.len(), 3);
let vals: Vec<i64> = reversed.iter().map(|v| v.as_i64().unwrap()).collect();
let mut sorted = vals.clone();
sorted.sort();
assert_eq!(sorted, vec![10, 20, 30]);
}
#[test]
fn pattern_child_extraction() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "child_extract",
"capture": "request",
"sutures": [{
"entries.`entry_\\d+`": {
"name": "/entries/[:]/name",
"value": "/entries/[:]/value"
}
}]
}]
}"#,
);
let input = NestedDynamic {
entries: HashMap::from([
(
"entry_0".into(),
Entry {
name: "alpha".into(),
value: 10,
},
),
(
"entry_1".into(),
Entry {
name: "beta".into(),
value: 20,
},
),
]),
};
let result = suture.stitch(&input).unwrap();
let entries = result["entries"].as_array().unwrap();
assert_eq!(entries.len(), 2);
let mut names: Vec<&str> = entries
.iter()
.map(|e| e["name"].as_str().unwrap())
.collect();
names.sort();
assert_eq!(names, vec!["alpha", "beta"]);
let mut values: Vec<i64> = entries
.iter()
.map(|e| e["value"].as_i64().unwrap())
.collect();
values.sort();
assert_eq!(values, vec![10, 20]);
}
#[test]
fn pattern_fan_out() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "fanout",
"capture": "request",
"sutures": [{
"data.`field_\\d+`": ["/primary/[:]", "/backup/[:]"]
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([("field_0".into(), 10), ("field_1".into(), 20)]),
};
let result = suture.stitch(&input).unwrap();
let primary = result["primary"].as_array().unwrap();
let backup = result["backup"].as_array().unwrap();
assert_eq!(primary.len(), 2);
assert_eq!(backup.len(), 2);
let mut primary_sorted: Vec<i64> = primary.iter().map(|v| v.as_i64().unwrap()).collect();
let mut backup_sorted: Vec<i64> = backup.iter().map(|v| v.as_i64().unwrap()).collect();
primary_sorted.sort();
backup_sorted.sort();
assert_eq!(primary_sorted, vec![10, 20]);
assert_eq!(backup_sorted, vec![10, 20]);
}
#[test]
fn multiple_patterns_two_families() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "multi_pattern",
"capture": "request",
"sutures": [{
"alpha.`a_\\d+`": "/group_a/[:]",
"beta.`b_\\d+`": "/group_b/[:]"
}]
}]
}"#,
);
let input = TwoFamilies {
alpha: HashMap::from([("a_0".into(), 1), ("a_1".into(), 2)]),
beta: HashMap::from([("b_0".into(), 100), ("b_1".into(), 200)]),
};
let result = suture.stitch(&input).unwrap();
let group_a = result["group_a"].as_array().unwrap();
let group_b = result["group_b"].as_array().unwrap();
assert_eq!(group_a.len(), 2);
assert_eq!(group_b.len(), 2);
let mut a_sorted: Vec<i64> = group_a.iter().map(|v| v.as_i64().unwrap()).collect();
a_sorted.sort();
assert_eq!(a_sorted, vec![1, 2]);
let mut b_sorted: Vec<i64> = group_b.iter().map(|v| v.as_i64().unwrap()).collect();
b_sorted.sort();
assert_eq!(b_sorted, vec![100, 200]);
}
#[test]
fn pattern_matches_all_keys() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "match_all",
"capture": "request",
"sutures": [{
"data.`.*`": "/all/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("anything".into(), 1),
("everything".into(), 2),
("123".into(), 3),
]),
};
let result = suture.stitch(&input).unwrap();
let all = result["all"].as_array().unwrap();
assert_eq!(all.len(), 3);
let mut sorted: Vec<i64> = all.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![1, 2, 3]);
}
#[test]
fn pattern_auto_anchored() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "anchored",
"capture": "request",
"sutures": [{
"data.`field`": "/exact/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("field".into(), 42),
("field_extra".into(), 99),
("prefix_field".into(), 88),
]),
};
let result = suture.stitch(&input).unwrap();
let exact = result["exact"].as_array().unwrap();
assert_eq!(exact.len(), 1);
assert_eq!(exact[0].as_i64().unwrap(), 42);
}
#[test]
fn pattern_escaped_dot() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "escaped_dot",
"capture": "request",
"sutures": [{
"fields.`field\\.name`": "/dotted/[:]"
}]
}]
}"#,
);
let input = FlatDynamic {
fields: HashMap::from([
("field.name".into(), "matched".into()),
("fieldXname".into(), "should_not_match".into()),
]),
};
let result = suture.stitch(&input).unwrap();
let dotted = result["dotted"].as_array().unwrap();
assert_eq!(dotted.len(), 1);
assert_eq!(dotted[0].as_str().unwrap(), "matched");
}
#[test]
fn pattern_alternation() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "alternation",
"capture": "request",
"sutures": [{
"data.`(?:alpha|beta)_\\d+`": "/matched/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("alpha_0".into(), 1),
("beta_1".into(), 2),
("gamma_2".into(), 3),
]),
};
let result = suture.stitch(&input).unwrap();
let matched = result["matched"].as_array().unwrap();
assert_eq!(matched.len(), 2);
let mut sorted: Vec<i64> = matched.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![1, 2]);
}
#[test]
fn pattern_long_key_names() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "long_keys",
"capture": "request",
"sutures": [{
"store.`field_[a-z_]+`": "/long/[:]"
}]
}]
}"#,
);
let long_key_a = "field_".to_string() + &"a".repeat(100);
let long_key_b = "field_".to_string() + &"b".repeat(100);
let input = LongKeyStruct {
store: HashMap::from([(long_key_a, 1), (long_key_b, 2)]),
};
let result = suture.stitch(&input).unwrap();
let long = result["long"].as_array().unwrap();
assert_eq!(long.len(), 2);
let mut sorted: Vec<i64> = long.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![1, 2]);
}
#[test]
fn pattern_single_char_keys() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "single_char",
"capture": "request",
"sutures": [{
"bucket.`[a-z]`": "/chars/[:]"
}]
}]
}"#,
);
let input = SingleCharKeys {
bucket: HashMap::from([
("a".into(), 1),
("z".into(), 2),
("AB".into(), 3), ("1".into(), 4), ]),
};
let result = suture.stitch(&input).unwrap();
let chars = result["chars"].as_array().unwrap();
assert_eq!(chars.len(), 2);
let mut sorted: Vec<i64> = chars.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![1, 2]);
}
#[test]
fn pattern_numeric_only_keys() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "numeric",
"capture": "request",
"sutures": [{
"index.`\\d+`": "/numbers/[:]"
}]
}]
}"#,
);
let input = NumericKeys {
index: HashMap::from([
("0".into(), "zero".into()),
("1".into(), "one".into()),
("42".into(), "forty_two".into()),
("abc".into(), "not_a_number".into()),
]),
};
let result = suture.stitch(&input).unwrap();
let numbers = result["numbers"].as_array().unwrap();
assert_eq!(numbers.len(), 3);
let mut sorted: Vec<&str> = numbers.iter().map(|v| v.as_str().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec!["forty_two", "one", "zero"]);
}
#[test]
fn mixed_direct_and_pattern() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "mixed",
"capture": "request",
"sutures": [{
"name": "/name",
"data.`data_\\d+`": "/data/[:]"
}]
}]
}"#,
);
let input = MixedStruct {
name: "my_name".into(),
data: HashMap::from([("data_0".into(), 10), ("data_1".into(), 20)]),
};
let result = suture.stitch(&input).unwrap();
assert_eq!(result["name"].as_str().unwrap(), "my_name");
let data = result["data"].as_array().unwrap();
assert_eq!(data.len(), 2);
let mut sorted: Vec<i64> = data.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![10, 20]);
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct NonObjectField {
data: Vec<i32>,
}
#[test]
fn pattern_on_non_object_source_skips() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "non_obj",
"capture": "request",
"sutures": [{
"data.`field_\\d+`": "/output/[:]"
}]
}]
}"#,
);
let input = NonObjectField {
data: vec![1, 2, 3],
};
let result = suture.stitch(&input).unwrap();
assert!(
result.get("output").is_none()
|| result["output"].as_array().map_or(true, |a| a.is_empty())
);
}
#[test]
fn pattern_empty_hashmap() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "empty_map",
"capture": "request",
"sutures": [{
"data.`field_\\d+`": "/output/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::new(),
};
let result = suture.stitch(&input).unwrap();
assert!(
result.get("output").is_none()
|| result["output"].as_array().map_or(true, |a| a.is_empty())
);
}
#[test]
fn pattern_single_matching_key() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "single_key",
"capture": "request",
"sutures": [{
"data.`field_\\d+`": "/output/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([("field_7".into(), 77)]),
};
let result = suture.stitch(&input).unwrap();
let output = result["output"].as_array().unwrap();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_i64().unwrap(), 77);
}
#[test]
fn pattern_fanout_nested() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "fanout_nested",
"capture": "request",
"sutures": [{
"entries.`entry_\\d+`": {
"name": ["/primary/[:]/name", "/mirror/[:]/name"]
}
}]
}]
}"#,
);
let input = NestedDynamic {
entries: HashMap::from([
(
"entry_0".into(),
Entry {
name: "x".into(),
value: 1,
},
),
(
"entry_1".into(),
Entry {
name: "y".into(),
value: 2,
},
),
]),
};
let result = suture.stitch(&input).unwrap();
let primary = result["primary"].as_array().unwrap();
let mirror = result["mirror"].as_array().unwrap();
assert_eq!(primary.len(), 2);
assert_eq!(mirror.len(), 2);
let mut primary_names: Vec<&str> = primary
.iter()
.map(|e| e["name"].as_str().unwrap())
.collect();
primary_names.sort();
assert_eq!(primary_names, vec!["x", "y"]);
let mut mirror_names: Vec<&str> = mirror.iter().map(|e| e["name"].as_str().unwrap()).collect();
mirror_names.sort();
assert_eq!(mirror_names, vec!["x", "y"]);
}
#[test]
fn pattern_slice_fewer_matches_than_range() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "fewer_matches",
"capture": "request",
"sutures": [{
"data.`field_\\d+`[0:5]": "/output/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([("field_0".into(), 10), ("field_1".into(), 20)]),
};
let result = suture.stitch(&input).unwrap();
let output = result["output"].as_array().unwrap();
assert_eq!(output.len(), 2);
}
#[test]
fn multiple_suture_objects_with_patterns() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "multi_obj",
"capture": "request",
"sutures": [
{ "alpha.`a_\\d+`": "/a_vals/[:]" },
{ "beta.`b_\\d+`": "/b_vals/[:]" }
]
}]
}"#,
);
let input = TwoFamilies {
alpha: HashMap::from([("a_0".into(), 10)]),
beta: HashMap::from([("b_0".into(), 20)]),
};
let result = suture.stitch(&input).unwrap();
let a_vals = result["a_vals"].as_array().unwrap();
assert_eq!(a_vals.len(), 1);
assert_eq!(a_vals[0].as_i64().unwrap(), 10);
let b_vals = result["b_vals"].as_array().unwrap();
assert_eq!(b_vals.len(), 1);
assert_eq!(b_vals[0].as_i64().unwrap(), 20);
}
#[test]
fn pattern_uppercase_start() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "upper_start",
"capture": "request",
"sutures": [{
"fields.`[A-Z][a-z]+`": "/capitalized/[:]"
}]
}]
}"#,
);
let input = FlatDynamic {
fields: HashMap::from([
("Hello".into(), "matched1".into()),
("World".into(), "matched2".into()),
("hello".into(), "not_matched".into()),
("H".into(), "too_short".into()),
]),
};
let result = suture.stitch(&input).unwrap();
let capitalized = result["capitalized"].as_array().unwrap();
assert_eq!(capitalized.len(), 2);
let mut sorted: Vec<&str> = capitalized.iter().map(|v| v.as_str().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec!["matched1", "matched2"]);
}
#[test]
fn pattern_quantifier_plus() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "quantifier",
"capture": "request",
"sutures": [{
"bucket.`x+`": "/xs/[:]"
}]
}]
}"#,
);
let input = SingleCharKeys {
bucket: HashMap::from([
("x".into(), 1),
("xx".into(), 2),
("xxx".into(), 3),
("y".into(), 4),
("xy".into(), 5),
]),
};
let result = suture.stitch(&input).unwrap();
let xs = result["xs"].as_array().unwrap();
assert_eq!(xs.len(), 3);
let mut sorted: Vec<i64> = xs.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![1, 2, 3]);
}
#[test]
fn suture_is_request_direction() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "direction_check",
"capture": "request",
"sutures": [{
"data.`field_\\d+`": "/output/[:]"
}]
}]
}"#,
);
assert!(suture.is_request());
assert!(!suture.is_response());
}
#[test]
fn mixed_direct_and_pattern_nested_suture() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "nested_mixed",
"capture": "request",
"sutures": [
{ "name": "/metadata/name" },
{ "data.`item_\\d+`": "/items/[:]" }
]
}]
}"#,
);
let input = MixedStruct {
name: "test_name".into(),
data: HashMap::from([
("item_0".into(), 100),
("item_1".into(), 200),
("other".into(), 999),
]),
};
let result = suture.stitch(&input).unwrap();
assert_eq!(result["metadata"]["name"].as_str().unwrap(), "test_name");
let items = result["items"].as_array().unwrap();
assert_eq!(items.len(), 2);
let mut sorted: Vec<i64> = items.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![100, 200]);
}
#[test]
fn pattern_with_constants() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "with_constants",
"capture": "request",
"sutures": [{
"data.`field_\\d+`": "/output/[:]",
"_": [{ "/type": "dynamic" }]
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([("field_0".into(), 10)]),
};
let result = suture.stitch(&input).unwrap();
let output = result["output"].as_array().unwrap();
assert_eq!(output.len(), 1);
assert_eq!(output[0].as_i64().unwrap(), 10);
assert_eq!(result["type"].as_str().unwrap(), "dynamic");
}
#[test]
fn pattern_deep_output_path() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "deep_output",
"capture": "request",
"sutures": [{
"data.`field_\\d+`": "/deep/nested/values/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([("field_0".into(), 42)]),
};
let result = suture.stitch(&input).unwrap();
let val = &result["deep"]["nested"]["values"];
let arr = val.as_array().unwrap();
assert_eq!(arr.len(), 1);
assert_eq!(arr[0].as_i64().unwrap(), 42);
}
#[test]
fn pattern_many_matches() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "many_matches",
"capture": "request",
"sutures": [{
"data.`item_\\d+`": "/items/[:]"
}]
}]
}"#,
);
let mut map = HashMap::new();
for i in 0..50 {
map.insert(format!("item_{}", i), i as i32);
}
let input = DynamicFields { data: map };
let result = suture.stitch(&input).unwrap();
let items = result["items"].as_array().unwrap();
assert_eq!(items.len(), 50);
let mut sorted: Vec<i64> = items.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
let expected: Vec<i64> = (0..50).collect();
assert_eq!(sorted, expected);
}
#[test]
fn pattern_optional_quantifier() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "optional_q",
"capture": "request",
"sutures": [{
"data.`field_\\d?`": "/output/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("field_".into(), 1), ("field_5".into(), 2), ("field_55".into(), 3), ]),
};
let result = suture.stitch(&input).unwrap();
let output = result["output"].as_array().unwrap();
assert_eq!(output.len(), 2);
let mut sorted: Vec<i64> = output.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![1, 2]);
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct BoolMap {
flags: HashMap<String, bool>,
}
#[test]
fn pattern_bool_values() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "bool_vals",
"capture": "request",
"sutures": [{
"flags.`flag_\\d+`": "/flags/[:]"
}]
}]
}"#,
);
let input = BoolMap {
flags: HashMap::from([("flag_0".into(), true), ("flag_1".into(), false)]),
};
let result = suture.stitch(&input).unwrap();
let flags = result["flags"].as_array().unwrap();
assert_eq!(flags.len(), 2);
let mut sorted: Vec<bool> = flags.iter().map(|v| v.as_bool().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![false, true]);
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct ValueMap {
data: HashMap<String, Value>,
}
#[test]
fn pattern_mixed_value_types() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "mixed_vals",
"capture": "request",
"sutures": [{
"data.`field_\\d+`": "/output/[:]"
}]
}]
}"#,
);
let input = ValueMap {
data: HashMap::from([
("field_0".into(), json!(42)),
("field_1".into(), json!("hello")),
("field_2".into(), json!(true)),
]),
};
let result = suture.stitch(&input).unwrap();
let output = result["output"].as_array().unwrap();
assert_eq!(output.len(), 3);
let has_int = output.iter().any(|v| v.as_i64().is_some());
let has_str = output.iter().any(|v| v.as_str().is_some());
let has_bool = output.iter().any(|v| v.as_bool().is_some());
assert!(has_int);
assert!(has_str);
assert!(has_bool);
}
#[test]
fn two_patterns_same_parent() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "two_on_same",
"capture": "request",
"sutures": [{
"data.`input_\\d+`": "/inputs/[:]",
"data.`output_\\d+`": "/outputs/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("input_0".into(), 1),
("input_1".into(), 2),
("output_0".into(), 10),
("output_1".into(), 20),
("other".into(), 99),
]),
};
let result = suture.stitch(&input).unwrap();
let inputs = result["inputs"].as_array().unwrap();
assert_eq!(inputs.len(), 2);
let mut in_sorted: Vec<i64> = inputs.iter().map(|v| v.as_i64().unwrap()).collect();
in_sorted.sort();
assert_eq!(in_sorted, vec![1, 2]);
let outputs = result["outputs"].as_array().unwrap();
assert_eq!(outputs.len(), 2);
let mut out_sorted: Vec<i64> = outputs.iter().map(|v| v.as_i64().unwrap()).collect();
out_sorted.sort();
assert_eq!(out_sorted, vec![10, 20]);
}
#[test]
fn pattern_complex_key_format() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "complex_keys",
"capture": "request",
"sutures": [{
"data.`[a-z]+_\\d+_[a-z]+`": "/complex/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([
("field_0_alpha".into(), 1),
("item_99_beta".into(), 2),
("nope".into(), 3),
("bad_format".into(), 4),
]),
};
let result = suture.stitch(&input).unwrap();
let complex = result["complex"].as_array().unwrap();
assert_eq!(complex.len(), 2);
let mut sorted: Vec<i64> = complex.iter().map(|v| v.as_i64().unwrap()).collect();
sorted.sort();
assert_eq!(sorted, vec![1, 2]);
}
#[test]
fn pattern_slice_zero_single_match() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "single_slice",
"capture": "request",
"sutures": [{
"data.`unique_\\d+`[0]": "/first/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([("unique_42".into(), 999), ("other_key".into(), 0)]),
};
let result = suture.stitch(&input).unwrap();
let first = result["first"].as_array().unwrap();
assert_eq!(first.len(), 1);
assert_eq!(first[0].as_i64().unwrap(), 999);
}
#[test]
fn pattern_slice_zero_no_matches() {
let suture = parse_first(
r#"{
"name": "test",
"suture_sets": [{
"name": "no_match_slice",
"capture": "request",
"sutures": [{
"data.`nonexistent_\\d+`[0]": "/output/[:]"
}]
}]
}"#,
);
let input = DynamicFields {
data: HashMap::from([("other_0".into(), 1)]),
};
let result = suture.stitch(&input).unwrap();
assert!(
result.get("output").is_none()
|| result["output"].as_array().map_or(true, |a| a.is_empty())
);
}