use serde_json::json;
use sutures::Stitch;
fn parse_first(json: &str) -> sutures::v1::Suture {
sutures::v1::parse(json)
.unwrap()
.into_iter()
.next()
.unwrap()
.unwrap()
}
fn response_suture(mappings: serde_json::Value) -> sutures::v1::Suture {
let manifest = json!({
"name": "test",
"suture_sets": [
{
"name": "test_set",
"capture": "response",
"sutures": [mappings]
}
]
});
parse_first(&manifest.to_string())
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct BasicPatternResult {
values: Vec<i64>,
}
#[test]
fn basic_pattern_field_digits_to_vec() {
let suture = response_suture(json!({
"/`field_\\d+`": "values[:]"
}));
let input = json!({
"field_0": 10,
"field_1": 20,
"field_2": 30
});
let result: BasicPatternResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 3);
assert!(result.values.contains(&10));
assert!(result.values.contains(&20));
assert!(result.values.contains(&30));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct AlphaPatternResult {
items: Vec<String>,
}
#[test]
fn alpha_pattern_item_lowercase_to_vec() {
let suture = response_suture(json!({
"/`item_[a-z]+`": "items[:]"
}));
let input = json!({
"item_foo": "hello",
"item_bar": "world"
});
let result: AlphaPatternResult = suture.unstitch(&input).unwrap();
assert_eq!(result.items.len(), 2);
assert!(result.items.contains(&"hello".to_string()));
assert!(result.items.contains(&"world".to_string()));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct NoMatchResult {
#[serde(default)]
values: Vec<i64>,
}
#[test]
fn pattern_no_matches_yields_empty_vec() {
let suture = response_suture(json!({
"/`field_\\d+`": "values[:]"
}));
let input = json!({
"name": "Alice",
"age": 30,
"active": true
});
let result: NoMatchResult = suture.unstitch(&input).unwrap();
assert!(result.values.is_empty());
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct PartialMatchResult {
values: Vec<i64>,
}
#[test]
fn pattern_partial_match_ignores_non_matching_keys() {
let suture = response_suture(json!({
"/`field_\\d+`": "values[:]"
}));
let input = json!({
"field_0": 100,
"field_1": 200,
"name": "should_be_ignored",
"other_key": 999
});
let result: PartialMatchResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 2);
assert!(result.values.contains(&100));
assert!(result.values.contains(&200));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct NonCapturingGroupResult {
values: Vec<i64>,
}
#[test]
fn non_capturing_group_pattern() {
let suture = response_suture(json!({
"/`(?:in|out)_\\d+`": "values[:]"
}));
let input = json!({
"in_0": 1,
"out_1": 2,
"in_2": 3,
"other_0": 999
});
let result: NonCapturingGroupResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 3);
assert!(result.values.contains(&1));
assert!(result.values.contains(&2));
assert!(result.values.contains(&3));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct FirstMatchResult {
values: Vec<i64>,
}
#[test]
fn pattern_slice_first_only() {
let suture = response_suture(json!({
"/`field_\\d+`[0]": "values[:]"
}));
let input = json!({
"field_0": 10,
"field_1": 20,
"field_2": 30
});
let result: FirstMatchResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 1);
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct FirstTwoResult {
values: Vec<i64>,
}
#[test]
fn pattern_slice_first_two() {
let suture = response_suture(json!({
"/`field_\\d+`[0:2]": "values[:]"
}));
let input = json!({
"field_0": 10,
"field_1": 20,
"field_2": 30,
"field_3": 40
});
let result: FirstTwoResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 2);
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct SkipFirstResult {
values: Vec<i64>,
}
#[test]
fn pattern_slice_skip_first() {
let suture = response_suture(json!({
"/`field_\\d+`[1:]": "values[:]"
}));
let input = json!({
"field_0": 10,
"field_1": 20,
"field_2": 30
});
let result: SkipFirstResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 2);
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct ReverseOrderResult {
values: Vec<i64>,
}
#[test]
fn pattern_slice_reverse_order() {
let suture = response_suture(json!({
"/`field_\\d+`[::-1]": "values[:]"
}));
let input = json!({
"field_0": 10,
"field_1": 20,
"field_2": 30
});
let result: ReverseOrderResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 3);
let suture_fwd = response_suture(json!({
"/`field_\\d+`": "values[:]"
}));
let fwd: ReverseOrderResult = suture_fwd.unstitch(&input).unwrap();
assert_eq!(result.values.len(), fwd.values.len());
let mut expected = fwd.values.clone();
expected.reverse();
assert_eq!(result.values, expected);
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct NestedPatternResult {
names: Vec<String>,
}
#[test]
fn nested_path_then_pattern_with_child_extraction() {
let suture = response_suture(json!({
"/data/`entry_\\d+`/name": "names[:]"
}));
let input = json!({
"data": {
"entry_0": { "name": "Alice", "age": 30 },
"entry_1": { "name": "Bob", "age": 25 },
"entry_2": { "name": "Carol", "age": 35 }
}
});
let result: NestedPatternResult = suture.unstitch(&input).unwrap();
assert_eq!(result.names.len(), 3);
assert!(result.names.contains(&"Alice".to_string()));
assert!(result.names.contains(&"Bob".to_string()));
assert!(result.names.contains(&"Carol".to_string()));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct MultiSubFieldResult {
names: Vec<String>,
scores: Vec<i64>,
}
#[test]
fn pattern_extract_multiple_sub_fields() {
let suture = response_suture(json!({
"/`player_\\d+`": {
"/name": "names[:]",
"/score": "scores[:]"
}
}));
let input = json!({
"player_0": { "name": "Alice", "score": 100 },
"player_1": { "name": "Bob", "score": 200 }
});
let result: MultiSubFieldResult = suture.unstitch(&input).unwrap();
assert_eq!(result.names.len(), 2);
assert_eq!(result.scores.len(), 2);
assert!(result.names.contains(&"Alice".to_string()));
assert!(result.names.contains(&"Bob".to_string()));
assert!(result.scores.contains(&100));
assert!(result.scores.contains(&200));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct FanOutResult {
primary: Vec<i64>,
secondary: Vec<i64>,
}
#[test]
fn pattern_fan_out_to_multiple_struct_fields() {
let suture = response_suture(json!({
"/`val_\\d+`": ["primary[:]", "secondary[:]"]
}));
let input = json!({
"val_0": 10,
"val_1": 20
});
let result: FanOutResult = suture.unstitch(&input).unwrap();
assert_eq!(result.primary.len(), 2);
assert_eq!(result.secondary.len(), 2);
assert!(result.primary.contains(&10));
assert!(result.primary.contains(&20));
assert!(result.secondary.contains(&10));
assert!(result.secondary.contains(&20));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct TwoPatternsResult {
inputs: Vec<f64>,
outputs: Vec<f64>,
}
#[test]
fn two_different_patterns_in_same_suture() {
let suture = response_suture(json!({
"/`in_\\d+`": "inputs[:]",
"/`out_\\d+`": "outputs[:]"
}));
let input = json!({
"in_0": 1.0,
"in_1": 2.0,
"out_0": 10.0,
"out_1": 20.0,
"out_2": 30.0
});
let result: TwoPatternsResult = suture.unstitch(&input).unwrap();
assert_eq!(result.inputs.len(), 2);
assert_eq!(result.outputs.len(), 3);
assert!(result.inputs.contains(&1.0));
assert!(result.inputs.contains(&2.0));
assert!(result.outputs.contains(&10.0));
assert!(result.outputs.contains(&20.0));
assert!(result.outputs.contains(&30.0));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct MixedResult {
name: String,
values: Vec<i64>,
}
#[test]
fn mix_of_direct_and_pattern_bindings() {
let suture = response_suture(json!({
"/name": "name",
"/`field_\\d+`": "values[:]"
}));
let input = json!({
"name": "test_object",
"field_0": 100,
"field_1": 200,
"field_2": 300
});
let result: MixedResult = suture.unstitch(&input).unwrap();
assert_eq!(result.name, "test_object");
assert_eq!(result.values.len(), 3);
assert!(result.values.contains(&100));
assert!(result.values.contains(&200));
assert!(result.values.contains(&300));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct AllKeysResult {
values: Vec<String>,
}
#[test]
fn pattern_matching_all_keys() {
let suture = response_suture(json!({
"/`[a-z]+`": "values[:]"
}));
let input = json!({
"alpha": "one",
"beta": "two",
"gamma": "three"
});
let result: AllKeysResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 3);
assert!(result.values.contains(&"one".to_string()));
assert!(result.values.contains(&"two".to_string()));
assert!(result.values.contains(&"three".to_string()));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct SpecialCharsResult {
values: Vec<String>,
}
#[test]
fn pattern_with_escaped_regex_chars() {
let suture = response_suture(json!({
"/`v\\d+\\.\\d+`": "values[:]"
}));
let input = json!({
"v1.0": "first",
"v2.0": "second",
"v30": "should_not_match"
});
let result: SpecialCharsResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 2);
assert!(result.values.contains(&"first".to_string()));
assert!(result.values.contains(&"second".to_string()));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct NumericKeysResult {
values: Vec<String>,
}
#[test]
fn pattern_numeric_only_keys() {
let suture = response_suture(json!({
"/`\\d+`": "values[:]"
}));
let input = json!({
"0": "zero",
"1": "one",
"42": "answer",
"abc": "should_not_match"
});
let result: NumericKeysResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 3);
assert!(result.values.contains(&"zero".to_string()));
assert!(result.values.contains(&"one".to_string()));
assert!(result.values.contains(&"answer".to_string()));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct SingleCharResult {
values: Vec<i64>,
}
#[test]
fn pattern_single_char_keys() {
let suture = response_suture(json!({
"/`[a-z]`": "values[:]"
}));
let input = json!({
"a": 1,
"b": 2,
"z": 26,
"ab": 99,
"A": 100
});
let result: SingleCharResult = suture.unstitch(&input).unwrap();
assert_eq!(result.values.len(), 3);
assert!(result.values.contains(&1));
assert!(result.values.contains(&2));
assert!(result.values.contains(&26));
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, sutures::Seam)]
struct EmptyObjectResult {
#[serde(default)]
values: Vec<i64>,
}
#[test]
fn empty_json_object_yields_empty_arrays() {
let suture = response_suture(json!({
"/`field_\\d+`": "values[:]"
}));
let input = json!({});
let result: EmptyObjectResult = suture.unstitch(&input).unwrap();
assert!(result.values.is_empty());
}