RenderTemplatesInString finds all template placeholders (e.g., {{.name}} or {{string .name}}) in a string, executes them with the provided data, and replaces them with the result. It is robust against strings that contain non-template curly braces by using a strict regex.
(logger *zap.Logger, input string, templateData map[string]interface{})
| 1447 | // executes them with the provided data, and replaces them with the result. |
| 1448 | // It is robust against strings that contain non-template curly braces by using a strict regex. |
| 1449 | func RenderTemplatesInString(logger *zap.Logger, input string, templateData map[string]interface{}) (string, error) { |
| 1450 | // This regex is specifically designed to match valid Keploy templates: |
| 1451 | // - It must start with {{ and optional whitespace. |
| 1452 | // - It can optionally have a function call ("string", "int", "float") followed by whitespace. |
| 1453 | // - It MUST contain a dot (.) to indicate a field access. |
| 1454 | // - It non-greedily matches characters until the closing braces. |
| 1455 | // This prevents it from matching invalid syntax like {{u^2}}. |
| 1456 | re := regexp.MustCompile(`\{\{\s*(?:string\s+|int\s+|float\s+)?\.[^{}]*?\}\}`) |
| 1457 | |
| 1458 | funcMap := template.FuncMap{ |
| 1459 | "int": ToInt, |
| 1460 | "string": ToString, |
| 1461 | "float": ToFloat, |
| 1462 | } |
| 1463 | |
| 1464 | var firstErr error |
| 1465 | |
| 1466 | // Check if the entire input string is a single template placeholder. |
| 1467 | // This is more efficient than using MatchString and FindString separately. |
| 1468 | matchIndexes := re.FindStringIndex(input) |
| 1469 | isExactPlaceholderMatch := matchIndexes != nil && matchIndexes[0] == 0 && matchIndexes[1] == len(input) |
| 1470 | |
| 1471 | result := re.ReplaceAllStringFunc(input, func(match string) string { |
| 1472 | // Only parse and execute the matched placeholder, not the entire string. |
| 1473 | tmpl, err := template.New("sub").Funcs(funcMap).Parse(match) |
| 1474 | if err != nil { |
| 1475 | |
| 1476 | logger.Debug("failed to parse a valid-looking template placeholder", zap.String("placeholder", match), zap.Error(err)) |
| 1477 | return match |
| 1478 | } |
| 1479 | |
| 1480 | var output bytes.Buffer |
| 1481 | err = tmpl.Execute(&output, templateData) |
| 1482 | if err != nil { |
| 1483 | if firstErr == nil { |
| 1484 | firstErr = fmt.Errorf("failed to execute template placeholder '%s': %v", match, err) |
| 1485 | } |
| 1486 | return match |
| 1487 | } |
| 1488 | |
| 1489 | if isExactPlaceholderMatch { |
| 1490 | return output.String() |
| 1491 | } |
| 1492 | |
| 1493 | // Ensure the placeholder result is safe to embed inside JSON strings by |
| 1494 | // escaping control characters like newlines, quotes, and backslashes. |
| 1495 | // This prevents errors such as `invalid character '\n' in string literal` |
| 1496 | // when we later json.Unmarshal the rendered testcase. |
| 1497 | return jsonEscapeString(logger, output.String()) |
| 1498 | }) |
| 1499 | |
| 1500 | return result, firstErr |
| 1501 | } |
| 1502 | |
| 1503 | // jsonEscapeString returns a JSON-safe representation of s suitable for |
| 1504 | // embedding directly inside a JSON string literal. It escapes characters |
no test coverage detected