1use serde_json::Value;
15use std::collections::HashMap;
16
17#[derive(Debug, Clone, Default)]
22pub struct RequestContext {
23 pub method: String,
25 pub path: String,
27 pub path_params: HashMap<String, Value>,
29 pub query_params: HashMap<String, Value>,
31 pub headers: HashMap<String, Value>,
33 pub body: Option<Value>,
35 pub multipart_fields: HashMap<String, Value>,
37 pub multipart_files: HashMap<String, String>,
39}
40
41impl RequestContext {
42 #[must_use]
44 pub fn new(method: String, path: String) -> Self {
45 Self {
46 method,
47 path,
48 ..Default::default()
49 }
50 }
51
52 #[must_use]
54 pub fn with_path_params(mut self, params: HashMap<String, Value>) -> Self {
55 self.path_params = params;
56 self
57 }
58
59 #[must_use]
61 pub fn with_query_params(mut self, params: HashMap<String, Value>) -> Self {
62 self.query_params = params;
63 self
64 }
65
66 #[must_use]
68 pub fn with_headers(mut self, headers: HashMap<String, Value>) -> Self {
69 self.headers = headers;
70 self
71 }
72
73 #[must_use]
75 pub fn with_body(mut self, body: Value) -> Self {
76 self.body = Some(body);
77 self
78 }
79
80 #[must_use]
82 pub fn with_multipart_fields(mut self, fields: HashMap<String, Value>) -> Self {
83 self.multipart_fields = fields;
84 self
85 }
86
87 #[must_use]
89 pub fn with_multipart_files(mut self, files: HashMap<String, String>) -> Self {
90 self.multipart_files = files;
91 self
92 }
93}
94
95#[must_use]
124pub fn expand_prompt_template(template: &str, context: &RequestContext) -> String {
125 let mut result = template.to_string();
126
127 result = result.replace("{{method}}", &context.method);
129
130 result = result.replace("{{path}}", &context.path);
132
133 if let Some(body) = &context.body {
135 result = expand_json_variables(&result, "body", body);
136 }
137
138 result = expand_map_variables(&result, "path", &context.path_params);
140
141 result = expand_map_variables(&result, "query", &context.query_params);
143
144 result = expand_map_variables(&result, "headers", &context.headers);
146
147 result = expand_map_variables(&result, "multipart", &context.multipart_fields);
149
150 result
151}
152
153fn expand_json_variables(template: &str, prefix: &str, value: &Value) -> String {
158 let mut result = template.to_string();
159
160 if let Some(obj) = value.as_object() {
162 for (key, val) in obj {
163 let placeholder = format!("{{{{{prefix}.{key}}}}}");
164 let replacement = match val {
165 Value::String(s) => s.clone(),
166 Value::Number(n) => n.to_string(),
167 Value::Bool(b) => b.to_string(),
168 Value::Null => "null".to_string(),
169 _ => serde_json::to_string(val).unwrap_or_default(),
170 };
171 result = result.replace(&placeholder, &replacement);
172 }
173 }
174
175 result
176}
177
178fn expand_map_variables(template: &str, prefix: &str, map: &HashMap<String, Value>) -> String {
183 let mut result = template.to_string();
184
185 for (key, val) in map {
186 let placeholder = format!("{{{{{prefix}.{key}}}}}");
187 let replacement = match val {
188 Value::String(s) => s.clone(),
189 Value::Number(n) => n.to_string(),
190 Value::Bool(b) => b.to_string(),
191 Value::Null => "null".to_string(),
192 _ => serde_json::to_string(val).unwrap_or_default(),
193 };
194 result = result.replace(&placeholder, &replacement);
195 }
196
197 result
198}
199
200#[must_use]
228pub fn expand_templates_in_json(value: Value, context: &RequestContext) -> Value {
229 match value {
230 Value::String(s) => {
231 let normalized = s
233 .replace("{{request.query.", "{{query.")
234 .replace("{{request.path.", "{{path.")
235 .replace("{{request.headers.", "{{headers.")
236 .replace("{{request.body.", "{{body.")
237 .replace("{{request.method}}", "{{method}}")
238 .replace("{{request.path}}", "{{path}}");
239 Value::String(expand_prompt_template(&normalized, context))
241 }
242 Value::Array(arr) => {
243 Value::Array(arr.into_iter().map(|v| expand_templates_in_json(v, context)).collect())
244 }
245 Value::Object(obj) => Value::Object(
246 obj.into_iter()
247 .map(|(k, v)| (k, expand_templates_in_json(v, context)))
248 .collect(),
249 ),
250 _ => value,
251 }
252}
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 use serde_json::json;
258 use std::collections::HashMap;
259
260 #[test]
263 fn test_request_context_new() {
264 let ctx = RequestContext::new("POST".to_string(), "/api/test".to_string());
265 assert_eq!(ctx.method, "POST");
266 assert_eq!(ctx.path, "/api/test");
267 assert!(ctx.path_params.is_empty());
268 assert!(ctx.query_params.is_empty());
269 assert!(ctx.headers.is_empty());
270 assert!(ctx.body.is_none());
271 assert!(ctx.multipart_fields.is_empty());
272 assert!(ctx.multipart_files.is_empty());
273 }
274
275 #[test]
276 fn test_request_context_default() {
277 let ctx = RequestContext::default();
278 assert_eq!(ctx.method, "");
279 assert_eq!(ctx.path, "");
280 assert!(ctx.path_params.is_empty());
281 assert!(ctx.query_params.is_empty());
282 assert!(ctx.headers.is_empty());
283 assert!(ctx.body.is_none());
284 assert!(ctx.multipart_fields.is_empty());
285 assert!(ctx.multipart_files.is_empty());
286 }
287
288 #[test]
289 fn test_request_context_with_path_params() {
290 let mut params = HashMap::new();
291 params.insert("id".to_string(), json!("123"));
292 params.insert("name".to_string(), json!("test"));
293
294 let ctx = RequestContext::new("GET".to_string(), "/users".to_string())
295 .with_path_params(params.clone());
296
297 assert_eq!(ctx.path_params.len(), 2);
298 assert_eq!(ctx.path_params.get("id"), Some(&json!("123")));
299 assert_eq!(ctx.path_params.get("name"), Some(&json!("test")));
300 }
301
302 #[test]
303 fn test_request_context_with_query_params() {
304 let mut params = HashMap::new();
305 params.insert("page".to_string(), json!(1));
306 params.insert("limit".to_string(), json!(10));
307
308 let ctx =
309 RequestContext::new("GET".to_string(), "/items".to_string()).with_query_params(params);
310
311 assert_eq!(ctx.query_params.len(), 2);
312 assert_eq!(ctx.query_params.get("page"), Some(&json!(1)));
313 }
314
315 #[test]
316 fn test_request_context_with_headers() {
317 let mut headers = HashMap::new();
318 headers.insert("content-type".to_string(), json!("application/json"));
319 headers.insert("authorization".to_string(), json!("Bearer token123"));
320
321 let ctx = RequestContext::new("POST".to_string(), "/api".to_string()).with_headers(headers);
322
323 assert_eq!(ctx.headers.len(), 2);
324 assert_eq!(ctx.headers.get("content-type"), Some(&json!("application/json")));
325 }
326
327 #[test]
328 fn test_request_context_with_body() {
329 let body = json!({"key": "value", "count": 42});
330 let ctx =
331 RequestContext::new("POST".to_string(), "/data".to_string()).with_body(body.clone());
332
333 assert_eq!(ctx.body, Some(body));
334 }
335
336 #[test]
337 fn test_request_context_with_multipart_fields() {
338 let mut fields = HashMap::new();
339 fields.insert("username".to_string(), json!("testuser"));
340 fields.insert("email".to_string(), json!("test@example.com"));
341
342 let ctx = RequestContext::new("POST".to_string(), "/upload".to_string())
343 .with_multipart_fields(fields);
344
345 assert_eq!(ctx.multipart_fields.len(), 2);
346 assert_eq!(ctx.multipart_fields.get("username"), Some(&json!("testuser")));
347 }
348
349 #[test]
350 fn test_request_context_with_multipart_files() {
351 let mut files = HashMap::new();
352 files.insert("document".to_string(), "/tmp/doc.pdf".to_string());
353 files.insert("image".to_string(), "/tmp/photo.jpg".to_string());
354
355 let ctx = RequestContext::new("POST".to_string(), "/upload".to_string())
356 .with_multipart_files(files);
357
358 assert_eq!(ctx.multipart_files.len(), 2);
359 assert_eq!(ctx.multipart_files.get("document"), Some(&"/tmp/doc.pdf".to_string()));
360 }
361
362 #[test]
363 fn test_request_context_builder_chain() {
364 let mut path_params = HashMap::new();
365 path_params.insert("id".to_string(), json!("456"));
366
367 let mut query_params = HashMap::new();
368 query_params.insert("verbose".to_string(), json!(true));
369
370 let mut headers = HashMap::new();
371 headers.insert("x-custom".to_string(), json!("value"));
372
373 let mut multipart_fields = HashMap::new();
374 multipart_fields.insert("field1".to_string(), json!("data"));
375
376 let mut multipart_files = HashMap::new();
377 multipart_files.insert("file1".to_string(), "/path/to/file".to_string());
378
379 let ctx = RequestContext::new("PUT".to_string(), "/resource/456".to_string())
380 .with_path_params(path_params)
381 .with_query_params(query_params)
382 .with_headers(headers)
383 .with_body(json!({"update": true}))
384 .with_multipart_fields(multipart_fields)
385 .with_multipart_files(multipart_files);
386
387 assert_eq!(ctx.method, "PUT");
388 assert_eq!(ctx.path, "/resource/456");
389 assert_eq!(ctx.path_params.len(), 1);
390 assert_eq!(ctx.query_params.len(), 1);
391 assert_eq!(ctx.headers.len(), 1);
392 assert!(ctx.body.is_some());
393 assert_eq!(ctx.multipart_fields.len(), 1);
394 assert_eq!(ctx.multipart_files.len(), 1);
395 }
396
397 #[test]
400 fn test_expand_prompt_template_basic() {
401 let context = RequestContext::new("GET".to_string(), "/users".to_string());
402 let template = "Method: {{method}}, Path: {{path}}";
403 let expanded = expand_prompt_template(template, &context);
404 assert_eq!(expanded, "Method: GET, Path: /users");
405 }
406
407 #[test]
408 fn test_expand_prompt_template_empty_template() {
409 let context = RequestContext::new("GET".to_string(), "/test".to_string());
410 let expanded = expand_prompt_template("", &context);
411 assert_eq!(expanded, "");
412 }
413
414 #[test]
415 fn test_expand_prompt_template_no_variables() {
416 let context = RequestContext::new("GET".to_string(), "/test".to_string());
417 let template = "This is a plain string with no variables";
418 let expanded = expand_prompt_template(template, &context);
419 assert_eq!(expanded, template);
420 }
421
422 #[test]
423 fn test_expand_prompt_template_missing_variable() {
424 let context = RequestContext::new("GET".to_string(), "/test".to_string());
425 let template = "Value is {{query.nonexistent}}";
426 let expanded = expand_prompt_template(template, &context);
427 assert_eq!(expanded, "Value is {{query.nonexistent}}");
429 }
430
431 #[test]
432 fn test_expand_prompt_template_multiple_occurrences() {
433 let context = RequestContext::new("GET".to_string(), "/api".to_string());
434 let template = "{{method}} to {{path}}, again {{method}} to {{path}}";
435 let expanded = expand_prompt_template(template, &context);
436 assert_eq!(expanded, "GET to /api, again GET to /api");
437 }
438
439 #[test]
440 fn test_expand_prompt_template_body() {
441 let body = json!({
442 "message": "Hello",
443 "user": "Alice"
444 });
445 let context = RequestContext::new("POST".to_string(), "/chat".to_string()).with_body(body);
446
447 let template = "User {{body.user}} says: {{body.message}}";
448 let expanded = expand_prompt_template(template, &context);
449 assert_eq!(expanded, "User Alice says: Hello");
450 }
451
452 #[test]
453 fn test_expand_prompt_template_body_with_number() {
454 let body = json!({
455 "count": 42,
456 "price": 19.99
457 });
458 let context = RequestContext::new("POST".to_string(), "/order".to_string()).with_body(body);
459
460 let template = "Count: {{body.count}}, Price: {{body.price}}";
461 let expanded = expand_prompt_template(template, &context);
462 assert_eq!(expanded, "Count: 42, Price: 19.99");
463 }
464
465 #[test]
466 fn test_expand_prompt_template_body_with_boolean() {
467 let body = json!({
468 "active": true,
469 "deleted": false
470 });
471 let context =
472 RequestContext::new("POST".to_string(), "/status".to_string()).with_body(body);
473
474 let template = "Active: {{body.active}}, Deleted: {{body.deleted}}";
475 let expanded = expand_prompt_template(template, &context);
476 assert_eq!(expanded, "Active: true, Deleted: false");
477 }
478
479 #[test]
480 fn test_expand_prompt_template_body_with_null() {
481 let body = json!({
482 "value": null
483 });
484 let context = RequestContext::new("POST".to_string(), "/data".to_string()).with_body(body);
485
486 let template = "Value: {{body.value}}";
487 let expanded = expand_prompt_template(template, &context);
488 assert_eq!(expanded, "Value: null");
489 }
490
491 #[test]
492 fn test_expand_prompt_template_body_with_nested_object() {
493 let body = json!({
494 "nested": {"inner": "value"}
495 });
496 let context = RequestContext::new("POST".to_string(), "/data".to_string()).with_body(body);
497
498 let template = "Nested: {{body.nested}}";
499 let expanded = expand_prompt_template(template, &context);
500 assert_eq!(expanded, r#"Nested: {"inner":"value"}"#);
501 }
502
503 #[test]
504 fn test_expand_prompt_template_body_with_array() {
505 let body = json!({
506 "items": [1, 2, 3]
507 });
508 let context = RequestContext::new("POST".to_string(), "/data".to_string()).with_body(body);
509
510 let template = "Items: {{body.items}}";
511 let expanded = expand_prompt_template(template, &context);
512 assert_eq!(expanded, "Items: [1,2,3]");
513 }
514
515 #[test]
516 fn test_expand_prompt_template_no_body() {
517 let context = RequestContext::new("GET".to_string(), "/test".to_string());
518 let template = "Body field: {{body.field}}";
519 let expanded = expand_prompt_template(template, &context);
520 assert_eq!(expanded, "Body field: {{body.field}}");
522 }
523
524 #[test]
525 fn test_expand_prompt_template_path_params() {
526 let mut path_params = HashMap::new();
527 path_params.insert("id".to_string(), json!("456"));
528 path_params.insert("name".to_string(), json!("test"));
529
530 let context = RequestContext::new("GET".to_string(), "/users/456".to_string())
531 .with_path_params(path_params);
532
533 let template = "Get user {{path.id}} with name {{path.name}}";
534 let expanded = expand_prompt_template(template, &context);
535 assert_eq!(expanded, "Get user 456 with name test");
536 }
537
538 #[test]
539 fn test_expand_prompt_template_query_params() {
540 let mut query_params = HashMap::new();
541 query_params.insert("search".to_string(), json!("term"));
542 query_params.insert("limit".to_string(), json!(10));
543
544 let context = RequestContext::new("GET".to_string(), "/search".to_string())
545 .with_query_params(query_params);
546
547 let template = "Search for {{query.search}} with limit {{query.limit}}";
548 let expanded = expand_prompt_template(template, &context);
549 assert_eq!(expanded, "Search for term with limit 10");
550 }
551
552 #[test]
553 fn test_expand_prompt_template_query_params_boolean() {
554 let mut query_params = HashMap::new();
555 query_params.insert("verbose".to_string(), json!(true));
556 query_params.insert("debug".to_string(), json!(false));
557
558 let context = RequestContext::new("GET".to_string(), "/api".to_string())
559 .with_query_params(query_params);
560
561 let template = "Verbose: {{query.verbose}}, Debug: {{query.debug}}";
562 let expanded = expand_prompt_template(template, &context);
563 assert_eq!(expanded, "Verbose: true, Debug: false");
564 }
565
566 #[test]
567 fn test_expand_prompt_template_query_params_null() {
568 let mut query_params = HashMap::new();
569 query_params.insert("filter".to_string(), json!(null));
570
571 let context = RequestContext::new("GET".to_string(), "/api".to_string())
572 .with_query_params(query_params);
573
574 let template = "Filter: {{query.filter}}";
575 let expanded = expand_prompt_template(template, &context);
576 assert_eq!(expanded, "Filter: null");
577 }
578
579 #[test]
580 fn test_expand_prompt_template_query_params_array() {
581 let mut query_params = HashMap::new();
582 query_params.insert("tags".to_string(), json!(["a", "b", "c"]));
583
584 let context = RequestContext::new("GET".to_string(), "/api".to_string())
585 .with_query_params(query_params);
586
587 let template = "Tags: {{query.tags}}";
588 let expanded = expand_prompt_template(template, &context);
589 assert_eq!(expanded, r#"Tags: ["a","b","c"]"#);
590 }
591
592 #[test]
593 fn test_expand_prompt_template_headers() {
594 let mut headers = HashMap::new();
595 headers.insert("user-agent".to_string(), json!("TestClient/1.0"));
596
597 let context =
598 RequestContext::new("GET".to_string(), "/api".to_string()).with_headers(headers);
599
600 let template = "Request from {{headers.user-agent}}";
601 let expanded = expand_prompt_template(template, &context);
602 assert_eq!(expanded, "Request from TestClient/1.0");
603 }
604
605 #[test]
606 fn test_expand_prompt_template_multipart_fields() {
607 let mut multipart_fields = HashMap::new();
608 multipart_fields.insert("username".to_string(), json!("testuser"));
609 multipart_fields.insert("description".to_string(), json!("A test file"));
610
611 let context = RequestContext::new("POST".to_string(), "/upload".to_string())
612 .with_multipart_fields(multipart_fields);
613
614 let template = "User: {{multipart.username}}, Desc: {{multipart.description}}";
615 let expanded = expand_prompt_template(template, &context);
616 assert_eq!(expanded, "User: testuser, Desc: A test file");
617 }
618
619 #[test]
620 fn test_expand_prompt_template_complex() {
621 let mut path_params = HashMap::new();
622 path_params.insert("id".to_string(), json!("789"));
623
624 let mut query_params = HashMap::new();
625 query_params.insert("format".to_string(), json!("json"));
626
627 let body = json!({"action": "update", "value": 42});
628
629 let context = RequestContext::new("PUT".to_string(), "/api/items/789".to_string())
630 .with_path_params(path_params)
631 .with_query_params(query_params)
632 .with_body(body);
633
634 let template = "{{method}} item {{path.id}} with action {{body.action}} and value {{body.value}} in format {{query.format}}";
635 let expanded = expand_prompt_template(template, &context);
636 assert_eq!(expanded, "PUT item 789 with action update and value 42 in format json");
637 }
638
639 #[test]
640 fn test_expand_prompt_template_empty_context() {
641 let context = RequestContext::default();
642 let template = "Method: {{method}}, Path: {{path}}";
643 let expanded = expand_prompt_template(template, &context);
644 assert_eq!(expanded, "Method: , Path: ");
645 }
646
647 #[test]
650 fn test_expand_templates_in_json() {
651 let context = RequestContext::new("GET".to_string(), "/api/users".to_string());
652 let value = json!({
653 "message": "Request to {{path}}",
654 "method": "{{method}}",
655 "nested": {
656 "path": "{{path}}"
657 },
658 "array": ["{{method}}", "{{path}}"]
659 });
660
661 let expanded = expand_templates_in_json(value, &context);
662 assert_eq!(expanded["message"], "Request to /api/users");
663 assert_eq!(expanded["method"], "GET");
664 assert_eq!(expanded["nested"]["path"], "/api/users");
665 assert_eq!(expanded["array"][0], "GET");
666 assert_eq!(expanded["array"][1], "/api/users");
667 }
668
669 #[test]
670 fn test_expand_templates_in_json_primitives_unchanged() {
671 let context = RequestContext::new("GET".to_string(), "/test".to_string());
672
673 let num_value = json!(42);
675 let expanded_num = expand_templates_in_json(num_value, &context);
676 assert_eq!(expanded_num, json!(42));
677
678 let bool_value = json!(true);
680 let expanded_bool = expand_templates_in_json(bool_value, &context);
681 assert_eq!(expanded_bool, json!(true));
682
683 let null_value = json!(null);
685 let expanded_null = expand_templates_in_json(null_value, &context);
686 assert_eq!(expanded_null, json!(null));
687
688 let float_value = json!(3.125);
690 let expanded_float = expand_templates_in_json(float_value, &context);
691 assert_eq!(expanded_float, json!(3.125));
692 }
693
694 #[test]
695 fn test_expand_templates_in_json_empty_string() {
696 let context = RequestContext::new("GET".to_string(), "/test".to_string());
697 let value = json!("");
698 let expanded = expand_templates_in_json(value, &context);
699 assert_eq!(expanded, json!(""));
700 }
701
702 #[test]
703 fn test_expand_templates_in_json_empty_array() {
704 let context = RequestContext::new("GET".to_string(), "/test".to_string());
705 let value = json!([]);
706 let expanded = expand_templates_in_json(value, &context);
707 assert_eq!(expanded, json!([]));
708 }
709
710 #[test]
711 fn test_expand_templates_in_json_empty_object() {
712 let context = RequestContext::new("GET".to_string(), "/test".to_string());
713 let value = json!({});
714 let expanded = expand_templates_in_json(value, &context);
715 assert_eq!(expanded, json!({}));
716 }
717
718 #[test]
719 fn test_expand_templates_in_json_deeply_nested() {
720 let context = RequestContext::new("POST".to_string(), "/deep".to_string());
721 let value = json!({
722 "level1": {
723 "level2": {
724 "level3": {
725 "method": "{{method}}",
726 "path": "{{path}}"
727 }
728 }
729 }
730 });
731
732 let expanded = expand_templates_in_json(value, &context);
733 assert_eq!(expanded["level1"]["level2"]["level3"]["method"], "POST");
734 assert_eq!(expanded["level1"]["level2"]["level3"]["path"], "/deep");
735 }
736
737 #[test]
738 fn test_expand_templates_in_json_mixed_array() {
739 let context = RequestContext::new("DELETE".to_string(), "/resource".to_string());
740 let value = json!([
741 "{{method}}",
742 42,
743 true,
744 null,
745 {"nested": "{{path}}"},
746 ["{{method}}", 123]
747 ]);
748
749 let expanded = expand_templates_in_json(value, &context);
750 assert_eq!(expanded[0], "DELETE");
751 assert_eq!(expanded[1], 42);
752 assert_eq!(expanded[2], true);
753 assert_eq!(expanded[3], json!(null));
754 assert_eq!(expanded[4]["nested"], "/resource");
755 assert_eq!(expanded[5][0], "DELETE");
756 assert_eq!(expanded[5][1], 123);
757 }
758
759 #[test]
760 fn test_expand_templates_in_json_normalize_request_prefix() {
761 let context = RequestContext::new("POST".to_string(), "/api/data".to_string());
762 let value = json!({
763 "message": "{{request.method}} {{request.path}}",
764 "query": "{{request.query.name}}"
765 });
766
767 let expanded = expand_templates_in_json(value, &context);
768 assert_eq!(expanded["message"], "POST /api/data");
769 }
771
772 #[test]
773 fn test_expand_templates_in_json_normalize_all_request_prefixes() {
774 let mut query_params = HashMap::new();
775 query_params.insert("search".to_string(), json!("test"));
776
777 let mut path_params = HashMap::new();
778 path_params.insert("id".to_string(), json!("123"));
779
780 let mut headers = HashMap::new();
781 headers.insert("auth".to_string(), json!("token"));
782
783 let body = json!({"field": "value"});
784
785 let context = RequestContext::new("GET".to_string(), "/items/123".to_string())
786 .with_query_params(query_params)
787 .with_path_params(path_params)
788 .with_headers(headers)
789 .with_body(body);
790
791 let value = json!({
792 "method": "{{request.method}}",
793 "path": "{{request.path}}",
794 "query_search": "{{request.query.search}}",
795 "path_id": "{{request.path.id}}",
796 "header_auth": "{{request.headers.auth}}",
797 "body_field": "{{request.body.field}}"
798 });
799
800 let expanded = expand_templates_in_json(value, &context);
801 assert_eq!(expanded["method"], "GET");
802 assert_eq!(expanded["path"], "/items/123");
803 assert_eq!(expanded["query_search"], "test");
804 assert_eq!(expanded["path_id"], "123");
805 assert_eq!(expanded["header_auth"], "token");
806 assert_eq!(expanded["body_field"], "value");
807 }
808
809 #[test]
810 fn test_expand_templates_in_json_string_without_template() {
811 let context = RequestContext::new("GET".to_string(), "/test".to_string());
812 let value = json!("plain string without any templates");
813 let expanded = expand_templates_in_json(value, &context);
814 assert_eq!(expanded, json!("plain string without any templates"));
815 }
816
817 #[test]
820 fn test_special_characters_in_values() {
821 let mut query_params = HashMap::new();
822 query_params
823 .insert("special".to_string(), json!("value with \"quotes\" and \\backslashes"));
824
825 let context = RequestContext::new("GET".to_string(), "/test?foo=bar&baz=qux".to_string())
826 .with_query_params(query_params);
827
828 let template = "Path: {{path}}, Special: {{query.special}}";
829 let expanded = expand_prompt_template(template, &context);
830 assert!(expanded.contains("/test?foo=bar&baz=qux"));
831 assert!(expanded.contains("value with \"quotes\" and \\backslashes"));
832 }
833
834 #[test]
835 fn test_unicode_in_values() {
836 let body = json!({
837 "message": "Hello δΈη! π",
838 "emoji": "πβ¨"
839 });
840 let context =
841 RequestContext::new("POST".to_string(), "/unicode".to_string()).with_body(body);
842
843 let template = "Message: {{body.message}}, Emoji: {{body.emoji}}";
844 let expanded = expand_prompt_template(template, &context);
845 assert_eq!(expanded, "Message: Hello δΈη! π, Emoji: πβ¨");
846 }
847
848 #[test]
849 fn test_whitespace_handling() {
850 let context =
851 RequestContext::new(" GET ".to_string(), " /path with spaces ".to_string());
852 let template = "Method: '{{method}}', Path: '{{path}}'";
853 let expanded = expand_prompt_template(template, &context);
854 assert_eq!(expanded, "Method: ' GET ', Path: ' /path with spaces '");
855 }
856
857 #[test]
858 fn test_empty_string_values() {
859 let mut query_params = HashMap::new();
860 query_params.insert("empty".to_string(), json!(""));
861
862 let context = RequestContext::new("GET".to_string(), "/test".to_string())
863 .with_query_params(query_params);
864
865 let template = "Empty value: '{{query.empty}}'";
866 let expanded = expand_prompt_template(template, &context);
867 assert_eq!(expanded, "Empty value: ''");
868 }
869
870 #[test]
871 fn test_request_context_debug() {
872 let ctx = RequestContext::new("GET".to_string(), "/test".to_string());
873 let debug_str = format!("{:?}", ctx);
874 assert!(debug_str.contains("RequestContext"));
875 assert!(debug_str.contains("GET"));
876 assert!(debug_str.contains("/test"));
877 }
878
879 #[test]
880 fn test_request_context_clone() {
881 let mut query_params = HashMap::new();
882 query_params.insert("key".to_string(), json!("value"));
883
884 let ctx = RequestContext::new("POST".to_string(), "/clone-test".to_string())
885 .with_query_params(query_params)
886 .with_body(json!({"data": 123}));
887
888 let cloned = ctx.clone();
889 assert_eq!(cloned.method, ctx.method);
890 assert_eq!(cloned.path, ctx.path);
891 assert_eq!(cloned.query_params, ctx.query_params);
892 assert_eq!(cloned.body, ctx.body);
893 }
894
895 #[test]
896 fn test_expand_prompt_template_body_non_object() {
897 let context = RequestContext::new("POST".to_string(), "/data".to_string())
899 .with_body(json!("just a string"));
900
901 let template = "Body: {{body}}, method: {{method}}";
902 let expanded = expand_prompt_template(template, &context);
903 assert_eq!(expanded, "Body: {{body}}, method: POST");
905 }
906
907 #[test]
908 fn test_expand_prompt_template_body_array_top_level() {
909 let context = RequestContext::new("POST".to_string(), "/data".to_string())
911 .with_body(json!([1, 2, 3]));
912
913 let template = "Array body: {{body.item}}, path: {{path}}";
914 let expanded = expand_prompt_template(template, &context);
915 assert_eq!(expanded, "Array body: {{body.item}}, path: /data");
917 }
918
919 #[test]
920 fn test_expand_prompt_template_body_primitive() {
921 let context =
923 RequestContext::new("POST".to_string(), "/data".to_string()).with_body(json!(42));
924
925 let template = "Number body: {{body.value}}, method: {{method}}";
926 let expanded = expand_prompt_template(template, &context);
927 assert_eq!(expanded, "Number body: {{body.value}}, method: POST");
929 }
930
931 #[test]
932 fn test_expand_json_variables_non_object() {
933 let template = "Value: {{test.field}}, other: {{other}}";
935
936 let result = expand_json_variables(template, "test", &json!("string value"));
938 assert_eq!(result, "Value: {{test.field}}, other: {{other}}");
939
940 let result = expand_json_variables(template, "test", &json!(123));
942 assert_eq!(result, "Value: {{test.field}}, other: {{other}}");
943
944 let result = expand_json_variables(template, "test", &json!(true));
946 assert_eq!(result, "Value: {{test.field}}, other: {{other}}");
947
948 let result = expand_json_variables(template, "test", &json!(null));
950 assert_eq!(result, "Value: {{test.field}}, other: {{other}}");
951
952 let result = expand_json_variables(template, "test", &json!([1, 2, 3]));
954 assert_eq!(result, "Value: {{test.field}}, other: {{other}}");
955 }
956
957 #[test]
958 fn test_expand_map_variables_empty_map() {
959 let template = "Query: {{query.param}}, path: {{path.id}}";
960 let empty_map = HashMap::new();
961
962 let result = expand_map_variables(template, "query", &empty_map);
963 assert_eq!(result, "Query: {{query.param}}, path: {{path.id}}");
964 }
965
966 #[test]
967 fn test_expand_map_variables_complex_types() {
968 let mut map = HashMap::new();
969 map.insert("nested".to_string(), json!({"inner": "value"}));
970 map.insert("array".to_string(), json!([1, 2, 3]));
971
972 let template = "Nested: {{test.nested}}, Array: {{test.array}}";
973 let result = expand_map_variables(template, "test", &map);
974 assert_eq!(result, r#"Nested: {"inner":"value"}, Array: [1,2,3]"#);
975 }
976
977 #[test]
978 fn test_expand_templates_in_json_with_request_body_prefix() {
979 let body = json!({"field": "value"});
980 let context = RequestContext::new("POST".to_string(), "/api".to_string()).with_body(body);
981
982 let value = json!({"msg": "{{request.body.field}}"});
983 let expanded = expand_templates_in_json(value, &context);
984 assert_eq!(expanded["msg"], "value");
985 }
986
987 #[test]
988 fn test_key_with_special_chars_in_placeholder() {
989 let mut query_params = HashMap::new();
991 query_params.insert("user-id".to_string(), json!("12345"));
992 query_params.insert("session_token".to_string(), json!("abc123"));
993
994 let context = RequestContext::new("GET".to_string(), "/api".to_string())
995 .with_query_params(query_params);
996
997 let template = "User: {{query.user-id}}, Token: {{query.session_token}}";
998 let expanded = expand_prompt_template(template, &context);
999 assert_eq!(expanded, "User: 12345, Token: abc123");
1000 }
1001
1002 #[test]
1003 fn test_large_number_values() {
1004 let mut query_params = HashMap::new();
1005 query_params.insert("big".to_string(), json!(9999999999i64));
1006 query_params.insert("float".to_string(), json!(1.23456789));
1007
1008 let context = RequestContext::new("GET".to_string(), "/api".to_string())
1009 .with_query_params(query_params);
1010
1011 let template = "Big: {{query.big}}, Float: {{query.float}}";
1012 let expanded = expand_prompt_template(template, &context);
1013 assert!(expanded.contains("9999999999"));
1014 assert!(expanded.contains("1.23456789"));
1015 }
1016
1017 #[test]
1018 fn test_multiple_template_variables_same_name_different_prefix() {
1019 let mut path_params = HashMap::new();
1020 path_params.insert("id".to_string(), json!("path-123"));
1021
1022 let mut query_params = HashMap::new();
1023 query_params.insert("id".to_string(), json!("query-456"));
1024
1025 let body = json!({"id": "body-789"});
1026
1027 let context = RequestContext::new("POST".to_string(), "/resource".to_string())
1028 .with_path_params(path_params)
1029 .with_query_params(query_params)
1030 .with_body(body);
1031
1032 let template = "Path ID: {{path.id}}, Query ID: {{query.id}}, Body ID: {{body.id}}";
1033 let expanded = expand_prompt_template(template, &context);
1034 assert_eq!(expanded, "Path ID: path-123, Query ID: query-456, Body ID: body-789");
1035 }
1036
1037 #[test]
1038 fn test_partial_placeholder_should_not_expand() {
1039 let context = RequestContext::new("GET".to_string(), "/test".to_string());
1040
1041 let template = "{{method} {{method}} {method}} {{metho {{method";
1043 let expanded = expand_prompt_template(template, &context);
1044 assert!(expanded.contains("GET"));
1046 assert!(expanded.contains("{{method}"));
1047 }
1048}