11/*
2- * Copyright 2002-2022 the original author or authors.
2+ * Copyright 2002-2023 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
@@ -145,7 +145,7 @@ public DefaultRequest variable(String name, @Nullable Object value) {
145145 }
146146
147147 @ Override
148- public DefaultRequest extension (String name , Object value ) {
148+ public DefaultRequest extension (String name , @ Nullable Object value ) {
149149 this .extensions .put (name , value );
150150 return this ;
151151 }
@@ -270,6 +270,7 @@ void verifyErrors() {
270270 "If expected, please filter them out: " + this .unexpectedErrors ,
271271 CollectionUtils .isEmpty (this .unexpectedErrors )));
272272 }
273+
273274 }
274275
275276
@@ -290,7 +291,12 @@ private DefaultResponse(
290291 @ Override
291292 public Path path (String path ) {
292293 this .delegate .verifyErrors ();
293- return new DefaultPath (path , this .delegate );
294+ return DefaultPath .forPath (null , path , this .delegate );
295+ }
296+
297+ @ Override
298+ public Path path (String path , Consumer <Path > pathConsumer ) {
299+ return DefaultPath .forNestedPath (null , path , this .delegate , pathConsumer );
294300 }
295301
296302 @ Override
@@ -329,6 +335,9 @@ public Traversable satisfy(Consumer<List<ResponseError>> consumer) {
329335 */
330336 private static final class DefaultPath implements Path {
331337
338+ @ Nullable
339+ private final String basePath ;
340+
332341 private final String path ;
333342
334343 private final ResponseDelegate delegate ;
@@ -337,19 +346,20 @@ private static final class DefaultPath implements Path {
337346
338347 private final JsonPathExpectationsHelper pathHelper ;
339348
340- private DefaultPath (String path , ResponseDelegate delegate ) {
349+ private DefaultPath (@ Nullable String basePath , String path , ResponseDelegate delegate ) {
341350 Assert .notNull (path , "`path` is required" );
342351 Assert .notNull (delegate , "ResponseContainer is required" );
343352
344- String fullPath = initFullPath (path );
345-
353+ this .basePath = basePath ;
346354 this .path = path ;
347355 this .delegate = delegate ;
356+
357+ String fullPath = initDataJsonPath (this .path );
348358 this .jsonPath = JsonPath .compile (fullPath );
349359 this .pathHelper = new JsonPathExpectationsHelper (fullPath );
350360 }
351361
352- private static String initFullPath (String path ) {
362+ private static String initDataJsonPath (String path ) {
353363 if (!StringUtils .hasText (path )) {
354364 path = "$.data" ;
355365 }
@@ -361,7 +371,12 @@ else if (!path.startsWith("$") && !path.startsWith("data.")) {
361371
362372 @ Override
363373 public Path path (String path ) {
364- return new DefaultPath (path , this .delegate );
374+ return forPath (this .basePath , path , this .delegate );
375+ }
376+
377+ @ Override
378+ public Path path (String path , Consumer <Path > pathConsumer ) {
379+ return forNestedPath (this .basePath , path , this .delegate , pathConsumer );
365380 }
366381
367382 @ Override
@@ -390,25 +405,25 @@ public Path pathDoesNotExist() {
390405 @ Override
391406 public <D > Entity <D , ?> entity (Class <D > entityType ) {
392407 D entity = this .delegate .read (this .jsonPath , new TypeRefAdapter <>(entityType ));
393- return new DefaultEntity <>(entity , this .path , this .delegate );
408+ return new DefaultEntity <>(entity , this .basePath , this . path , this .delegate );
394409 }
395410
396411 @ Override
397412 public <D > Entity <D , ?> entity (ParameterizedTypeReference <D > entityType ) {
398413 D entity = this .delegate .read (this .jsonPath , new TypeRefAdapter <>(entityType ));
399- return new DefaultEntity <>(entity , this .path , this .delegate );
414+ return new DefaultEntity <>(entity , this .basePath , this . path , this .delegate );
400415 }
401416
402417 @ Override
403418 public <D > EntityList <D > entityList (Class <D > elementType ) {
404419 List <D > entity = this .delegate .read (this .jsonPath , new TypeRefAdapter <>(List .class , elementType ));
405- return new DefaultEntityList <>(entity , this .path , this .delegate );
420+ return new DefaultEntityList <>(entity , this .basePath , this . path , this .delegate );
406421 }
407422
408423 @ Override
409424 public <D > EntityList <D > entityList (ParameterizedTypeReference <D > elementType ) {
410425 List <D > entity = this .delegate .read (this .jsonPath , new TypeRefAdapter <>(List .class , elementType ));
411- return new DefaultEntityList <>(entity , this .path , this .delegate );
426+ return new DefaultEntityList <>(entity , this .basePath , this . path , this .delegate );
412427 }
413428
414429 @ Override
@@ -439,6 +454,22 @@ private void matchesJson(String expected, boolean strict) {
439454 }
440455 });
441456 }
457+
458+ static Path forPath (@ Nullable String basePath , String path , ResponseDelegate delegate ) {
459+ String pathToUse = joinPaths (basePath , path );
460+ return new DefaultPath (basePath , pathToUse , delegate );
461+ }
462+
463+ static Path forNestedPath (@ Nullable String basePath , String path , ResponseDelegate delegate , Consumer <Path > consumer ) {
464+ String pathToUse = joinPaths (basePath , path );
465+ consumer .accept (new DefaultPath (pathToUse , pathToUse , delegate ));
466+ return new DefaultPath (basePath , path , delegate );
467+ }
468+
469+ private static String joinPaths (@ Nullable String basePath , String path ) {
470+ return (basePath != null ? basePath + "." + path : path );
471+ }
472+
442473 }
443474
444475
@@ -449,14 +480,18 @@ private static class DefaultEntity<D, S extends Entity<D, S>> implements Entity<
449480
450481 private final D entity ;
451482
483+ @ Nullable
484+ private final String basePath ;
485+
452486 private final String path ;
453487
454488 private final ResponseDelegate delegate ;
455489
456- protected DefaultEntity (D entity , String path , ResponseDelegate delegate ) {
490+ protected DefaultEntity (D entity , @ Nullable String basePath , String path , ResponseDelegate delegate ) {
457491 this .entity = entity ;
458- this .delegate = delegate ;
492+ this .basePath = basePath ;
459493 this .path = path ;
494+ this .delegate = delegate ;
460495 }
461496
462497 protected D getEntity () {
@@ -473,7 +508,12 @@ protected String getPath() {
473508
474509 @ Override
475510 public Path path (String path ) {
476- return new DefaultPath (path , this .delegate );
511+ return DefaultPath .forPath (this .basePath , path , this .delegate );
512+ }
513+
514+ @ Override
515+ public Path path (String path , Consumer <Path > pathConsumer ) {
516+ return DefaultPath .forNestedPath (this .basePath , path , this .delegate , pathConsumer );
477517 }
478518
479519 @ Override
@@ -528,11 +568,12 @@ private <T extends S> T self() {
528568 /**
529569 * Default {@link EntityList} implementation.
530570 */
531- private static final class DefaultEntityList <E > extends DefaultEntity <List <E >, EntityList <E >>
532- implements EntityList <E > {
571+ @ SuppressWarnings ("SlowListContainsAll" )
572+ private static final class DefaultEntityList <E >
573+ extends DefaultEntity <List <E >, EntityList <E >> implements EntityList <E > {
533574
534- private DefaultEntityList (List <E > entity , String path , ResponseDelegate delegate ) {
535- super (entity , path , delegate );
575+ private DefaultEntityList (List <E > entity , @ Nullable String basePath , String path , ResponseDelegate delegate ) {
576+ super (entity , basePath , path , delegate );
536577 }
537578
538579 @ Override
0 commit comments