Issue
I'm migrating some routes from a jax-rs based application to SpringBoot. In jax-rs I can use @Path to define a regex that contains multiple URL path elements:
@Path("{id:[^/]+/y[\d]{4}/m[\d]{1,2}/d[\d]{1,2}/h[\d]{1,2}}/")
The id variable in the method body will then be the matching segment of the URL and I can go about my day.
With @RequestMapping in Spring this doesn't work. As soon as you put a forward slash into the regex you get a PatternParseException.
PathContainer pathContainingSlash = PathContainer.parsePath("/api/test/y1978/m07/d15");
PathPatternParser parser = new PathPatternParser();
assertThrows(PatternParseException.class, () ->
parser.parse("/api/test/{ticketId:y[\\d]{4}/m[\\d]{1,2}/d[\\d]{1,2}}"));
This same problem appears to happen with AntPathMatcher.
AntPathMatcher antPathMatcher = new AntPathMatcher();
assertThrows(IllegalStateException.class, () ->
antPathMatcher.extractUriTemplateVariables(
"/api/test/{ticketId:y[\\d]{4}/m[\\d]{1,2}/d[\\d]{1,2}}",
"/api/test/y1978/m07/d15"));
This is a problem because I have about 78 of these URL patterns. I'm going to have to define each pattern individually with each path element being a separate variable. Then I'm going to have to use String concatenation to combine them back together in the format of a path.
@GetMapping("/{year:y[\\d]{4}}/{month:m[\\d]1,2}/{day:d[\\d]{1,2}")
public ResponseEntity<Void> foo(@PathVariable String year,
@PathVariable String month,
@PathVariable String day) {
String date = year + "/" + month + "/" + day;
}
Other than using Jax-rs in my SpringBoot app, is there accomplish this? It's possible to write them all like this but it seems sub-optimal.
For clarity, I really want a way to extract multiple path elements from a URL into a @PathVariable. I would like something like this:
@GetMapping("/api/test/{date:y[\\d]{4}/m[\\d]{1,2}/d[\\d]{1,2}}")
public ResponseEntity<Void> foo(@PathVariable String date) {}
So that date is now equal to y1978/m07/d15
Also, this is just one example pattern. There are 78 unique patterns, that have a varying number of a path elements and contents of the elements. In Jax-RS using @Path I can OR these regexes together and create one route and the path variable is accessible inside the method.
Solution
how about adding spring-boot-starter-validation
for validation
requires addition of following jar
org.springframework.boot:spring-boot-starter-validation
add
@org.springframework.validation.annotation.Validated
on top of controller classadd
@javax.validation.constraints.Pattern
withregex
attribute to the@PathVariable
method params
@GetMapping("{year}/{month}/{day}")
public ResponseEntity<Void> foo(
@PathVariable @Pattern(regexp = "[\\d]{4}", message = "year must be ..") String year,
@PathVariable @Pattern(regexp = "[\\d]{1,2}", message = "month must ..") String month,
@PathVariable @Pattern(regexp = "[\\d]{1,2}", message= "day must be ..") String day) {
String date = year + "/" + month + "/" + day;
- to return http 400 status, add a method to handle the ConstraintViolationException
@ExceptionHandler(value = { ConstraintViolationException.class })
protected ResponseEntity<List<String>> handleConstraintViolations(ConstraintViolationException ex, WebRequest request) {
List<String> errorMessages = ex.getConstraintViolations().stream()
.map(violation -> violation.getMessage()).collect(Collectors.toList());
return new ResponseEntity<List<String>>(errorMessages, HttpStatus.BAD_REQUEST);
}
more validation examples here: https://reflectoring.io/bean-validation-with-spring-boot/
more exception handling options here: https://www.baeldung.com/exception-handling-for-rest-with-spring
Answered By - indybee
Answer Checked By - Willingham (JavaFixing Volunteer)