Repository: quarkusio/quarkus
Issue: RESTEasy reactive illegally restricts JAX-RSpath parameter names

### Describe the bug

Consider the following JAX-RS resource:
```
@Produces(MediaType.APPLICATION_JSON)
@Path("/")
public class ContextNotWorking {
    @GET
    @Path("foo/{something-with-dash:[^a]*}")
	public Response doSomething(@PathParam("something-with-dash") String param) {
	    return Response.noContent().build();
	}
}
```
Note that path parameter _something-with-dash_. According to the Javadoc of @Path the dash is an allowed character in a parameter name. However, if you try to run this with resteasy-reactive startup fails with
```
Caused by: java.lang.IllegalArgumentException: Path '/foo/{something-with-dash:[^a]*}' of method 'org.knime.xxx.ContextNotWorking#doSomething' is not a valid expression
	at org.jboss.resteasy.reactive.server.processor.ServerEndpointIndexer.validateMethodPath(ServerEndpointIndexer.java:226)
	at org.jboss.resteasy.reactive.server.processor.ServerEndpointIndexer.handleAdditionalMethodProcessing(ServerEndpointIndexer.java:218)
	at io.quarkus.resteasy.reactive.server.deployment.QuarkusServerEndpointIndexer.handleAdditionalMethodProcessing(QuarkusServerEndpointIndexer.java:164)
	at io.quarkus.resteasy.reactive.server.deployment.QuarkusServerEndpointIndexer.handleAdditionalMethodProcessing(QuarkusServerEndpointIndexer.java:32)
	at org.jboss.resteasy.reactive.common.processor.EndpointIndexer.createResourceMethod(EndpointIndexer.java:657)
	... 14 more
Caused by: java.util.regex.PatternSyntaxException: named capturing group is missing trailing '>' near index 12
(?<something-with-dash>[^a]*)$
            ^
	at java.base/java.util.regex.Pattern.error(Pattern.java:2027)
	at java.base/java.util.regex.Pattern.groupname(Pattern.java:2949)
	at java.base/java.util.regex.Pattern.group0(Pattern.java:2995)
	at java.base/java.util.regex.Pattern.sequence(Pattern.java:2123)
	at java.base/java.util.regex.Pattern.expr(Pattern.java:2068)
	at java.base/java.util.regex.Pattern.compile(Pattern.java:1782)
	at java.base/java.util.regex.Pattern.<init>(Pattern.java:1428)
	at java.base/java.util.regex.Pattern.compile(Pattern.java:1068)
	at org.jboss.resteasy.reactive.server.mapping.URITemplate.<init>(URITemplate.java:157)
	at org.jboss.resteasy.reactive.server.processor.ServerEndpointIndexer.validateMethodPath(ServerEndpointIndexer.java:223)
	... 18 more
```
It seems he reactive extension does not comply with the JAX-RS specification.
The same class works fine with the non-reactive version of RESTEasy.
Patterns in Java indeed do *not* allow dashes in capture group names.

### Expected behavior

Code that is legal according to the JAX-RS specification should work in Quarkus.

### Actual behavior

Quarkus startup fails with the error above.

### How to Reproduce?

Try to run the provided JAX-RS resource class with _quarkus-resteasy-reactive_.

### Output of `uname -a` or `ver`

_No response_

### Output of `java -version`

17

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

2.8.2.Final

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

_No response_

### Additional information

_No response_

Changed files:
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/URITemplate.java
independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java
independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/RegexResource.java
independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java

diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
index a48cd45b766..18167fbeeb3 100644
--- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
+++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java
@@ -303,11 +303,12 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
     private String sanitizePath(String path) {
         // this simply replaces the whitespace characters (not part of a path variable) with %20
         // TODO: this might have to be more complex, URL encoding maybe?
-        boolean inVariable = false;
+        // zero braces indicates we are outside of a variable
+        int bracesCount = 0;
         StringBuilder replaced = null;
         for (int i = 0; i < path.length(); i++) {
             char c = path.charAt(i);
-            if ((c == ' ') && (!inVariable)) {
+            if ((c == ' ') && (bracesCount == 0)) {
                 if (replaced == null) {
                     replaced = new StringBuilder(path.length() + 2);
                     replaced.append(path, 0, i);
@@ -319,9 +320,9 @@ private String sanitizePath(String path) {
                 replaced.append(c);
             }
             if (c == '{') {
-                inVariable = true;
+                bracesCount++;
             } else if (c == '}') {
-                inVariable = false;
+                bracesCount--;
             }
         }
         if (replaced == null) {
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java
index 90d61fb86c7..d291f4ad179 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/RequestMapper.java
@@ -75,8 +75,8 @@ public RequestMatch<T> map(String path) {
                         break;
                     }
                     matchPos = matcher.end();
-                    for (String name : segment.names) {
-                        params[paramCount++] = URIDecoder.decodeURIComponent(matcher.group(name), false);
+                    for (String group : segment.groups) {
+                        params[paramCount++] = URIDecoder.decodeURIComponent(matcher.group(group), false);
                     }
                 } else if (segment.type == URITemplate.Type.LITERAL) {
                     //make sure the literal text is the same
diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/URITemplate.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/URITemplate.java
index f46fcab7f4c..09e45a3c97b 100644
--- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/URITemplate.java
+++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/mapping/URITemplate.java
@@ -8,6 +8,8 @@
 
 public class URITemplate implements Dumpable, Comparable<URITemplate> {
 
+    private static final Pattern GROUP_NAME_PATTERN = Pattern.compile("^[A-Za-z][A-Za-z\\\\d]*$");
+
     public final String template;
 
     public final String stem;
@@ -65,17 +67,19 @@ public URITemplate(String template, boolean prefixMatch) {
                         if (sb.length() > 0) {
                             capGroups++;
                             if (i + 1 == template.length() || template.charAt(i + 1) == '/') {
-                                components.add(new TemplateComponent(Type.DEFAULT_REGEX, null, sb.toString(), null, null));
+                                components
+                                        .add(new TemplateComponent(Type.DEFAULT_REGEX, null, sb.toString().trim(), null, null,
+                                                null));
                             } else {
-                                components.add(new TemplateComponent(Type.CUSTOM_REGEX, "[^/]+?", sb.toString(),
-                                        null, null));
+                                components.add(new TemplateComponent(Type.CUSTOM_REGEX, "[^/]+?", sb.toString().trim(),
+                                        null, null, null));
                             }
                         } else {
                             throw new IllegalArgumentException("Invalid template " + template);
                         }
                         sb.setLength(0);
                     } else if (c == ':') {
-                        name = sb.toString();
+                        name = sb.toString().trim();
                         sb.setLength(0);
                         state = 2;
                     } else {
@@ -89,8 +93,8 @@ public URITemplate(String template, boolean prefixMatch) {
                             capGroups++;
                             complexGroups++;
                             components
-                                    .add(new TemplateComponent(Type.CUSTOM_REGEX, sb.toString(), name, null,
-                                            null));
+                                    .add(new TemplateComponent(Type.CUSTOM_REGEX, sb.toString().trim(), name, null,
+                                            null, null));
                         } else {
                             throw new IllegalArgumentException("Invalid template " + template);
                         }
@@ -124,30 +128,43 @@ public URITemplate(String template, boolean prefixMatch) {
         //coalesce the components
         //once we have a CUSTOM_REGEX everything goes out the window, so we need to turn the remainder of the
         //template into a single CUSTOM_REGEX
+        List<String> groupAggregator = null;
         List<String> nameAggregator = null;
         StringBuilder regexAggregator = null;
         Iterator<TemplateComponent> it = components.iterator();
         while (it.hasNext()) {
             TemplateComponent component = it.next();
+
+            if (component.type == Type.CUSTOM_REGEX && nameAggregator == null) {
+                regexAggregator = new StringBuilder();
+                groupAggregator = new ArrayList<>();
+                nameAggregator = new ArrayList<>();
+            }
+
             if (nameAggregator != null) {
                 it.remove();
                 if (component.type == Type.LITERAL) {
                     regexAggregator.append(Pattern.quote(component.literalText));
-                } else if (component.type == Type.DEFAULT_REGEX) {
-                    regexAggregator.append("(?<").append(component.name).append(">[^/]+?)");
-                    nameAggregator.add(component.name);
-                } else if (component.type == Type.CUSTOM_REGEX) {
-                    regexAggregator.append("(?<").append(component.name).append(">").append(component.literalText.trim())
+                } else if (component.type == Type.DEFAULT_REGEX || component.type == Type.CUSTOM_REGEX) {
+                    String groupName = component.name;
+                    // test if the component name is a valid java groupname according to the rules outlined in java.util.Pattern#groupName
+                    // Paths allow for parameter names with characters not allowed in group names. Generate a custom one in the form group + running number when the component name alone would be invalid.
+                    if (!GROUP_NAME_PATTERN.matcher(component.name).matches()) {
+                        groupName = "group" + groupAggregator.size();
+                    }
+
+                    String regex = "[^/]+?";
+
+                    if (component.type == Type.CUSTOM_REGEX) {
+                        regex = component.literalText.trim();
+                    }
+
+                    groupAggregator.add(groupName + "");
+                    regexAggregator.append("(?<").append(groupName).append(">")
+                            .append(regex)
                             .append(")");
                     nameAggregator.add(component.name);
                 }
-            } else if (component.type == Type.CUSTOM_REGEX) {
-                it.remove();
-                regexAggregator = new StringBuilder();
-                nameAggregator = new ArrayList<>();
-                regexAggregator.append("(?<").append(component.name).append(">").append(component.literalText.trim())
-                        .append(")");
-                nameAggregator.add(component.name);
             }
         }
         if (nameAggregator != null) {
@@ -155,7 +172,7 @@ public URITemplate(String template, boolean prefixMatch) {
                 regexAggregator.append("$");
             }
             components.add(new TemplateComponent(Type.CUSTOM_REGEX, null, null, Pattern.compile(regexAggregator.toString()),
-                    nameAggregator.toArray(new String[0])));
+                    nameAggregator.toArray(new String[0]), groupAggregator.toArray(new String[0])));
         }
         this.stem = stem;
         this.literalCharacterCount = litChars;
@@ -174,13 +191,13 @@ private String handlePossibleStem(List<TemplateComponent> components, String ste
                 //all JAX-RS paths have an implicit (/.*)? at the end of them
                 //to technically every path has an optional implicit slash
                 stem = stem.substring(0, stem.length() - 1);
-                components.add(new TemplateComponent(Type.LITERAL, stem, null, null, null));
-                components.add(new TemplateComponent(Type.LITERAL, "/", null, null, null));
+                components.add(new TemplateComponent(Type.LITERAL, stem, null, null, null, null));
+                components.add(new TemplateComponent(Type.LITERAL, "/", null, null, null, null));
             } else {
-                components.add(new TemplateComponent(Type.LITERAL, literal, null, null, null));
+                components.add(new TemplateComponent(Type.LITERAL, literal, null, null, null, null));
             }
         } else {
-            components.add(new TemplateComponent(Type.LITERAL, literal, null, null, null));
+            components.add(new TemplateComponent(Type.LITERAL, literal, null, null, null, null));
         }
         return stem;
     }
@@ -269,14 +286,20 @@ public static class TemplateComponent implements Dumpable {
         /**
          * The names of all the capturing groups. Only used for CUSTOM_REGEX
          */
+        public final String[] groups;
+
+        /**
+         * The names of all the components. Only used for CUSTOM_REGEX
+         */
         public final String[] names;
 
-        public TemplateComponent(Type type, String literalText, String name, Pattern pattern, String[] names) {
+        public TemplateComponent(Type type, String literalText, String name, Pattern pattern, String[] names, String[] groups) {
             this.type = type;
             this.literalText = literalText;
             this.name = name;
             this.pattern = pattern;
             this.names = names;
+            this.groups = groups;
         }
 
         @Override
diff --git a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/RegexResource.java b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/RegexResource.java
index 179200a5597..f633fc44b24 100644
--- a/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/RegexResource.java
+++ b/independent-projects/resteasy-reactive/server/vertx/src/test/java/org/jboss/resteasy/reactive/server/vertx/test/matching/RegexResource.java
@@ -8,8 +8,8 @@
 public class RegexResource {
 
     @GET
-    @Path("/{pin:[A-Z0-9]{4}}")
-    public String hello(@PathParam("pin") String name) {
+    @Path("/{  1p-_in.  :  [A-Z0-9]{4}  }")
+    public String hello(@PathParam("1p-_in.") String name) {
         return "pin " + name;
     }