1212import ru .vyarus .dropwizard .guice .test .client .builder .TestClientDefaults ;
1313import ru .vyarus .dropwizard .guice .test .client .builder .TestClientRequestBuilder ;
1414import ru .vyarus .dropwizard .guice .test .client .builder .TestRequestConfig ;
15+ import ru .vyarus .dropwizard .guice .url .util .RestPathUtils ;
1516
1617import java .net .URI ;
1718import java .util .function .Consumer ;
120121 * </code></pre>
121122 * <p>
122123 * {@link TestClient} is a general client class, but there are special client classes for rest (extending it):
123- * {@link ru.vyarus.dropwizard.guice.test.client.TestRestClient} and
124- * {@link ru.vyarus.dropwizard.guice.test.client.ResourceClient} (they could be obtained from the root
124+ * {@link ru.vyarus.dropwizard.guice.test.client.ResourceClient} (could be obtained from the root) and
125125 * {@link ru.vyarus.dropwizard.guice.test.ClientSupport object (which is also a test client)})
126126 *
127127 * @param <T> actual client type
@@ -219,12 +219,12 @@ public Invocation.Builder request(final String path, final Object... args) {
219219 * @param args variables for path placeholders (String.format() arguments)
220220 * @return new client with a different root path
221221 */
222- public T subClient (final String path , final Object ... args ) {
222+ public TestClient <?> subClient (final String path , final Object ... args ) {
223223 Preconditions .checkState (!path .toLowerCase ().startsWith ("http" ),
224224 "Only sub urls relative to current client url could be used. For completely custom external "
225- + "client creation use ClientSupport.customClient ()" );
225+ + "client creation use ClientSupport.externalClient ()" );
226226 // client INHERITS current defaults
227- return createClient ( String .format (path , args ));
227+ return new TestClient <>(() -> target ( String .format (path , args )), defaults );
228228 }
229229
230230 /**
@@ -240,10 +240,10 @@ public T subClient(final String path, final Object... args) {
240240 * @param consumer uri builder configurator
241241 * @return client with a constructed path (relative to the current client path)
242242 */
243- public T subClient (final Consumer <UriBuilder > consumer ) {
243+ public TestClient <?> subClient (final Consumer <UriBuilder > consumer ) {
244244 final UriBuilder uriBuilder = UriBuilder .newInstance ();
245245 consumer .accept (uriBuilder );
246- return createClient ( uriBuilder .toString ());
246+ return new TestClient <>(() -> target ( uriBuilder .toString ()), defaults );
247247 }
248248
249249 /**
@@ -260,6 +260,56 @@ public <K> ResourceClient<K> subClient(final Consumer<UriBuilder> consumer, fina
260260 return new ResourceClient <>(() -> target (uriBuilder .toString ()), defaults , resource );
261261 }
262262
263+ /**
264+ * Create a new sub-client for a specified resource class (appends a resource path, obtained from
265+ * {@link jakarta.ws.rs.Path} annotation, to the current client path). Method is useful when generic
266+ * rest path must be "typed" with a resource type (to be able to call resource methods directly).
267+ * <p>
268+ * In case of sub-resources, use {@link #subResourceClient(String, Class, Object...)} to properly specify
269+ * sub-resource mapping path (from lookup method):
270+ * {@code ResourceClient rest = client.subResourceClient("path", SubResource.class)}.
271+ * IMPORTANT: this is NOT THE SAME: {@code client.subClient("path").restClient(SubResource.class)} because
272+ * "restClient()" call would append path from resource, which is ignored for sub resources!.
273+ * <p>
274+ * Defaults could be used to declare path parameter values:
275+ * {@code ResourceClient rest = client.restClient(Resource.class).defaultPathParam("param", "value")} where
276+ * a resource class path is like "/some/{param}/path". With the default path param, there would be no need to
277+ * declare it for each request call.
278+ * <p>
279+ * All defaults, configured for the current client, will be inherited in a sub-client. If this is not required,
280+ * just clean defaults after creation: {@code client.subClient(ResClass.class).reset()}.
281+ *
282+ * @param resource resource class one to build a path for
283+ * @return resource client (with a resource path, relative to the current client path)
284+ * @param <R> resource type
285+ */
286+ public <R > ResourceClient <R > restClient (final Class <R > resource ) {
287+ final String target = RestPathUtils .getResourcePath (resource );
288+ // last class used for a resource type to get methods on
289+ return new ResourceClient <>(() -> target (target ), defaults , resource );
290+ }
291+
292+ /**
293+ * Create a sub client for the sub-resource.
294+ * <p>
295+ * IMPORTANT: Path, declared on sub-resource class is ignored! Only lookup method path is counted.
296+ * For example, {@code @Path("/sub") SubResource something() {...}} means all sub resource methods would be
297+ * available on "/sub/*".
298+ *
299+ * @param path sub-resource mapping path (from sub-resource method; could contain String.format()
300+ * placeholders: %s)
301+ * @param args variables for path placeholders (String.format() arguments)
302+ * @param subResource sub-resource
303+ * @param <R> sub-resource type
304+ * @return sub-resource client
305+ */
306+ public <R > ResourceClient <R > subResourceClient (final String path , final Class <R > subResource ,
307+ final Object ... args ) {
308+ final String target = String .format (path , args );
309+ // last class used for a resource type to get methods on
310+ return new ResourceClient <>(() -> target (target ), defaults , subResource );
311+ }
312+
263313 /**
264314 * Cast current path as provided resource (full!) path. Use-case: resources were mapped on non-standard path
265315 * (admin context resources or internal resource mappings).
@@ -268,10 +318,10 @@ public <K> ResourceClient<K> subClient(final Consumer<UriBuilder> consumer, fina
268318 * already a resource path.
269319 *
270320 * @param resource resource type
271- * @param <T > resource type
321+ * @param <R > resource type
272322 * @return rest client for provided resource
273323 */
274- public <T > ResourceClient <T > asRestClient (final Class <T > resource ) {
324+ public <R > ResourceClient <R > asRestClient (final Class <R > resource ) {
275325 return new ResourceClient <>(() -> target ("/" ), defaults , resource );
276326 }
277327
@@ -910,12 +960,14 @@ public TestClientRequestBuilder buildDelete(final String path, final Object... a
910960 * <p>
911961 * See {@link ru.vyarus.dropwizard.guice.test.client.builder.FormBuilder#param(String, Object)} for more details
912962 * about parameter values conversion.
963+ * <p>
964+ * Use null path to build entity: {@code buildForm(null).param().buildEntity()}.
913965 *
914966 * @param path target path, relative to rest root (could contain String.format() placeholders: %s)
915967 * @param args variables for path placeholders (String.format() arguments)
916968 * @return form builder
917969 */
918- public FormBuilder buildForm (final String path , final Object ... args ) {
970+ public FormBuilder buildForm (final @ Nullable String path , final Object ... args ) {
919971 return new FormBuilder (target (path , args ), defaults );
920972 }
921973
@@ -965,16 +1017,4 @@ private <R> R handleShortcut(final TestClientRequestBuilder request, final @Null
9651017 // immediate result mapping with bypassing exceptions in rest stubs mode (if not exception mapper registered)
9661018 return request .as (result );
9671019 }
968-
969- /**
970- * Create a client instance. It is assumed that all underlying classes would override this method to produce
971- * sub clients of the same type.
972- *
973- * @param target target path
974- * @return client insatnce
975- */
976- @ SuppressWarnings ("unchecked" )
977- protected T createClient (final String target ) {
978- return (T ) new TestClient <>(() -> target (target ), defaults );
979- }
9801020}
0 commit comments