From 985fa8ce2f020f5ba961a28703f406e2225e1ba3 Mon Sep 17 00:00:00 2001 From: nenzen Date: Tue, 12 May 2026 11:38:18 +0200 Subject: [PATCH] Support non-JSON content types and fix multi-placeholder replacement --- .../controller/MockRequestInterceptor.java | 16 ++++- .../su/apimposter/service/MockService.java | 68 +++++++++++-------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/src/main/java/dsv/su/apimposter/controller/MockRequestInterceptor.java b/src/main/java/dsv/su/apimposter/controller/MockRequestInterceptor.java index 88b6f74..1f929a0 100644 --- a/src/main/java/dsv/su/apimposter/controller/MockRequestInterceptor.java +++ b/src/main/java/dsv/su/apimposter/controller/MockRequestInterceptor.java @@ -135,11 +135,21 @@ public class MockRequestInterceptor implements HandlerInterceptor { // Replace placeholders in the mock response body with path/global values Object body = mockService.replacePlaceholders(selected.body(), pathVars); - // Write the mock response: headers, status, and JSON body + // Write the mock response: headers, status, and body selected.headers().forEach(response::setHeader); response.setStatus(selected.status()); - response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.getWriter().write(mockService.toJson(body)); + + // Respect Content-Type from YAML headers; default to application/json + String contentType = selected.headers().getOrDefault("Content-Type", + MediaType.APPLICATION_JSON_VALUE); + response.setContentType(contentType); + + // For non-JSON content types with a string body, write the raw string directly + if (!contentType.contains("json") && body instanceof String rawBody) { + response.getWriter().write(rawBody); + } else { + response.getWriter().write(mockService.toJson(body)); + } return false; // Request has been fully handled } diff --git a/src/main/java/dsv/su/apimposter/service/MockService.java b/src/main/java/dsv/su/apimposter/service/MockService.java index 3b5a66b..5b614af 100644 --- a/src/main/java/dsv/su/apimposter/service/MockService.java +++ b/src/main/java/dsv/su/apimposter/service/MockService.java @@ -215,28 +215,24 @@ public class MockService { String result = input; // Process datetime with offset (e.g., {datetime+2d4h30m} or {datetime+2d4h30m:rfc3339}) - Matcher dateTimeOffsetMatcher = DATETIME_OFFSET_PATTERN.matcher(result); - if (dateTimeOffsetMatcher.find()) { - String sign = dateTimeOffsetMatcher.group("sign"); - String daysStr = dateTimeOffsetMatcher.group("days"); - String hoursStr = dateTimeOffsetMatcher.group("hours"); - String minutesStr = dateTimeOffsetMatcher.group("minutes"); - String format = dateTimeOffsetMatcher.group("format"); + result = replaceAllMatches(result, DATETIME_OFFSET_PATTERN, matcher -> { + String sign = matcher.group("sign"); + String daysStr = matcher.group("days"); + String hoursStr = matcher.group("hours"); + String minutesStr = matcher.group("minutes"); + String format = matcher.group("format"); LocalDateTime now = LocalDateTime.now(); LocalDateTime target = calculateDateTimeWithOffset(now, sign, daysStr, hoursStr, minutesStr); - - String replacement = formatDateTime(target, format); - result = dateTimeOffsetMatcher.replaceFirst(replacement); - } + return formatDateTime(target, format); + }); // Process datetime with days at specific time (e.g., {datetime+2d@13:00} or {datetime+2d@13:00:rfc3339}) - Matcher dateTimeAtTimeMatcher = DATETIME_AT_TIME_PATTERN.matcher(result); - if (dateTimeAtTimeMatcher.find()) { - String sign = dateTimeAtTimeMatcher.group("sign"); - int days = Integer.parseInt(dateTimeAtTimeMatcher.group("days")); - String timeStr = dateTimeAtTimeMatcher.group("time"); - String format = dateTimeAtTimeMatcher.group("format"); + result = replaceAllMatches(result, DATETIME_AT_TIME_PATTERN, matcher -> { + String sign = matcher.group("sign"); + int days = Integer.parseInt(matcher.group("days")); + String timeStr = matcher.group("time"); + String format = matcher.group("format"); LocalDate targetDate = LocalDate.now(); if ("+".equals(sign)) { @@ -247,17 +243,14 @@ public class MockService { LocalTime time = LocalTime.parse(timeStr); LocalDateTime target = LocalDateTime.of(targetDate, time); - - String replacement = formatDateTime(target, format); - result = dateTimeAtTimeMatcher.replaceFirst(replacement); - } + return formatDateTime(target, format); + }); // Process date with offset (e.g., {date-4d} or {date-4d:MMM dd, yyyy}) - Matcher dateOffsetMatcher = DATE_OFFSET_PATTERN.matcher(result); - if (dateOffsetMatcher.find()) { - String sign = dateOffsetMatcher.group("sign"); - int days = Integer.parseInt(dateOffsetMatcher.group("days")); - String format = dateOffsetMatcher.group("format"); + result = replaceAllMatches(result, DATE_OFFSET_PATTERN, matcher -> { + String sign = matcher.group("sign"); + int days = Integer.parseInt(matcher.group("days")); + String format = matcher.group("format"); LocalDate target = LocalDate.now(); if ("+".equals(sign)) { @@ -265,14 +258,29 @@ public class MockService { } else { target = target.minusDays(days); } - - String replacement = formatDate(target, format); - result = dateOffsetMatcher.replaceFirst(replacement); - } + return formatDate(target, format); + }); return result; } + /** + * Replaces all matches of a pattern in the input string, using a function + * to compute the replacement for each match. + */ + private String replaceAllMatches(String input, Pattern pattern, + java.util.function.Function replacer) + { + Matcher matcher = pattern.matcher(input); + StringBuilder sb = new StringBuilder(); + while (matcher.find()) { + String replacement = replacer.apply(matcher); + matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement)); + } + matcher.appendTail(sb); + return sb.toString(); + } + /** * Calculates a LocalDateTime with the given offset components. * @param base the base datetime to start from -- 2.39.5