From d18914e699e2ec88c563adf081aa7512955072e2 Mon Sep 17 00:00:00 2001 From: nenzen Date: Wed, 10 Sep 2025 13:50:17 +0200 Subject: [PATCH 1/5] Add relative date and time --- README.md | 60 ++++++ .../su/apimposter/service/MockService.java | 179 ++++++++++++++++++ 2 files changed, 239 insertions(+) diff --git a/README.md b/README.md index d8aa966..5daf0f3 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,66 @@ You can include optional type hints in placeholders to ensure JSON values are re If parsing fails (e.g. `abc` for an `int`), APImposter **falls back to the original string**. +### Relative Date/Time Placeholders + +APImposter supports dynamic date and time placeholders that are calculated relative to the current time. This is perfect for testing calendar applications, scheduling systems, or any time-sensitive mock data. + +#### Supported formats + +| Format | Example | Description | +|--------|---------|-------------| +| `{datetime+/-offset}` | `{datetime+2d4h30m}` | DateTime with flexible time offset | +| `{datetime+/-Nd@HH:MM}` | `{datetime+1d@13:30}` | DateTime N days from now at specific time | +| `{date+/-Nd}` | `{date-7d}` | Date N days from now | + +#### Format specifiers (optional) + +Add a format specifier after a colon to control the output format: + +| Specifier | Description | Example Output | +|-----------|-------------|----------------| +| `iso` (default) | ISO standard format | `2025-09-03T15:30:00` | +| `rfc3339` | RFC 3339 with UTC timezone | `2025-09-03T15:30:00Z` | +| Custom pattern | Any valid DateTimeFormatter pattern | `Sep 03, 2025` | + +#### Examples + +```yaml +# Basic relative dates (ISO format) +- method: GET + path: "/events" + response: + body: + todayMeeting: "{datetime+0d@08:00}" # Today at 8:00 AM + tomorrowLunch: "{datetime+1d@13:00}" # Tomorrow at 1:00 PM + nextWeek: "{date+7d}" # Date 7 days from now + pastEvent: "{datetime-2d4h30m}" # 2 days, 4h, 30m ago + yesterday: "{date-1d}" # Yesterday's date + +# Different output formats +- method: GET + path: "/calendar" + response: + body: + # RFC3339 format + eventTimeRfc: "{datetime+1d@15:30:rfc3339}" # "2025-09-03T15:30:00Z" + + # Custom formats + eventDate: "{date+1d:MMM dd, yyyy}" # "Sep 03, 2025" + timeOnly: "{datetime+0d@14:30:HH:mm}" # "14:30" + shortDate: "{date+7d:MM/dd/yy}" # "09/09/25" +``` + +#### Time components for offset format + +- `d` = days +- `h` = hours +- `m` = minutes + +All components are optional and can be combined: `{datetime+1d2h}`, `{datetime+30m}`, `{datetime-4h15m}` + +**Note:** If a custom format is invalid, the system automatically falls back to ISO format to prevent errors. + --- ## Example Requests and Responses diff --git a/src/main/java/dsv/su/apimposter/service/MockService.java b/src/main/java/dsv/su/apimposter/service/MockService.java index cec8528..4aacec8 100644 --- a/src/main/java/dsv/su/apimposter/service/MockService.java +++ b/src/main/java/dsv/su/apimposter/service/MockService.java @@ -1,5 +1,10 @@ package dsv.su.apimposter.service; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -32,6 +37,11 @@ public class MockService { private final AntPathMatcher pathMatcher = new AntPathMatcher(); private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{([\\w\\.]+)(?::(int|float|bool))?}"); + // Patterns for relative date/time placeholders with optional format specifier + private static final Pattern DATETIME_OFFSET_PATTERN = Pattern.compile("\\{datetime([+-])(\\d+d)?(\\d+h)?(\\d+m)?(?::(\\w+|[^}]+))?}"); + private static final Pattern DATETIME_AT_TIME_PATTERN = Pattern.compile("\\{datetime([+-])(\\d+)d@(\\d{1,2}:\\d{2})(?::(\\w+|[^}]+))?}"); + private static final Pattern DATE_OFFSET_PATTERN = Pattern.compile("\\{date([+-])(\\d+)d(?::(\\w+|[^}]+))?}"); + /** * Initializes the mock service with a reference to the file loader, * which holds the live endpoint and global placeholder data. @@ -142,6 +152,12 @@ public class MockService { .toList(); } else if (body instanceof String str) { + // Check for relative date/time placeholders first + String dateTimeResult = processDateTimePlaceholders(str); + if (!dateTimeResult.equals(str)) { + return dateTimeResult; // Date/time placeholder was processed + } + Matcher matcher = PLACEHOLDER_PATTERN.matcher(str); // If the string is a single placeholder (e.g. "{id:int}" or "{id}"), resolve and return its value if (matcher.matches()) { @@ -167,6 +183,169 @@ public class MockService { return body; } + /** + * Processes relative date/time placeholders in a string. + * Supports three formats with optional format specifiers: + * - {datetime+2d4h30m[:format]} - datetime now +/- time offset + * - {datetime+2d@13:00[:format]} - datetime now +/- days at specific time + * - {date-4d[:format]} - date now +/- days + * + * Format specifiers: + * - iso (default) - ISO standard format + * - rfc3339 - RFC 3339 format with UTC timezone + * - custom pattern - Valid DateTimeFormatter pattern + * + * Examples: + * - {datetime+1d} - "2025-09-03T14:30:00" + * - {datetime+1d:rfc3339} - "2025-09-03T14:30:00Z" + * - {date+1d:MMM dd, yyyy} - "Sep 03, 2025" + * + * @param input the input string that may contain date/time placeholders + * @return the string with date/time placeholders replaced, or original string if no placeholders found + */ + private String processDateTimePlaceholders(String input) { + 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(1); + String daysStr = dateTimeOffsetMatcher.group(2); + String hoursStr = dateTimeOffsetMatcher.group(3); + String minutesStr = dateTimeOffsetMatcher.group(4); + String format = dateTimeOffsetMatcher.group(5); + + LocalDateTime now = LocalDateTime.now(); + LocalDateTime target = calculateDateTimeWithOffset(now, sign, daysStr, hoursStr, minutesStr); + + String replacement = formatDateTime(target, format); + result = dateTimeOffsetMatcher.replaceFirst(replacement); + } + + // 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(1); + int days = Integer.parseInt(dateTimeAtTimeMatcher.group(2)); + String timeStr = dateTimeAtTimeMatcher.group(3); + String format = dateTimeAtTimeMatcher.group(4); + + LocalDate targetDate = LocalDate.now(); + if ("+".equals(sign)) { + targetDate = targetDate.plusDays(days); + } else { + targetDate = targetDate.minusDays(days); + } + + LocalTime time = LocalTime.parse(timeStr); + LocalDateTime target = LocalDateTime.of(targetDate, time); + + String replacement = formatDateTime(target, format); + result = dateTimeAtTimeMatcher.replaceFirst(replacement); + } + + // 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(1); + int days = Integer.parseInt(dateOffsetMatcher.group(2)); + String format = dateOffsetMatcher.group(3); + + LocalDate target = LocalDate.now(); + if ("+".equals(sign)) { + target = target.plusDays(days); + } else { + target = target.minusDays(days); + } + + String replacement = formatDate(target, format); + result = dateOffsetMatcher.replaceFirst(replacement); + } + + return result; + } + + /** + * Calculates a LocalDateTime with the given offset components. + * @param base the base datetime to start from + * @param sign "+" or "-" to indicate direction + * @param daysStr days component (e.g., "2d") + * @param hoursStr hours component (e.g., "4h") + * @param minutesStr minutes component (e.g., "30m") + * @return the calculated LocalDateTime + */ + private LocalDateTime calculateDateTimeWithOffset(LocalDateTime base, String sign, String daysStr, String hoursStr, String minutesStr) { + LocalDateTime result = base; + + int multiplier = "+".equals(sign) ? 1 : -1; + + if (daysStr != null) { + int days = Integer.parseInt(daysStr.replace("d", "")); + result = result.plusDays(days * multiplier); + } + + if (hoursStr != null) { + int hours = Integer.parseInt(hoursStr.replace("h", "")); + result = result.plusHours(hours * multiplier); + } + + if (minutesStr != null) { + int minutes = Integer.parseInt(minutesStr.replace("m", "")); + result = result.plusMinutes(minutes * multiplier); + } + + return result; + } + + /** + * Formats a LocalDateTime according to the specified format. + * Supported formats: + * - null or "iso" - ISO_LOCAL_DATE_TIME (default) + * - "rfc3339" - RFC 3339 format with UTC timezone + * - custom pattern - Any valid DateTimeFormatter pattern + * @param dateTime the LocalDateTime to format + * @param format the format specifier + * @return formatted date/time string + */ + private String formatDateTime(LocalDateTime dateTime, String format) { + if (format == null || "iso".equals(format)) { + return dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + + try { + return switch (format) { + case "rfc3339" -> dateTime.atZone(ZoneOffset.UTC).format(DateTimeFormatter.ISO_INSTANT); + default -> dateTime.format(DateTimeFormatter.ofPattern(format)); + }; + } catch (Exception e) { + // If format is invalid, fallback to ISO format + return dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + } + + /** + * Formats a LocalDate according to the specified format. + * Supported formats: + * - null or "iso" - ISO_LOCAL_DATE (default) + * - "rfc3339" - ISO_LOCAL_DATE (same as ISO for date-only) + * - custom pattern - Any valid DateTimeFormatter pattern + * @param date the LocalDate to format + * @param format the format specifier + * @return formatted date string + */ + private String formatDate(LocalDate date, String format) { + if (format == null || "iso".equals(format) || "rfc3339".equals(format)) { + return date.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + try { + return date.format(DateTimeFormatter.ofPattern(format)); + } catch (Exception e) { + // If format is invalid, fallback to ISO format + return date.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + } + /** * Attempts to convert a raw string into a typed value if a type hint is provided. * Supported types: -- 2.39.5 From ba6817390c15b6bda87a2f37b3184f611efb87d2 Mon Sep 17 00:00:00 2001 From: nenzen Date: Wed, 10 Sep 2025 13:55:44 +0200 Subject: [PATCH 2/5] Fix checkstyle errors --- .../su/apimposter/service/MockService.java | 49 ++++++++++++------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/src/main/java/dsv/su/apimposter/service/MockService.java b/src/main/java/dsv/su/apimposter/service/MockService.java index 4aacec8..939943d 100644 --- a/src/main/java/dsv/su/apimposter/service/MockService.java +++ b/src/main/java/dsv/su/apimposter/service/MockService.java @@ -38,10 +38,19 @@ public class MockService { private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{([\\w\\.]+)(?::(int|float|bool))?}"); // Patterns for relative date/time placeholders with optional format specifier - private static final Pattern DATETIME_OFFSET_PATTERN = Pattern.compile("\\{datetime([+-])(\\d+d)?(\\d+h)?(\\d+m)?(?::(\\w+|[^}]+))?}"); - private static final Pattern DATETIME_AT_TIME_PATTERN = Pattern.compile("\\{datetime([+-])(\\d+)d@(\\d{1,2}:\\d{2})(?::(\\w+|[^}]+))?}"); + private static final Pattern DATETIME_OFFSET_PATTERN = + Pattern.compile("\\{datetime([+-])(\\d+d)?(\\d+h)?(\\d+m)?(?::(\\w+|[^}]+))?}"); + private static final Pattern DATETIME_AT_TIME_PATTERN = + Pattern.compile("\\{datetime([+-])(\\d+)d@(\\d{1,2}:\\d{2})(?::(\\w+|[^}]+))?}"); private static final Pattern DATE_OFFSET_PATTERN = Pattern.compile("\\{date([+-])(\\d+)d(?::(\\w+|[^}]+))?}"); + private static final int GROUP_ONE = 1; + private static final int GROUP_TWO = 2; + private static final int GROUP_THREE = 3; + private static final int GROUP_FOUR = 4; + private static final int GROUP_FIVE = 5; + + /** * Initializes the mock service with a reference to the file loader, * which holds the live endpoint and global placeholder data. @@ -161,8 +170,8 @@ public class MockService { Matcher matcher = PLACEHOLDER_PATTERN.matcher(str); // If the string is a single placeholder (e.g. "{id:int}" or "{id}"), resolve and return its value if (matcher.matches()) { - String key = matcher.group(1); - String type = matcher.group(2); + String key = matcher.group(GROUP_ONE); + String type = matcher.group(GROUP_TWO); String raw = resolveValue(key, pathVariables, globals); return convertWithType(raw, type); } @@ -170,8 +179,8 @@ public class MockService { // Otherwise: perform inline replacement for all placeholders in the string StringBuilder sb = new StringBuilder(); while (matcher.find()) { - String key = matcher.group(1); - String type = matcher.group(2); + String key = matcher.group(GROUP_ONE); + String type = matcher.group(GROUP_TWO); String raw = resolveValue(key, pathVariables, globals); Object replacement = convertWithType(raw, type); matcher.appendReplacement(sb, Matcher.quoteReplacement(String.valueOf(replacement))); @@ -209,11 +218,11 @@ public class MockService { // 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(1); - String daysStr = dateTimeOffsetMatcher.group(2); - String hoursStr = dateTimeOffsetMatcher.group(3); - String minutesStr = dateTimeOffsetMatcher.group(4); - String format = dateTimeOffsetMatcher.group(5); + String sign = dateTimeOffsetMatcher.group(GROUP_ONE); + String daysStr = dateTimeOffsetMatcher.group(GROUP_TWO); + String hoursStr = dateTimeOffsetMatcher.group(GROUP_THREE); + String minutesStr = dateTimeOffsetMatcher.group(GROUP_FOUR); + String format = dateTimeOffsetMatcher.group(GROUP_FIVE); LocalDateTime now = LocalDateTime.now(); LocalDateTime target = calculateDateTimeWithOffset(now, sign, daysStr, hoursStr, minutesStr); @@ -225,10 +234,10 @@ public class MockService { // 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(1); - int days = Integer.parseInt(dateTimeAtTimeMatcher.group(2)); - String timeStr = dateTimeAtTimeMatcher.group(3); - String format = dateTimeAtTimeMatcher.group(4); + String sign = dateTimeAtTimeMatcher.group(GROUP_ONE); + int days = Integer.parseInt(dateTimeAtTimeMatcher.group(GROUP_TWO)); + String timeStr = dateTimeAtTimeMatcher.group(GROUP_THREE); + String format = dateTimeAtTimeMatcher.group(GROUP_FOUR); LocalDate targetDate = LocalDate.now(); if ("+".equals(sign)) { @@ -247,9 +256,9 @@ public class MockService { // 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(1); - int days = Integer.parseInt(dateOffsetMatcher.group(2)); - String format = dateOffsetMatcher.group(3); + String sign = dateOffsetMatcher.group(GROUP_ONE); + int days = Integer.parseInt(dateOffsetMatcher.group(GROUP_TWO)); + String format = dateOffsetMatcher.group(GROUP_THREE); LocalDate target = LocalDate.now(); if ("+".equals(sign)) { @@ -274,7 +283,9 @@ public class MockService { * @param minutesStr minutes component (e.g., "30m") * @return the calculated LocalDateTime */ - private LocalDateTime calculateDateTimeWithOffset(LocalDateTime base, String sign, String daysStr, String hoursStr, String minutesStr) { + private LocalDateTime calculateDateTimeWithOffset(LocalDateTime base, String sign, String daysStr, + String hoursStr, String minutesStr) + { LocalDateTime result = base; int multiplier = "+".equals(sign) ? 1 : -1; -- 2.39.5 From e75826acca49ca71d2877d9afa718bd253ea6334 Mon Sep 17 00:00:00 2001 From: nenzen Date: Mon, 3 Nov 2025 23:20:08 +0100 Subject: [PATCH 3/5] Use more descriptive group constants --- .../su/apimposter/service/MockService.java | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/main/java/dsv/su/apimposter/service/MockService.java b/src/main/java/dsv/su/apimposter/service/MockService.java index 939943d..7d4bf5d 100644 --- a/src/main/java/dsv/su/apimposter/service/MockService.java +++ b/src/main/java/dsv/su/apimposter/service/MockService.java @@ -44,11 +44,27 @@ public class MockService { Pattern.compile("\\{datetime([+-])(\\d+)d@(\\d{1,2}:\\d{2})(?::(\\w+|[^}]+))?}"); private static final Pattern DATE_OFFSET_PATTERN = Pattern.compile("\\{date([+-])(\\d+)d(?::(\\w+|[^}]+))?}"); - private static final int GROUP_ONE = 1; - private static final int GROUP_TWO = 2; - private static final int GROUP_THREE = 3; - private static final int GROUP_FOUR = 4; - private static final int GROUP_FIVE = 5; + // Placeholder pattern groups + private static final int PLACEHOLDER_KEY = 1; + private static final int PLACEHOLDER_TYPE = 2; + + // Date/time pattern groups + private static final int DATETIME_SIGN = 1; + private static final int DATETIME_DAYS = 2; + private static final int DATETIME_HOURS = 3; + private static final int DATETIME_MINUTES = 4; + private static final int DATETIME_FORMAT = 5; + + // Date/time at specific time pattern groups + private static final int AT_TIME_SIGN = 1; + private static final int AT_TIME_DAYS = 2; + private static final int AT_TIME_TIME = 3; + private static final int AT_TIME_FORMAT = 4; + + // Date offset pattern groups + private static final int DATE_SIGN = 1; + private static final int DATE_DAYS = 2; + private static final int DATE_FORMAT = 3; /** @@ -170,8 +186,8 @@ public class MockService { Matcher matcher = PLACEHOLDER_PATTERN.matcher(str); // If the string is a single placeholder (e.g. "{id:int}" or "{id}"), resolve and return its value if (matcher.matches()) { - String key = matcher.group(GROUP_ONE); - String type = matcher.group(GROUP_TWO); + String key = matcher.group(PLACEHOLDER_KEY); + String type = matcher.group(PLACEHOLDER_TYPE); String raw = resolveValue(key, pathVariables, globals); return convertWithType(raw, type); } @@ -179,8 +195,8 @@ public class MockService { // Otherwise: perform inline replacement for all placeholders in the string StringBuilder sb = new StringBuilder(); while (matcher.find()) { - String key = matcher.group(GROUP_ONE); - String type = matcher.group(GROUP_TWO); + String key = matcher.group(PLACEHOLDER_KEY); + String type = matcher.group(PLACEHOLDER_TYPE); String raw = resolveValue(key, pathVariables, globals); Object replacement = convertWithType(raw, type); matcher.appendReplacement(sb, Matcher.quoteReplacement(String.valueOf(replacement))); @@ -218,11 +234,11 @@ public class MockService { // 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(GROUP_ONE); - String daysStr = dateTimeOffsetMatcher.group(GROUP_TWO); - String hoursStr = dateTimeOffsetMatcher.group(GROUP_THREE); - String minutesStr = dateTimeOffsetMatcher.group(GROUP_FOUR); - String format = dateTimeOffsetMatcher.group(GROUP_FIVE); + String sign = dateTimeOffsetMatcher.group(DATETIME_SIGN); + String daysStr = dateTimeOffsetMatcher.group(DATETIME_DAYS); + String hoursStr = dateTimeOffsetMatcher.group(DATETIME_HOURS); + String minutesStr = dateTimeOffsetMatcher.group(DATETIME_MINUTES); + String format = dateTimeOffsetMatcher.group(DATETIME_FORMAT); LocalDateTime now = LocalDateTime.now(); LocalDateTime target = calculateDateTimeWithOffset(now, sign, daysStr, hoursStr, minutesStr); @@ -234,10 +250,10 @@ public class MockService { // 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(GROUP_ONE); - int days = Integer.parseInt(dateTimeAtTimeMatcher.group(GROUP_TWO)); - String timeStr = dateTimeAtTimeMatcher.group(GROUP_THREE); - String format = dateTimeAtTimeMatcher.group(GROUP_FOUR); + String sign = dateTimeAtTimeMatcher.group(AT_TIME_SIGN); + int days = Integer.parseInt(dateTimeAtTimeMatcher.group(AT_TIME_DAYS)); + String timeStr = dateTimeAtTimeMatcher.group(AT_TIME_TIME); + String format = dateTimeAtTimeMatcher.group(AT_TIME_FORMAT); LocalDate targetDate = LocalDate.now(); if ("+".equals(sign)) { @@ -256,9 +272,9 @@ public class MockService { // 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(GROUP_ONE); - int days = Integer.parseInt(dateOffsetMatcher.group(GROUP_TWO)); - String format = dateOffsetMatcher.group(GROUP_THREE); + String sign = dateOffsetMatcher.group(DATE_SIGN); + int days = Integer.parseInt(dateOffsetMatcher.group(DATE_DAYS)); + String format = dateOffsetMatcher.group(DATE_FORMAT); LocalDate target = LocalDate.now(); if ("+".equals(sign)) { -- 2.39.5 From 52a3201d3a31ab6f837fd3c678f87e4fb9fcef53 Mon Sep 17 00:00:00 2001 From: nenzen Date: Mon, 3 Nov 2025 23:35:45 +0100 Subject: [PATCH 4/5] Use capture group names, https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/util/regex/Pattern.html\#groupname --- .../su/apimposter/service/MockService.java | 67 +++++++------------ 1 file changed, 25 insertions(+), 42 deletions(-) diff --git a/src/main/java/dsv/su/apimposter/service/MockService.java b/src/main/java/dsv/su/apimposter/service/MockService.java index 7d4bf5d..3b5a66b 100644 --- a/src/main/java/dsv/su/apimposter/service/MockService.java +++ b/src/main/java/dsv/su/apimposter/service/MockService.java @@ -35,36 +35,19 @@ public class MockService { private final MockFileLoader loader; private final AntPathMatcher pathMatcher = new AntPathMatcher(); - private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{([\\w\\.]+)(?::(int|float|bool))?}"); + // Patterns using named capture groups (Java 21+) + private static final Pattern PLACEHOLDER_PATTERN = + Pattern.compile("\\{(?[\\w\\.]+)(?::(?int|float|bool))?}"); // Patterns for relative date/time placeholders with optional format specifier private static final Pattern DATETIME_OFFSET_PATTERN = - Pattern.compile("\\{datetime([+-])(\\d+d)?(\\d+h)?(\\d+m)?(?::(\\w+|[^}]+))?}"); + Pattern.compile("\\{datetime(?[+-])(?\\d+d)?(?\\d+h)?" + + "(?\\d+m)?(?::(?\\w+|[^}]+))?}"); private static final Pattern DATETIME_AT_TIME_PATTERN = - Pattern.compile("\\{datetime([+-])(\\d+)d@(\\d{1,2}:\\d{2})(?::(\\w+|[^}]+))?}"); - private static final Pattern DATE_OFFSET_PATTERN = Pattern.compile("\\{date([+-])(\\d+)d(?::(\\w+|[^}]+))?}"); - - // Placeholder pattern groups - private static final int PLACEHOLDER_KEY = 1; - private static final int PLACEHOLDER_TYPE = 2; - - // Date/time pattern groups - private static final int DATETIME_SIGN = 1; - private static final int DATETIME_DAYS = 2; - private static final int DATETIME_HOURS = 3; - private static final int DATETIME_MINUTES = 4; - private static final int DATETIME_FORMAT = 5; - - // Date/time at specific time pattern groups - private static final int AT_TIME_SIGN = 1; - private static final int AT_TIME_DAYS = 2; - private static final int AT_TIME_TIME = 3; - private static final int AT_TIME_FORMAT = 4; - - // Date offset pattern groups - private static final int DATE_SIGN = 1; - private static final int DATE_DAYS = 2; - private static final int DATE_FORMAT = 3; + Pattern.compile("\\{datetime(?[+-])(?\\d+)d@(?