Skip to content

Commit 3f9f04c

Browse files
committed
feat(template): ease restrictions on format_with_inputs method
1 parent 7814c8a commit 3f9f04c

File tree

3 files changed

+181
-74
lines changed

3 files changed

+181
-74
lines changed

src/lib.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,6 @@
242242
//! let result = template.format("not_a_list");
243243
//! assert!(result.is_err());
244244
//! // Error: "Sort operation can only be applied to lists"
245-
//!
246-
//! // Structured template input count validation
247-
//! let template = Template::parse("A: {upper} B: {lower}").unwrap();
248-
//! let result = template.format_with_inputs(&[&["only_one"]], &[" ", " "]);
249-
//! assert!(result.is_err());
250-
//! // Error: "Expected 2 input slices for 2 template sections, got 1"
251245
//! ```
252246
//!
253247
//! ## Performance Notes

src/pipeline/template.rs

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,15 @@ impl MultiTemplate {
607607
/// # Returns
608608
///
609609
/// * `Ok(String)` - The formatted result with each template section processed with its joined inputs
610-
/// * `Err(String)` - Error if inputs/separators length doesn't match template section count or processing fails
610+
/// * `Err(String)` - Error if template section processing fails
611+
///
612+
/// # Input/Template/Separator Count Handling
613+
///
614+
/// This method gracefully handles mismatches between counts:
615+
/// - **Excess inputs**: Extra inputs beyond template section count are truncated/ignored
616+
/// - **Insufficient inputs**: Missing inputs are treated as empty strings for remaining template sections
617+
/// - **Excess separators**: Extra separators beyond template section count are truncated/ignored
618+
/// - **Insufficient separators**: Missing separators default to space " " for remaining template sections
611619
///
612620
/// # Template Section Ordering
613621
///
@@ -624,13 +632,6 @@ impl MultiTemplate {
624632
/// - **Single item `["value"]`**: Uses "value" directly as input
625633
/// - **Multiple items `["a", "b", "c"]`**: Joins with corresponding separator
626634
///
627-
/// # Errors
628-
///
629-
/// Returns an error if:
630-
/// - The number of input slices doesn't match the number of template sections
631-
/// - The number of separators doesn't match the number of template sections
632-
/// - Any template section processing fails
633-
///
634635
/// # Examples
635636
///
636637
/// ```rust
@@ -644,21 +645,31 @@ impl MultiTemplate {
644645
/// ], &[" ", " "]).unwrap();
645646
/// assert_eq!(result, "Users: JOHN DOE PETER PARKER | Email: admin@example.com");
646647
///
647-
/// // File batch processing with different separators
648-
/// let template = Template::parse("tar -czf {lower}.tar.gz {join: }").unwrap();
648+
/// // Excess inputs are truncated
649+
/// let template = Template::parse("diff {} {}").unwrap();
649650
/// let result = template.format_with_inputs(&[
650-
/// &["BACKUP"],
651-
/// &["file1.txt", "file2.txt", "file3.txt"],
651+
/// &["file1.txt"],
652+
/// &["file2.txt"],
653+
/// &["file3.txt"], // This will be ignored
652654
/// ], &[" ", " "]).unwrap();
653-
/// assert_eq!(result, "tar -czf backup.tar.gz file1.txt file2.txt file3.txt");
655+
/// assert_eq!(result, "diff file1.txt file2.txt");
654656
///
655-
/// // Command construction with custom separators
656-
/// let template = Template::parse("grep {join:\\|} {join:,}").unwrap();
657+
/// // Insufficient inputs use empty strings
658+
/// let template = Template::parse("cmd {} {} {}").unwrap();
659+
/// let result = template.format_with_inputs(&[
660+
/// &["arg1"],
661+
/// &["arg2"],
662+
/// // Missing third input - will use empty string
663+
/// ], &[" ", " ", " "]).unwrap();
664+
/// assert_eq!(result, "cmd arg1 arg2 ");
665+
///
666+
/// // Insufficient separators default to space
667+
/// let template = Template::parse("files: {} more: {}").unwrap();
657668
/// let result = template.format_with_inputs(&[
658-
/// &["error", "warning"],
659-
/// &["log1.txt", "log2.txt"],
660-
/// ], &["|", ","]).unwrap();
661-
/// assert_eq!(result, "grep error|warning log1.txt,log2.txt");
669+
/// &["a", "b", "c"],
670+
/// &["x", "y", "z"],
671+
/// ], &[","]).unwrap(); // Only one separator provided
672+
/// assert_eq!(result, "files: a,b,c more: x y z"); // Second uses default space
662673
/// ```
663674
pub fn format_with_inputs(
664675
&self,
@@ -667,23 +678,30 @@ impl MultiTemplate {
667678
) -> Result<String, String> {
668679
let template_sections_count = self.template_section_count();
669680

670-
if inputs.len() != template_sections_count {
671-
return Err(format!(
672-
"Expected {} input slices for {} template sections, got {}",
673-
template_sections_count,
674-
template_sections_count,
675-
inputs.len()
676-
));
677-
}
681+
// Handle input/template count mismatches gracefully
682+
let adjusted_inputs: Vec<&[&str]> = (0..template_sections_count)
683+
.map(|i| {
684+
if i < inputs.len() {
685+
inputs[i] // Use actual input
686+
} else {
687+
&[] as &[&str] // Empty slice for missing inputs
688+
}
689+
})
690+
.collect();
691+
692+
// Handle separator/template count mismatches gracefully
693+
let adjusted_separators: Vec<&str> = (0..template_sections_count)
694+
.map(|i| {
695+
if i < separators.len() {
696+
separators[i] // Use actual separator
697+
} else {
698+
" " // Default to space for missing separators
699+
}
700+
})
701+
.collect();
678702

679-
if separators.len() != template_sections_count {
680-
return Err(format!(
681-
"Expected {} separators for {} template sections, got {}",
682-
template_sections_count,
683-
template_sections_count,
684-
separators.len()
685-
));
686-
}
703+
let inputs = &adjusted_inputs;
704+
let separators = &adjusted_separators;
687705

688706
let mut result = String::new();
689707
let mut template_index = 0;

tests/multi_template_tests.rs

Lines changed: 128 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -407,51 +407,146 @@ fn test_format_with_inputs_multiple_sections() {
407407
}
408408

409409
#[test]
410-
fn test_format_with_inputs_error_wrong_input_count() {
411-
// Test error when input count doesn't match template section count
410+
fn test_format_with_inputs_input_count_handling() {
411+
// Test graceful handling when input count doesn't match template section count
412412
let template = MultiTemplate::parse("A: {upper} B: {lower}").unwrap();
413413

414-
// Too few inputs
414+
// Too few inputs - should use empty string for missing inputs
415415
let result = template.format_with_inputs(&[&["only_one"]], &[" ", " "]);
416-
assert!(result.is_err());
417-
assert!(
418-
result
419-
.unwrap_err()
420-
.contains("Expected 2 input slices for 2 template sections, got 1")
421-
);
416+
assert!(result.is_ok());
417+
assert_eq!(result.unwrap(), "A: ONLY_ONE B: ");
422418

423-
// Too many inputs
419+
// Too many inputs - should truncate excess inputs
424420
let result = template.format_with_inputs(&[&["one"], &["two"], &["three"]], &[" ", " "]);
425-
assert!(result.is_err());
426-
assert!(
427-
result
428-
.unwrap_err()
429-
.contains("Expected 2 input slices for 2 template sections, got 3")
421+
assert!(result.is_ok());
422+
assert_eq!(result.unwrap(), "A: ONE B: two");
423+
}
424+
425+
#[test]
426+
fn test_format_with_inputs_excess_inputs() {
427+
// Test that excess inputs are truncated gracefully
428+
let template = MultiTemplate::parse("diff {} {}").unwrap();
429+
let result = template.format_with_inputs(
430+
&[
431+
&["file1.txt"],
432+
&["file2.txt"],
433+
&["file3.txt"], // This should be ignored
434+
&["file4.txt"], // This should also be ignored
435+
],
436+
&[" ", " "],
430437
);
438+
439+
assert!(result.is_ok());
440+
assert_eq!(result.unwrap(), "diff file1.txt file2.txt");
431441
}
432442

433443
#[test]
434-
fn test_format_with_inputs_error_wrong_separator_count() {
435-
// Test error when separator count doesn't match template section count
436-
let template = MultiTemplate::parse("A: {upper} B: {lower}").unwrap();
444+
fn test_format_with_inputs_insufficient_inputs() {
445+
// Test that missing inputs are treated as empty strings
446+
let template = MultiTemplate::parse("cmd {upper} {lower} {trim}").unwrap();
447+
let result = template.format_with_inputs(
448+
&[
449+
&["arg1"],
450+
&["ARG2"],
451+
// Missing third input
452+
],
453+
&[" ", " ", " "],
454+
);
437455

438-
// Too few separators
439-
let result = template.format_with_inputs(&[&["one"], &["two"]], &[" "]);
440-
assert!(result.is_err());
441-
assert!(
442-
result
443-
.unwrap_err()
444-
.contains("Expected 2 separators for 2 template sections, got 1")
456+
assert!(result.is_ok());
457+
assert_eq!(result.unwrap(), "cmd ARG1 arg2 ");
458+
}
459+
460+
#[test]
461+
fn test_format_with_inputs_empty_inputs_array() {
462+
// Test with completely empty inputs array
463+
let template = MultiTemplate::parse("start {upper} middle {lower} end").unwrap();
464+
let result = template.format_with_inputs(&[], &[" ", " "]);
465+
466+
assert!(result.is_ok());
467+
assert_eq!(result.unwrap(), "start middle end");
468+
}
469+
470+
#[test]
471+
fn test_format_with_inputs_mixed_empty_and_filled() {
472+
// Test with mix of empty slices and filled slices
473+
let template = MultiTemplate::parse("A:{upper} B:{lower} C:{trim}").unwrap();
474+
let result = template.format_with_inputs(
475+
&[
476+
&[], // Empty slice for first section
477+
&["hello"], // Normal input for second section
478+
&[], // Empty slice for third section
479+
],
480+
&[" ", " ", " "],
445481
);
446482

447-
// Too many separators
448-
let result = template.format_with_inputs(&[&["one"], &["two"]], &[" ", " ", " "]);
449-
assert!(result.is_err());
450-
assert!(
451-
result
452-
.unwrap_err()
453-
.contains("Expected 2 separators for 2 template sections, got 3")
483+
assert!(result.is_ok());
484+
assert_eq!(result.unwrap(), "A: B:hello C:");
485+
}
486+
487+
#[test]
488+
fn test_format_with_inputs_one_to_one_mode_scenario() {
489+
// Test the specific scenario from the original issue
490+
let template = MultiTemplate::parse("diff {} {}").unwrap();
491+
492+
// Simulating OneToOne mode: individual slices for each input
493+
let inputs = ["file1.txt", "file2.txt", "file3.txt"];
494+
let input_arrays: Vec<&[&str]> = inputs.iter().map(std::slice::from_ref).collect();
495+
496+
let result = template.format_with_inputs(&input_arrays, &[" ", " "]);
497+
498+
assert!(result.is_ok());
499+
assert_eq!(result.unwrap(), "diff file1.txt file2.txt");
500+
// file3.txt should be ignored (truncated)
501+
}
502+
503+
#[test]
504+
fn test_format_with_inputs_separator_defaults() {
505+
// Test that missing separators default to space " "
506+
let template = MultiTemplate::parse("files: {} | items: {} | values: {}").unwrap();
507+
508+
// Provide separators for only first section
509+
let result = template.format_with_inputs(
510+
&[
511+
&["a", "b", "c"], // First section gets comma separator
512+
&["x", "y", "z"], // Second section gets default space separator
513+
&["1", "2", "3"], // Third section gets default space separator
514+
],
515+
&[","],
454516
);
517+
518+
assert!(result.is_ok());
519+
assert_eq!(
520+
result.unwrap(),
521+
"files: a,b,c | items: x y z | values: 1 2 3"
522+
);
523+
}
524+
525+
#[test]
526+
fn test_format_with_inputs_no_separators_provided() {
527+
// Test with no separators provided - all should default to space
528+
let template = MultiTemplate::parse("A: {} B: {}").unwrap();
529+
530+
let result = template.format_with_inputs(&[&["one", "two", "three"], &["a", "b", "c"]], &[]); // No separators provided
531+
532+
assert!(result.is_ok());
533+
assert_eq!(result.unwrap(), "A: one two three B: a b c");
534+
}
535+
536+
#[test]
537+
fn test_format_with_inputs_separator_count_handling() {
538+
// Test graceful handling when separator count doesn't match template section count
539+
let template = MultiTemplate::parse("A: {upper} B: {lower}").unwrap();
540+
541+
// Too few separators - should use default space for missing separators
542+
let result = template.format_with_inputs(&[&["one"], &["two"]], &[","]);
543+
assert!(result.is_ok());
544+
assert_eq!(result.unwrap(), "A: ONE B: two");
545+
546+
// Too many separators - should truncate excess separators
547+
let result = template.format_with_inputs(&[&["one"], &["two"]], &[",", ";", ":"]);
548+
assert!(result.is_ok());
549+
assert_eq!(result.unwrap(), "A: ONE B: two");
455550
}
456551

457552
#[test]
@@ -477,7 +572,7 @@ fn test_format_with_inputs_empty_sections() {
477572
#[test]
478573
fn test_format_with_inputs_custom_separators() {
479574
// Test different separators for each section
480-
let template = MultiTemplate::parse("List1: {join:;} | List2: {join:,}").unwrap();
575+
let template = MultiTemplate::parse("List1: {} | List2: {}").unwrap();
481576
let result = template
482577
.format_with_inputs(&[&["a", "b", "c"], &["x", "y", "z"]], &["-", "|"])
483578
.unwrap();

0 commit comments

Comments
 (0)