This is an automated email from the ASF dual-hosted git repository.
jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git
The following commit(s) were added to refs/heads/master by this push:
new 3e949b0867 Fix issue with @Path with default blank values
3e949b0867 is described below
commit 3e949b08671e63e57d22cd4016ef6476feaab99b
Author: James Bognar <[email protected]>
AuthorDate: Thu Nov 13 09:24:47 2025 -0500
Fix issue with @Path with default blank values
---
.../src/main/java/org/apache/juneau/Constants.java | 33 ++++++++++
.../org/apache/juneau/http/annotation/Path.java | 3 +-
.../juneau/http/annotation/PathAnnotation.java | 5 +-
.../org/apache/juneau/httppart/HttpPartSchema.java | 8 ++-
.../rest/client/remote/RemoteOperationMeta.java | 7 +-
.../java/org/apache/juneau/rest/arg/PathArg.java | 4 +-
.../juneau/httppart/HttpPartSchema_Path_Test.java | 76 ++++++++++++++++++++++
7 files changed, 127 insertions(+), 9 deletions(-)
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Constants.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Constants.java
new file mode 100644
index 0000000000..d7d7badd2b
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Constants.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau;
+
+/**
+ * Global constants used across the Juneau framework.
+ */
+public class Constants {
+
+ /**
+ * Sentinel value to indicate that a default value is not specified.
+ *
+ * <p>
+ * Used in annotation default values where empty string cannot
distinguish between
+ * "no value specified" and "explicitly set to empty string".
+ */
+ public static final String NONE = "_NONE_";
+}
+
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
index 845bfb71b8..d2d9c22703 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
@@ -18,6 +18,7 @@ package org.apache.juneau.http.annotation;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
+import static org.apache.juneau.Constants.*;
import java.lang.annotation.*;
@@ -94,7 +95,7 @@ public @interface Path {
*
* @return The annotation value.
*/
- String def() default "";
+ String def() default NONE;
/**
* Optional description for the exposed API.
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathAnnotation.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathAnnotation.java
index 31d37024e2..2ca5145fd6 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathAnnotation.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/PathAnnotation.java
@@ -18,6 +18,7 @@ package org.apache.juneau.http.annotation;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
+import static org.apache.juneau.Constants.*;
import static org.apache.juneau.common.utils.CollectionUtils.*;
import static org.apache.juneau.common.utils.Utils.*;
@@ -89,7 +90,7 @@ public class PathAnnotation {
Class<? extends HttpPartParser> parser =
HttpPartParser.Void.class;
Class<? extends HttpPartSerializer> serializer =
HttpPartSerializer.Void.class;
Schema schema = SchemaAnnotation.DEFAULT;
- String name = "", value = "", def = "";
+ String name = "", value = "", def = NONE;
/**
* Constructor.
@@ -274,7 +275,7 @@ public class PathAnnotation {
*/
public static Value<String> findDef(ParameterInfo pi) {
Value<String> n = Value.empty();
-
rstream(pi.getAllAnnotationInfos(Path.class)).map(AnnotationInfo::inner).filter(x
-> isNotEmpty(x.def())).forEach(x -> n.set(x.def()));
+
rstream(pi.getAllAnnotationInfos(Path.class)).map(AnnotationInfo::inner).filter(x
-> isNotEmpty(x.def()) && ne(NONE, x.def())).forEach(x -> n.set(x.def()));
return n;
}
diff --git
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
index f6205c8d9e..6426435041 100644
---
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
+++
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
@@ -24,6 +24,7 @@ import static org.apache.juneau.common.utils.StringUtils.*;
import static org.apache.juneau.common.utils.ThrowableUtils.*;
import static org.apache.juneau.common.utils.Utils.*;
import static org.apache.juneau.common.utils.Utils.isEmpty;
+import static org.apache.juneau.Constants.*;
import static org.apache.juneau.httppart.HttpPartDataType.*;
import static org.apache.juneau.httppart.HttpPartFormat.*;
@@ -2666,7 +2667,9 @@ public class HttpPartSchema {
if (! SchemaAnnotation.empty(a.schema()))
apply(a.schema());
name(firstNonEmpty(a.name(), a.value()));
- _default(a.def());
+ String def = a.def();
+ if (ne(NONE, def))
+ _default = def; // Set directly to allow empty
strings as valid defaults
parser(a.parser());
serializer(a.serializer());
@@ -2675,7 +2678,8 @@ public class HttpPartSchema {
allowEmptyValue();
required(false);
} else if (required == null) {
- required(true);
+ // Path parameters with default values are not
required
+ required(eq(NONE, def));
}
return this;
diff --git
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
index dd95e37a74..82d3ab0657 100644
---
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
+++
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
@@ -16,6 +16,7 @@
*/
package org.apache.juneau.rest.client.remote;
+import static org.apache.juneau.Constants.*;
import static org.apache.juneau.common.utils.CollectionUtils.*;
import static org.apache.juneau.common.utils.PredicateUtils.*;
import static org.apache.juneau.common.utils.StringUtils.*;
@@ -233,7 +234,7 @@ public class RemoteOperationMeta {
.forEach(p -> {
String name = firstNonEmpty(p.name(),
p.value());
String def = p.def();
- if (isNotEmpty(name) &&
isNotEmpty(def)) {
+ if (isNotEmpty(name) && ne(NONE, def)) {
defaults.put(name, def);
}
});
@@ -245,13 +246,13 @@ public class RemoteOperationMeta {
for (var p : x.value()) {
String name =
firstNonEmpty(p.name(), p.value());
String def = p.def();
- if (isNotEmpty(name) &&
isNotEmpty(def)) {
+ if (isNotEmpty(name) &&
ne(NONE, def)) {
defaults.put(name, def);
}
}
});
}
-
+
private static void processQueryDefaults(MethodInfo mi,
Map<String,String> defaults) {
rstream(mi.getAllAnnotationInfos())
.map(x -> x.cast(Query.class))
diff --git
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathArg.java
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathArg.java
index 094a0fd848..4c2f76262f 100644
---
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathArg.java
+++
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/arg/PathArg.java
@@ -16,6 +16,7 @@
*/
package org.apache.juneau.rest.arg;
+import static org.apache.juneau.Constants.*;
import static org.apache.juneau.common.utils.StringUtils.*;
import static org.apache.juneau.common.utils.Utils.*;
import static org.apache.juneau.http.annotation.PathAnnotation.*;
@@ -172,7 +173,8 @@ public class PathArg implements RestOpArg {
Path mergedPath = getMergedPath(paramInfo, name);
// Use merged path annotation for all lookups
- this.def = nn(mergedPath) && ! mergedPath.def().isEmpty() ?
mergedPath.def() : findDef(paramInfo).orElse(null);
+ String pathDef = nn(mergedPath) ? mergedPath.def() : null;
+ this.def = nn(pathDef) && ne(NONE, pathDef) ? pathDef :
findDef(paramInfo).orElse(null);
this.type = paramInfo.getParameterType().innerType();
this.schema = nn(mergedPath) ?
HttpPartSchema.create(mergedPath) : HttpPartSchema.create(Path.class,
paramInfo);
Class<? extends HttpPartParser> pp = schema.getParser();
diff --git
a/juneau-utest/src/test/java/org/apache/juneau/httppart/HttpPartSchema_Path_Test.java
b/juneau-utest/src/test/java/org/apache/juneau/httppart/HttpPartSchema_Path_Test.java
index 599b2445f8..205fbf5f3b 100644
---
a/juneau-utest/src/test/java/org/apache/juneau/httppart/HttpPartSchema_Path_Test.java
+++
b/juneau-utest/src/test/java/org/apache/juneau/httppart/HttpPartSchema_Path_Test.java
@@ -814,4 +814,80 @@ class HttpPartSchema_Path_Test extends TestBase {
assertThrowsWithMessage(SchemaValidationException.class,
"Maximum number of items exceeded.",
()->s.getItems().getItems().getItems().validateOutput(split("1,2,3,4,5"),
BeanContext.DEFAULT));
assertThrowsWithMessage(SchemaValidationException.class,
"Maximum number of items exceeded.",
()->s.getItems().getItems().getItems().getItems().validateOutput(split("1,2,3,4,5,6"),
BeanContext.DEFAULT));
}
+
+
//-----------------------------------------------------------------------------------------------------------------
+ // @Path with default values - required flag behavior
+
//-----------------------------------------------------------------------------------------------------------------
+
+ @Path(name="x")
+ public static class E01 {}
+
+ @Test void e01_required_noDefault() {
+ var s = HttpPartSchema.create().applyAll(Path.class,
E01.class).build();
+ // Path parameters without default values should be required
+ assertTrue(s.isRequired());
+ }
+
+ @Path(name="x", def="defaultValue")
+ public static class E02 {}
+
+ @Test void e02_notRequired_withDefault() {
+ var s = HttpPartSchema.create().applyAll(Path.class,
E02.class).build();
+ // Path parameters with default values should NOT be required
+ assertFalse(s.isRequired());
+ assertEquals("defaultValue", s.getDefault());
+ }
+
+ @Path(name="x", def="")
+ public static class E03 {}
+
+ @Test void e03_notRequired_withEmptyDefault() {
+ var s = HttpPartSchema.create().applyAll(Path.class,
E03.class).build();
+ // Path parameters with empty string default should NOT be
required
+ assertFalse(s.isRequired());
+ assertEquals("", s.getDefault());
+ }
+
+ public static class E04 {
+ public void a(
+ @Path(name = "logger", def = "") String
loggerName,
+ @Path(name = "level", def = "INFO") String
levelName
+ ) {
+ /* no-op */
+ }
+ }
+
+ @Test void e04_multiplePathParamsWithDefaults() throws Exception {
+ var loggerParam = MethodInfo.of(E04.class.getMethod("a",
String.class, String.class)).getParameter(0);
+ var levelParam = MethodInfo.of(E04.class.getMethod("a",
String.class, String.class)).getParameter(1);
+
+ var loggerSchema = HttpPartSchema.create().applyAll(Path.class,
loggerParam).build();
+ var levelSchema = HttpPartSchema.create().applyAll(Path.class,
levelParam).build();
+
+ // Both should not be required since they have defaults
+ assertFalse(loggerSchema.isRequired());
+ assertFalse(levelSchema.isRequired());
+
+ // Verify defaults are set correctly
+ assertEquals("", loggerSchema.getDefault());
+ assertEquals("INFO", levelSchema.getDefault());
+
+ // Verify names are set correctly
+ assertEquals("logger", loggerSchema.getName());
+ assertEquals("level", levelSchema.getName());
+
+ // This should not throw the regression error:
+ // "Cannot specify a default value on a required value."
+ assertDoesNotThrow(() -> loggerSchema.validateInput("test"));
+ assertDoesNotThrow(() -> levelSchema.validateInput("DEBUG"));
+ }
+
+ @Path(value="/remainder")
+ public static class E05 {}
+
+ @Test void e05_pathRemainder_notRequired() {
+ var s = HttpPartSchema.create().applyAll(Path.class,
E05.class).build();
+ // Path remainders (starting with '/') should never be required
+ assertFalse(s.isRequired());
+ }
}
\ No newline at end of file