You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2020/03/19 16:40:16 UTC

[juneau] branch master updated: JUNEAU-205

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 cd7c561  JUNEAU-205
cd7c561 is described below

commit cd7c5617fd6af1504ef04675961f9f354ff737c3
Author: JamesBognar <ja...@apache.org>
AuthorDate: Thu Mar 19 12:39:58 2020 -0400

    JUNEAU-205
    
    Add @Path(required=boolean) annotation to allow path variables to be
    optionally matched.
---
 .../juneau/http/annotation/AnnotationUtils.java    | 14 +++++++
 .../org/apache/juneau/http/annotation/Path.java    | 47 ++++++++++++++++++++++
 .../juneau/httppart/HttpPartSchemaBuilder.java     |  8 ++--
 juneau-doc/docs/ReleaseNotes/8.1.4.html            |  2 +
 .../rest/annotation2/PathAnnotationTest.java       | 32 +++++++++++++++
 5 files changed, 100 insertions(+), 3 deletions(-)

diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/AnnotationUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/AnnotationUtils.java
index 337aa24..e944778 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/AnnotationUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/AnnotationUtils.java
@@ -227,6 +227,7 @@ public class AnnotationUtils {
 			allEmpty(a.description(), a._enum(), a.example(), a.api())
 			&& allEmpty(a.name(), a.value(), a.type(), a.format(), a.pattern(), a.maximum(), a.minimum(), a.multipleOf())
 			&& allFalse(a.exclusiveMaximum(), a.exclusiveMinimum(), a.uniqueItems())
+			&& allTrue(a.required())
 			&& allMinusOne(a.maxLength(), a.minLength(), a.maxItems(), a.minItems())
 			&& empty(a.items());
 	}
@@ -272,6 +273,19 @@ public class AnnotationUtils {
 	}
 
 	/**
+	 * Returns <jk>true</jk> if all the specified booleans are true.
+	 *
+	 * @param booleans The booleans to test.
+	 * @return <jk>true</jk> if all the specified booleans are true.
+	 */
+	protected static boolean allTrue(boolean...booleans) {
+		for (boolean b : booleans)
+			if (! b)
+				return false;
+		return true;
+	}
+
+	/**
 	 * Returns <jk>true</jk> if all the specified longs are -1.
 	 *
 	 * @param longs The booleans to test.
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 2dc8a50..c1ac168 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
@@ -221,6 +221,53 @@ public @interface Path {
 	 */
 	String[] description() default {};
 
+	/**
+	 * <mk>required</mk> field of the {@doc SwaggerParameterObject}.
+	 *
+	 * <p>
+	 * Determines whether the parameter is mandatory.
+	 *
+	 * <p>
+	 * If validation fails during serialization or parsing, the part serializer/parser will throw a {@link SchemaValidationException}.
+	 * <br>On the client-side, this gets converted to a <c>RestCallException</c> which is thrown before the connection is made.
+	 * <br>On the server-side, this gets converted to a <c>BadRequest</c> (400).
+	 *
+	 * <p>
+	 * A path can be marked as not-required when the path variable is resolved by a parent resource like so:
+	 *
+	 * <p class='bcode w800'>
+	 * 	<ja>@Rest</ja>(path=<js>"/parent/{p1}"</js>,children=Child.<jk>class</jk>)
+	 * 	<jk>public class</jk> Parent {
+	 * 		...
+	 * 	}
+	 *
+	 * 	<ja>@Rest</ja>(path="/child")
+	 * 	<jk>public class</jk> Child {
+	 *
+	 * 		<ja>@RestMethod</ja>(path="/")
+	 * 		<jk>public</jk> String doGet(<ja>@Path</ja>(name=<js>"p1"</js>,required=<jk>false</jk>) String p1) {
+	 * 			<jc>// p1 will be null when accessed via "/child"</jc>
+	 *			<jc>// p1 will be non-null when accessed via "/parent/p1/child".</jc>
+	 * 		}
+	 * 		...
+	 * 	}
+	 * </p>
+	 * 
+	 * <p>
+	 * This allows the child resource to be mapped to multiple parents that may resolve various different path variables.
+	 *
+	 * <h5 class='section'>Used for:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li>
+	 * 		Server-side schema-based parsing validation.
+	 * 	<li>
+	 * 		Server-side generated Swagger documentation.
+	 * 	<li>
+	 * 		Client-side schema-based serializing validation.
+	 * </ul>
+	 */
+	boolean required() default true;
+
 	//=================================================================================================================
 	// Attributes specific to parameters other than body
 	//=================================================================================================================
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
index 21c75b4..2c3e95e 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
@@ -263,10 +263,12 @@ public class HttpPartSchemaBuilder {
 		serializer(a.serializer());
 
 		// Path remainder always allows empty value.
-		if (startsWith(name, '/'))
+		if (startsWith(name, '/')) {
 			allowEmptyValue();
-		else
-			required(true);
+			required(false);
+		} else {
+			required(a.required());
+		}
 
 		return this;
 	}
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.4.html b/juneau-doc/docs/ReleaseNotes/8.1.4.html
index 07852ec..a80fb9b 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.4.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.4.html
@@ -309,6 +309,8 @@
 			<li class='jc'>{@link oajr.RestGuard}
 			<li class='jc'>{@link oajr.ResponseHandler}
 		</ul>
+	<li>
+		New {@link oaj.http.annotation.Path#required() @Path(required)} annotation support.
 </ul>
 
 <h5 class='topic w800'>juneau-rest-server-springboot</h5>
diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
index e5708e6..81ab56d 100644
--- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
+++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
@@ -1103,4 +1103,36 @@ public class PathAnnotationTest {
 		ParameterInfo x = tc.getParameterInfo("/example/{P}","get","path","P");
 		assertEquals("{f1:'b'}", x.getExample());
 	}
+
+
+	//=================================================================================================================
+	// Basic tests
+	//=================================================================================================================
+
+	@Rest(path="/u1/{u1}",children=U2.class)
+	public static class U1 {
+	}
+	@Rest(path="/u2")
+	public static class U2 {
+		@RestMethod(path="/")
+		public String doGet(@Path(name="u1",required=false) String u1) {
+			return u1 == null ? "nil" : u1;
+		}
+		@RestMethod(path="/foo/{bar}")
+		public String doGetFoo(@Path(name="u1", required=false) String u1, @Path(name="bar",required=false) String bar) {
+			return (u1 == null ? "nil" : u1) + "," + (bar == null ? "nil" : bar);
+		}
+	}
+
+	static MockRest u1 = MockRest.build(U1.class, null);
+	static MockRest u2 = MockRest.build(U2.class, null);
+
+	@Test
+	public void u01_nonRequiredPath() throws Exception {
+		u1.get("/u1/foo/u2").execute().assertStatus(200).assertBody("foo");
+		u1.get("/u1/foo/u2/foo/xxx").execute().assertStatus(200).assertBody("foo,xxx");
+		u2.get("/").execute().assertStatus(200).assertBody("nil");
+		u2.get("/foo/xxx").execute().assertStatus(200).assertBody("nil,xxx");
+	}
+
 }
\ No newline at end of file