├── backstopper_logo.png ├── samples ├── sample-spring-boot3-webflux │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── application.properties │ │ │ │ └── logback.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── nike │ │ │ │ └── backstopper │ │ │ │ └── springboot3webfluxsample │ │ │ │ ├── Main.java │ │ │ │ ├── model │ │ │ │ ├── RgbColor.java │ │ │ │ └── SampleModel.java │ │ │ │ └── error │ │ │ │ └── SampleProjectApiErrorsImpl.java │ │ └── test │ │ │ └── java │ │ │ ├── com │ │ │ └── nike │ │ │ │ └── backstopper │ │ │ │ └── springbootsample │ │ │ │ └── error │ │ │ │ └── SampleProjectApiErrorsImplTest.java │ │ │ └── jsr303convention │ │ │ ├── VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java │ │ │ ├── VerifyJsr303ContractTest.java │ │ │ └── ApplicationJsr303AnnotationTroller.java │ ├── buildSample.sh │ ├── runSample.sh │ └── build.gradle ├── sample-spring-boot3-webmvc │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── application.properties │ │ │ │ └── logback.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── nike │ │ │ │ └── backstopper │ │ │ │ └── springboot3webmvcsample │ │ │ │ ├── Main.java │ │ │ │ ├── model │ │ │ │ ├── RgbColor.java │ │ │ │ └── SampleModel.java │ │ │ │ └── error │ │ │ │ └── SampleProjectApiErrorsImpl.java │ │ └── test │ │ │ └── java │ │ │ ├── com │ │ │ └── nike │ │ │ │ └── backstopper │ │ │ │ └── springbootsample │ │ │ │ └── error │ │ │ │ └── SampleProjectApiErrorsImplTest.java │ │ │ └── jsr303convention │ │ │ ├── VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java │ │ │ ├── VerifyJsr303ContractTest.java │ │ │ └── ApplicationJsr303AnnotationTroller.java │ ├── buildSample.sh │ ├── runSample.sh │ └── build.gradle └── sample-spring-web-mvc │ ├── buildSample.sh │ ├── runSample.sh │ ├── src │ ├── main │ │ ├── resources │ │ │ └── logback.xml │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── backstopper │ │ │ └── springsample │ │ │ ├── model │ │ │ ├── RgbColor.java │ │ │ └── SampleModel.java │ │ │ └── error │ │ │ └── SampleProjectApiErrorsImpl.java │ └── test │ │ └── java │ │ └── com │ │ └── nike │ │ └── backstopper │ │ ├── springsample │ │ └── error │ │ │ └── SampleProjectApiErrorsImplTest.java │ │ └── apierror │ │ └── contract │ │ └── jsr303convention │ │ ├── VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java │ │ ├── VerifyJsr303ContractTest.java │ │ └── ApplicationJsr303AnnotationTroller.java │ └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── .gitignore ├── .codecov.yml ├── backstopper-core ├── src │ ├── test │ │ ├── resources │ │ │ └── logback.xml │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── backstopper │ │ │ ├── apierror │ │ │ ├── projectspecificinfo │ │ │ │ ├── ProjectApiErrorsCoreApiErrorsOnlyTest.java │ │ │ │ └── range │ │ │ │ │ └── IntegerRangeTest.java │ │ │ └── sample │ │ │ │ └── SampleProjectApiErrorsBaseTest.java │ │ │ ├── handler │ │ │ ├── listener │ │ │ │ └── impl │ │ │ │ │ └── ListenerTestBase.java │ │ │ └── ErrorResponseInfoTest.java │ │ │ ├── exception │ │ │ ├── ServersideValidationErrorTest.java │ │ │ └── network │ │ │ │ ├── ServerHttpStatusCodeExceptionTest.java │ │ │ │ └── ServerUnknownHttpStatusCodeExceptionTest.java │ │ │ └── service │ │ │ ├── NoOpJsr303ValidatorTest.java │ │ │ └── FailFastServersideValidationServiceTest.java │ └── main │ │ └── java │ │ └── com │ │ └── nike │ │ └── backstopper │ │ ├── exception │ │ ├── network │ │ │ ├── ServerTimeoutException.java │ │ │ ├── ServerUnreachableException.java │ │ │ ├── DownstreamRequestOrResponseBodyFailedValidationException.java │ │ │ └── NetworkExceptionBase.java │ │ ├── StackTraceLoggingBehavior.java │ │ ├── WrapperException.java │ │ └── ServersideValidationError.java │ │ ├── handler │ │ ├── UnexpectedMajorExceptionHandlingError.java │ │ ├── listener │ │ │ ├── ApiExceptionHandlerListener.java │ │ │ └── impl │ │ │ │ └── GenericApiExceptionHandlerListener.java │ │ └── ErrorResponseInfo.java │ │ ├── util │ │ └── ApiErrorUtil.java │ │ └── apierror │ │ ├── SortedApiErrorSet.java │ │ ├── ApiErrorComparator.java │ │ └── projectspecificinfo │ │ └── range │ │ └── IntegerRange.java ├── build.gradle └── README.md ├── backstopper-spring-boot3-webmvc ├── src │ └── test │ │ ├── resources │ │ └── logback.xml │ │ └── java │ │ └── com │ │ └── nike │ │ └── backstopper │ │ └── handler │ │ └── springboot │ │ └── config │ │ └── BackstopperSpringboot3WebMvcConfigTest.java └── build.gradle ├── testonly ├── testonly-spring-6_0-webmvc │ ├── src │ │ └── test │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── java │ │ │ └── serverconfig │ │ │ ├── classpathscan │ │ │ └── Spring_6_0_WebMvcClasspathScanConfig.java │ │ │ └── directimport │ │ │ └── Spring_6_0_WebMvcDirectImportConfig.java │ ├── README.md │ └── build.gradle ├── testonly-spring-6_1-webmvc │ ├── src │ │ └── test │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── java │ │ │ └── serverconfig │ │ │ ├── classpathscan │ │ │ └── Spring_6_1_WebMvcClasspathScanConfig.java │ │ │ └── directimport │ │ │ └── Spring_6_1_WebMvcDirectImportConfig.java │ ├── README.md │ └── build.gradle ├── testonly-springboot3_0-webflux │ ├── src │ │ └── test │ │ │ └── resources │ │ │ └── logback.xml │ ├── README.md │ └── build.gradle ├── testonly-springboot3_0-webmvc │ ├── src │ │ └── test │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── java │ │ │ └── serverconfig │ │ │ ├── classpathscan │ │ │ └── Springboot3_0WebMvcClasspathScanConfig.java │ │ │ └── directimport │ │ │ └── Springboot3_0WebMvcDirectImportConfig.java │ ├── README.md │ └── build.gradle ├── testonly-springboot3_1-webflux │ ├── src │ │ └── test │ │ │ └── resources │ │ │ └── logback.xml │ ├── README.md │ └── build.gradle ├── testonly-springboot3_1-webmvc │ ├── src │ │ └── test │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── java │ │ │ └── serverconfig │ │ │ ├── classpathscan │ │ │ └── Springboot3_1WebMvcClasspathScanConfig.java │ │ │ └── directimport │ │ │ └── Springboot3_1WebMvcDirectImportConfig.java │ ├── README.md │ └── build.gradle ├── testonly-springboot3_2-webflux │ ├── src │ │ └── test │ │ │ └── resources │ │ │ └── logback.xml │ ├── README.md │ └── build.gradle ├── testonly-springboot3_2-webmvc │ ├── src │ │ └── test │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── java │ │ │ └── serverconfig │ │ │ ├── classpathscan │ │ │ └── Springboot3_2WebMvcClasspathScanConfig.java │ │ │ └── directimport │ │ │ └── Springboot3_2WebMvcDirectImportConfig.java │ ├── README.md │ └── build.gradle ├── testonly-springboot3_3-webflux │ ├── src │ │ └── test │ │ │ └── resources │ │ │ └── logback.xml │ ├── README.md │ └── build.gradle ├── testonly-springboot3_3-webmvc │ ├── src │ │ └── test │ │ │ ├── resources │ │ │ └── logback.xml │ │ │ └── java │ │ │ └── serverconfig │ │ │ ├── classpathscan │ │ │ └── Springboot3_3WebMvcClasspathScanConfig.java │ │ │ └── directimport │ │ │ └── Springboot3_3WebMvcDirectImportConfig.java │ ├── README.md │ └── build.gradle ├── testonly-spring-webflux-reusable-test-support │ ├── src │ │ ├── test │ │ │ └── java │ │ │ │ ├── testonly │ │ │ │ └── componenttest │ │ │ │ │ └── spring │ │ │ │ │ └── reusable │ │ │ │ │ └── error │ │ │ │ │ └── SampleProjectApiErrorsImplTest.java │ │ │ │ └── jsr303convention │ │ │ │ ├── VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java │ │ │ │ ├── VerifyJsr303ContractTest.java │ │ │ │ └── ApplicationJsr303AnnotationTroller.java │ │ └── main │ │ │ └── java │ │ │ └── testonly │ │ │ └── componenttest │ │ │ └── spring │ │ │ └── reusable │ │ │ ├── model │ │ │ ├── RgbColor.java │ │ │ └── SampleModel.java │ │ │ ├── error │ │ │ └── SampleProjectApiErrorsImpl.java │ │ │ └── filter │ │ │ ├── ExplodingWebFilter.java │ │ │ └── ExplodingHandlerFilterFunction.java │ └── build.gradle └── testonly-spring-webmvc-reusable-test-support │ ├── src │ ├── test │ │ └── java │ │ │ ├── testonly │ │ │ └── componenttest │ │ │ │ └── spring │ │ │ │ └── reusable │ │ │ │ └── error │ │ │ │ └── SampleProjectApiErrorsImplTest.java │ │ │ └── jsr303convention │ │ │ ├── VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java │ │ │ ├── VerifyJsr303ContractTest.java │ │ │ └── ApplicationJsr303AnnotationTroller.java │ └── main │ │ └── java │ │ └── testonly │ │ └── componenttest │ │ └── spring │ │ └── reusable │ │ ├── model │ │ └── RgbColor.java │ │ ├── error │ │ └── SampleProjectApiErrorsImpl.java │ │ └── testutil │ │ └── ExplodingServletFilter.java │ └── build.gradle ├── backstopper-jackson ├── build.gradle └── README.md ├── backstopper-spring-web-flux ├── src │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── backstopper │ │ │ └── handler │ │ │ └── spring │ │ │ └── webflux │ │ │ └── componenttest │ │ │ └── model │ │ │ ├── RgbColor.java │ │ │ └── SampleModel.java │ └── main │ │ └── java │ │ └── com │ │ └── nike │ │ └── backstopper │ │ └── handler │ │ └── spring │ │ └── webflux │ │ ├── listener │ │ └── impl │ │ │ └── OneOffSpringWebFluxFrameworkExceptionHandlerListener.java │ │ └── DisconnectedClientHelper.java └── build.gradle ├── nike-internal-util ├── build.gradle ├── src │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── nike │ │ │ └── internal │ │ │ └── util │ │ │ ├── testing │ │ │ └── TestUtilsTest.java │ │ │ └── ImmutablePairTest.java │ └── main │ │ └── java │ │ └── com │ │ └── nike │ │ └── internal │ │ └── util │ │ ├── testing │ │ └── TestUtils.java │ │ └── MapBuilder.java └── README.md ├── backstopper-custom-validators ├── build.gradle └── README.md ├── NOTICE.txt ├── license └── LICENSE.errorhandling.txt ├── backstopper-servlet-api └── build.gradle ├── .github └── workflows │ └── build.yml ├── settings.gradle ├── backstopper-reusable-tests-junit5 ├── build.gradle └── README.md ├── backstopper-spring-web-mvc ├── build.gradle └── src │ └── test │ └── java │ └── com │ └── nike │ └── backstopper │ └── handler │ └── spring │ └── SpringApiExceptionHandlerUtilsTest.java ├── backstopper-spring-web ├── build.gradle └── README.md └── CONTRIBUTING.md /backstopper_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nike-Inc/backstopper/HEAD/backstopper_logo.png -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/buildSample.sh: -------------------------------------------------------------------------------- 1 | echo "../../gradlew clean build" 2 | ../../gradlew clean build -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/buildSample.sh: -------------------------------------------------------------------------------- 1 | echo "../../gradlew clean build" 2 | ../../gradlew clean build -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/buildSample.sh: -------------------------------------------------------------------------------- 1 | echo "../../gradlew clean build" 2 | ../../gradlew clean build -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Nike-Inc/backstopper/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/runSample.sh: -------------------------------------------------------------------------------- 1 | echo "../../gradlew run" 2 | echo "NOTE: Type ctrl+c to stop" 3 | ../../gradlew run $* -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/runSample.sh: -------------------------------------------------------------------------------- 1 | echo "../../gradlew run" 2 | echo "NOTE: Type ctrl+c to stop" 3 | ../../gradlew run $* -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/runSample.sh: -------------------------------------------------------------------------------- 1 | echo "../../gradlew run" 2 | echo "NOTE: Type ctrl+c to stop" 3 | ../../gradlew run $* -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version=2.0.2-SNAPSHOT 2 | nikeInternalUtilVersion=2.0.1-SNAPSHOT 3 | groupId=com.nike.backstopper 4 | nikeInternalUtilGroupId=com.nike.internal 5 | artifactId=backstopper 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Gradle stuff 2 | .gradle 3 | build/ 4 | gradle-app.setting 5 | 6 | ### IntelliJ stuff 7 | .idea/ 8 | *.iml 9 | *.ipr 10 | *.iws 11 | out/ 12 | classes/ 13 | .idea_modules/ 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | # Controls the behavior of the codecov.io integration. 2 | # See https://github.com/codecov/support/wiki/Codecov-Yaml for details on the options. 3 | coverage: 4 | status: 5 | project: 6 | default: 7 | enabled: yes 8 | target: 85% 9 | patch: 10 | default: 11 | enabled: yes 12 | target: 90% 13 | -------------------------------------------------------------------------------- /backstopper-core/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /backstopper-spring-boot3-webmvc/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-spring-6_0-webmvc/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-spring-6_1-webmvc/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_0-webflux/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_0-webmvc/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_1-webflux/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_1-webmvc/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_2-webflux/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_2-webmvc/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_3-webflux/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_3-webmvc/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] |-%-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/exception/network/ServerTimeoutException.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception.network; 2 | 3 | /** 4 | * Indicates that the call to the downstream server was taking too long and was killed. 5 | * 6 | * @author Nic Munroe 7 | */ 8 | public class ServerTimeoutException extends NetworkExceptionBase { 9 | public ServerTimeoutException(Throwable cause, String connectionType) { 10 | super(cause, connectionType); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/exception/network/ServerUnreachableException.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception.network; 2 | 3 | /** 4 | * Indicates that the downstream server could not be communicated with at all for some reason. 5 | * 6 | * @author Nic Munroe 7 | */ 8 | public class ServerUnreachableException extends NetworkExceptionBase { 9 | public ServerUnreachableException(Throwable cause, String connectionType) { 10 | super(cause, connectionType); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backstopper-spring-boot3-webmvc/src/test/java/com/nike/backstopper/handler/springboot/config/BackstopperSpringboot3WebMvcConfigTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.springboot.config; 2 | 3 | import org.junit.Test; 4 | 5 | /** 6 | * Tests the functionality of {@link BackstopperSpringboot3WebMvcConfig}. 7 | * 8 | * @author Nic Munroe 9 | */ 10 | public class BackstopperSpringboot3WebMvcConfigTest { 11 | 12 | @Test 13 | public void code_coverage_hoops() { 14 | // jump! 15 | new BackstopperSpringboot3WebMvcConfig(); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/handler/UnexpectedMajorExceptionHandlingError.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler; 2 | 3 | /** 4 | * Typed exception used by {@link com.nike.backstopper.handler.ApiExceptionHandlerBase} to indicate 5 | * that some unexpected (and major) error occurred while handling an exception. Likely indicates a bug in the exception 6 | * handler that needs to be fixed. 7 | * 8 | * @author Nic Munroe 9 | */ 10 | public class UnexpectedMajorExceptionHandlingError extends Exception { 11 | @SuppressWarnings("WeakerAccess") 12 | public UnexpectedMajorExceptionHandlingError(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backstopper-jackson/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":backstopper-core"), 6 | "com.fasterxml.jackson.core:jackson-core:$jacksonVersion", 7 | "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" 8 | ) 9 | testImplementation( 10 | "junit:junit:$junitVersion", 11 | "org.mockito:mockito-core:$mockitoVersion", 12 | "ch.qos.logback:logback-classic:$logbackVersion", 13 | "org.assertj:assertj-core:$assertJVersion", 14 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 15 | "org.hamcrest:hamcrest-all:$hamcrestVersion" 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /backstopper-spring-web-flux/src/test/java/com/nike/backstopper/handler/spring/webflux/componenttest/model/RgbColor.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.spring.webflux.componenttest.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | 5 | public enum RgbColor { 6 | RED, GREEN, BLUE; 7 | 8 | @JsonCreator 9 | @SuppressWarnings("unused") 10 | public static RgbColor toRgbColor(String colorString) { 11 | for (RgbColor color : values()) { 12 | if (color.name().equalsIgnoreCase(colorString)) 13 | return color; 14 | } 15 | throw new IllegalArgumentException( 16 | "Cannot convert the string: \"" + colorString + "\" to a valid RgbColor enum value." 17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /nike-internal-util/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | version=nikeInternalUtilVersion 4 | groupId = nikeInternalUtilGroupId 5 | group = nikeInternalUtilGroupId 6 | 7 | dependencies { 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" 10 | ) 11 | testImplementation( 12 | "junit:junit:$junitVersion", 13 | "org.mockito:mockito-core:$mockitoVersion", 14 | "com.fasterxml.jackson.core:jackson-core:$jacksonVersion", 15 | "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", 16 | "ch.qos.logback:logback-classic:$logbackVersion", 17 | "org.assertj:assertj-core:$assertJVersion", 18 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion" 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /backstopper-custom-validators/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | "org.slf4j:slf4j-api:$slf4jVersion", 6 | "jakarta.validation:jakarta.validation-api:$jakartaValidationVersion" 7 | ) 8 | testImplementation( 9 | project(":nike-internal-util"), 10 | "junit:junit:$junitVersion", 11 | "org.mockito:mockito-core:$mockitoVersion", 12 | "ch.qos.logback:logback-classic:$logbackVersion", 13 | "org.assertj:assertj-core:$assertJVersion", 14 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 15 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 16 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/main/java/com/nike/backstopper/springboot3webmvcsample/Main.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springboot3webmvcsample; 2 | 3 | import com.nike.backstopper.springboot3webmvcsample.config.SampleSpringboot3WebMvcSpringConfig; 4 | 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Import; 8 | 9 | /** 10 | * Starts up the Backstopper Spring Boot 3 Web MVC Sample server (on port 8080 by default). 11 | * 12 | * @author Nic Munroe 13 | */ 14 | @SpringBootApplication 15 | @Import(SampleSpringboot3WebMvcSpringConfig.class) 16 | public class Main { 17 | public static void main(String[] args) { 18 | SpringApplication.run(Main.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/main/java/com/nike/backstopper/springboot3webfluxsample/Main.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springboot3webfluxsample; 2 | 3 | import com.nike.backstopper.springboot3webfluxsample.config.SampleSpringboot3WebFluxSpringConfig; 4 | 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.context.annotation.Import; 8 | 9 | /** 10 | * Starts up the Backstopper Spring Boot 3 WebFlux Sample server (on port 8080 by default). 11 | * 12 | * @author Nic Munroe 13 | */ 14 | @SpringBootApplication 15 | @Import(SampleSpringboot3WebFluxSpringConfig.class) 16 | public class Main { 17 | public static void main(String[] args) { 18 | SpringApplication.run(Main.class, args); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | 2 | Backstopper 3 | =========== 4 | 5 | Licenses for individual components of this project can be found in a 6 | LICENSE..txt file, located in the 'license' directory at the 7 | root of the project. See those files for the terms of the license for 8 | each component. 9 | 10 | ------------------------------------------------------------------------------- 11 | 12 | This product contains a modified portion of software 13 | provided by Munroe Enterprises, Inc.: 14 | 15 | * LICENSE: 16 | * license/LICENSE.errorhandling.txt (ISC License) 17 | 18 | 19 | This product contains a modified portion of 'Apache Commons Lang', a Java 20 | library, which can be obtained at: 21 | 22 | * LICENSE: 23 | * license/LICENSE.apache_commons_lang.txt (Apache License 2.0) 24 | * HOMEPAGE: 25 | * https://commons.apache.org/proper/commons-lang/ 26 | -------------------------------------------------------------------------------- /license/LICENSE.errorhandling.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Munroe Enterprises, Inc., munroe.enterprises@gmail.com 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /nike-internal-util/src/test/java/com/nike/internal/util/testing/TestUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.internal.util.testing; 2 | 3 | import org.junit.Test; 4 | 5 | import java.net.ServerSocket; 6 | 7 | import static org.assertj.core.api.Assertions.assertThat; 8 | import static org.assertj.core.api.Assertions.catchThrowable; 9 | 10 | /** 11 | * Tests the functionality of {@link TestUtils}. 12 | */ 13 | public class TestUtilsTest { 14 | 15 | @Test 16 | public void findFreePort_finds_an_open_port() { 17 | // given 18 | int freePort = TestUtils.findFreePort(); 19 | 20 | // when 21 | Throwable ex = catchThrowable(() -> { 22 | ServerSocket serverSocket = new ServerSocket(freePort); 23 | serverSocket.close(); 24 | }); 25 | 26 | // then 27 | assertThat(ex).isNull(); 28 | } 29 | } -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/src/test/java/com/nike/backstopper/springsample/error/SampleProjectApiErrorsImplTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springsample.error; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrorsTestBase; 5 | 6 | /** 7 | * Extends {@link ProjectApiErrorsTestBase} in order to inherit tests that will verify the correctness of this 8 | * project's {@link SampleProjectApiErrorsImpl}. 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public class SampleProjectApiErrorsImplTest extends ProjectApiErrorsTestBase { 13 | 14 | private final ProjectApiErrors projectApiErrors = new SampleProjectApiErrorsImpl(); 15 | 16 | @Override 17 | protected ProjectApiErrors getProjectApiErrors() { 18 | return projectApiErrors; 19 | } 20 | } -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/apierror/projectspecificinfo/ProjectApiErrorsCoreApiErrorsOnlyTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.apierror.projectspecificinfo; 2 | 3 | import com.nike.backstopper.apierror.testutil.ProjectApiErrorsForTesting; 4 | 5 | /** 6 | * Extension of {@link ProjectApiErrorsTestBase} that tests the case where the {@link ProjectApiErrors} doesn't have 7 | * any project-specific ApiErrors and thus only contains core errors. 8 | * 9 | * @author Nic Munroe 10 | */ 11 | public class ProjectApiErrorsCoreApiErrorsOnlyTest extends ProjectApiErrorsTestBase { 12 | 13 | private static final ProjectApiErrors testProjectApiErrors = ProjectApiErrorsForTesting.withProjectSpecificData(null, null); 14 | 15 | @Override 16 | protected ProjectApiErrors getProjectApiErrors() { 17 | return testProjectApiErrors; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/src/test/java/testonly/componenttest/spring/reusable/error/SampleProjectApiErrorsImplTest.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.error; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrorsTestBase; 5 | 6 | /** 7 | * Extends {@link ProjectApiErrorsTestBase} in order to inherit tests that will verify the correctness of 8 | * {@link SampleProjectApiErrorsImpl}. 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public class SampleProjectApiErrorsImplTest extends ProjectApiErrorsTestBase { 13 | 14 | private final ProjectApiErrors projectApiErrors = new SampleProjectApiErrorsImpl(); 15 | 16 | @Override 17 | protected ProjectApiErrors getProjectApiErrors() { 18 | return projectApiErrors; 19 | } 20 | } -------------------------------------------------------------------------------- /testonly/testonly-spring-webmvc-reusable-test-support/src/test/java/testonly/componenttest/spring/reusable/error/SampleProjectApiErrorsImplTest.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.error; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrorsTestBase; 5 | 6 | /** 7 | * Extends {@link ProjectApiErrorsTestBase} in order to inherit tests that will verify the correctness of 8 | * {@link SampleProjectApiErrorsImpl}. 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public class SampleProjectApiErrorsImplTest extends ProjectApiErrorsTestBase { 13 | 14 | private final ProjectApiErrors projectApiErrors = new SampleProjectApiErrorsImpl(); 15 | 16 | @Override 17 | protected ProjectApiErrors getProjectApiErrors() { 18 | return projectApiErrors; 19 | } 20 | } -------------------------------------------------------------------------------- /backstopper-servlet-api/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":backstopper-core"), 6 | ) 7 | compileOnly( 8 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 9 | "jakarta.servlet:jakarta.servlet-api:$servletApiVersion", 10 | ) 11 | testImplementation( 12 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 13 | "junit:junit:$junitVersion", 14 | "org.mockito:mockito-core:$mockitoVersion", 15 | "ch.qos.logback:logback-classic:$logbackVersion", 16 | "org.assertj:assertj-core:$assertJVersion", 17 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 18 | "org.hamcrest:hamcrest-all:$hamcrestVersion", 19 | "jakarta.servlet:jakarta.servlet-api:$servletApiVersion", 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/exception/network/DownstreamRequestOrResponseBodyFailedValidationException.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception.network; 2 | 3 | /** 4 | * Exception for when a downstream server's request or response body failed validation (e.g. JSR 303 validation). 5 | * Since this usually happens because the request/response body was passed to 6 | * {@link com.nike.backstopper.service.FailFastServersideValidationService} for validation and failed, 7 | * the {@link #getCause()} is likely (but not guaranteed) to be a 8 | * {@link com.nike.backstopper.exception.ServersideValidationError}. 9 | * 10 | * @author Nic Munroe 11 | */ 12 | public class DownstreamRequestOrResponseBodyFailedValidationException extends NetworkExceptionBase { 13 | public DownstreamRequestOrResponseBodyFailedValidationException(Throwable cause, String connectionType) { 14 | super(cause, connectionType); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/test/java/com/nike/backstopper/springbootsample/error/SampleProjectApiErrorsImplTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springbootsample.error; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrorsTestBase; 5 | import com.nike.backstopper.springboot3webfluxsample.error.SampleProjectApiErrorsImpl; 6 | 7 | /** 8 | * Extends {@link ProjectApiErrorsTestBase} in order to inherit tests that will verify the correctness of this 9 | * project's {@link SampleProjectApiErrorsImpl}. 10 | * 11 | * @author Nic Munroe 12 | */ 13 | public class SampleProjectApiErrorsImplTest extends ProjectApiErrorsTestBase { 14 | 15 | private final ProjectApiErrors projectApiErrors = new SampleProjectApiErrorsImpl(); 16 | 17 | @Override 18 | protected ProjectApiErrors getProjectApiErrors() { 19 | return projectApiErrors; 20 | } 21 | } -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/test/java/com/nike/backstopper/springbootsample/error/SampleProjectApiErrorsImplTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springbootsample.error; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrorsTestBase; 5 | import com.nike.backstopper.springboot3webmvcsample.error.SampleProjectApiErrorsImpl; 6 | 7 | /** 8 | * Extends {@link ProjectApiErrorsTestBase} in order to inherit tests that will verify the correctness of this 9 | * project's {@link SampleProjectApiErrorsImpl}. 10 | * 11 | * @author Nic Munroe 12 | */ 13 | public class SampleProjectApiErrorsImplTest extends ProjectApiErrorsTestBase { 14 | 15 | private final ProjectApiErrors projectApiErrors = new SampleProjectApiErrorsImpl(); 16 | 17 | @Override 18 | protected ProjectApiErrors getProjectApiErrors() { 19 | return projectApiErrors; 20 | } 21 | } -------------------------------------------------------------------------------- /backstopper-core/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":nike-internal-util"), 6 | "org.slf4j:slf4j-api:$slf4jVersion", 7 | "jakarta.inject:jakarta.inject-api:$jakartaInjectVersion", 8 | "jakarta.validation:jakarta.validation-api:$jakartaValidationVersion" 9 | ) 10 | compileOnly( 11 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" 12 | ) 13 | testImplementation( 14 | "junit:junit:$junitVersion", 15 | "org.mockito:mockito-core:$mockitoVersion", 16 | "com.fasterxml.jackson.core:jackson-core:$jacksonVersion", 17 | "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", 18 | "ch.qos.logback:logback-classic:$logbackVersion", 19 | "org.assertj:assertj-core:$assertJVersion", 20 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 21 | "org.hamcrest:hamcrest-all:$hamcrestVersion", 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/handler/listener/impl/ListenerTestBase.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.listener.impl; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.handler.listener.ApiExceptionHandlerListenerResult; 5 | 6 | import java.util.Collection; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | /** 11 | * Base class for the test classes testing the various {@link com.nike.backstopper.handler.listener.ApiExceptionHandlerListener} implementations. 12 | * provides common helper methods. 13 | * 14 | * @author Nic Munroe 15 | */ 16 | public abstract class ListenerTestBase { 17 | 18 | protected void validateResponse(ApiExceptionHandlerListenerResult result, boolean expectedShouldHandle, Collection expectedErrors) { 19 | if (!expectedShouldHandle) { 20 | assertThat(result.shouldHandleResponse).isFalse(); 21 | return; 22 | } 23 | 24 | assertThat(result.errors).containsExactlyInAnyOrderElementsOf(expectedErrors); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | test { 4 | useJUnitPlatform() 5 | } 6 | 7 | dependencies { 8 | compileOnly( 9 | project(":backstopper-spring-web-flux"), 10 | project(":backstopper-custom-validators"), 11 | "org.springframework:spring-context:$spring6_0Version", 12 | "org.springframework:spring-webflux:$spring6_0Version", 13 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 14 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 15 | "io.rest-assured:rest-assured:$restAssuredVersion", 16 | // Pulling in commons-codec manually to avoid vulnerability warning coming from RestAssured transitive dep. 17 | "commons-codec:commons-codec:$commonsCodecVersion", 18 | "org.assertj:assertj-core:$assertJVersion", 19 | ) 20 | testImplementation( 21 | project(":backstopper-reusable-tests-junit5"), 22 | "org.springframework:spring-webflux:$spring6_0Version", 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /nike-internal-util/README.md: -------------------------------------------------------------------------------- 1 | # Nike Internal Util 2 | 3 | Requires Java 17 or later. (If you're needing this for Java 7+ instead, see the 4 | [1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x/nike-internal-util).) 5 | 6 | This is a small utilities library that provides some reusable helper methods and classes. It is "internal" in the sense 7 | that it is not intended to be directly pulled in and used by non-Nike projects. That said you can use it if you want to, 8 | just be aware that some liberties might be taken regarding version numbers, backwards compatibility, etc over time when 9 | compared with libraries specifically intended for public consumption. 10 | 11 | ## Backstopper Info 12 | 13 | This library may move into its own repository at some point. For now it's piggybacking on the Backstopper repository. 14 | See the [base project README.md](../README.md), [User Guide](../USER_GUIDE.md), and Backstopper repository source code 15 | and javadocs for Backstopper information. 16 | 17 | ## License 18 | 19 | Nike Internal Util is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 20 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/src/test/java/jsr303convention/VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.backstopper.apierror.contract.jsr303convention.VerifyEnumsReferencedByStringConvertsToClassTypeJsr303AnnotationsAreJacksonCaseInsensitiveTest; 5 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 6 | 7 | /** 8 | * Makes sure that any Enums referenced by {@link StringConvertsToClassType} JSR 303 annotations are case insensitive if 9 | * they are marked with {@link StringConvertsToClassType#allowCaseInsensitiveEnumMatch()} set to true. 10 | */ 11 | public class VerifyStringConvertsToClassTypeAnnotationsAreValidTest 12 | extends VerifyEnumsReferencedByStringConvertsToClassTypeJsr303AnnotationsAreJacksonCaseInsensitiveTest { 13 | 14 | @Override 15 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 16 | return ApplicationJsr303AnnotationTroller.getInstance(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webmvc-reusable-test-support/src/test/java/jsr303convention/VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.backstopper.apierror.contract.jsr303convention.VerifyEnumsReferencedByStringConvertsToClassTypeJsr303AnnotationsAreJacksonCaseInsensitiveTest; 5 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 6 | 7 | /** 8 | * Makes sure that any Enums referenced by {@link StringConvertsToClassType} JSR 303 annotations are case insensitive if 9 | * they are marked with {@link StringConvertsToClassType#allowCaseInsensitiveEnumMatch()} set to true. 10 | */ 11 | public class VerifyStringConvertsToClassTypeAnnotationsAreValidTest 12 | extends VerifyEnumsReferencedByStringConvertsToClassTypeJsr303AnnotationsAreJacksonCaseInsensitiveTest { 13 | 14 | @Override 15 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 16 | return ApplicationJsr303AnnotationTroller.getInstance(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /nike-internal-util/src/main/java/com/nike/internal/util/testing/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.nike.internal.util.testing; 2 | 3 | import java.io.IOException; 4 | import java.net.ServerSocket; 5 | 6 | /** 7 | * Contains static helper methods useful during testing. 8 | */ 9 | public class TestUtils { 10 | 11 | // Private constructor - use the static helper methods for everything in this class. 12 | private TestUtils() { 13 | // Do nothing 14 | } 15 | 16 | /** 17 | * Finds an unused port on the machine hosting the currently running JVM. 18 | *

19 | * Does not throw any checked {@link IOException} that occurs while trying to find a free port. If one occurs, 20 | * it will be wrapped in a {@link RuntimeException}. 21 | */ 22 | public static int findFreePort() { 23 | try (ServerSocket serverSocket = new ServerSocket(0)) { 24 | serverSocket.setReuseAddress(true); 25 | return serverSocket.getLocalPort(); 26 | } 27 | catch (IOException e) { 28 | throw new RuntimeException("Error while trying to find a free port.", e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /testonly/testonly-spring-6_0-webmvc/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-spring6-webmvc 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the [backstopper-spring-web-mvc](../../backstopper-spring-web-mvc) 10 | module's functionality works as expected in Spring 6.0.x environments, for both classpath-scanning and direct-import 11 | Backstopper configuration use cases. 12 | 13 | ## More Info 14 | 15 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 16 | source code and javadocs for all further information. 17 | 18 | ## License 19 | 20 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 21 | -------------------------------------------------------------------------------- /testonly/testonly-spring-6_1-webmvc/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-spring6-webmvc 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the [backstopper-spring-web-mvc](../../backstopper-spring-web-mvc) 10 | module's functionality works as expected in Spring 6.1.x environments, for both classpath-scanning and direct-import 11 | Backstopper configuration use cases. 12 | 13 | ## More Info 14 | 15 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 16 | source code and javadocs for all further information. 17 | 18 | ## License 19 | 20 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 21 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - '**' 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Set up JDK 17 16 | uses: actions/setup-java@v4 17 | with: 18 | distribution: 'temurin' 19 | java-version: '17' 20 | cache: 'gradle' 21 | - name: Validate Gradle Wrapper Checksums 22 | uses: gradle/actions/wrapper-validation@v3 23 | - name: Build with Gradle 24 | run: ./gradlew clean build 25 | - name: Upload coverage report to CodeCov 26 | uses: codecov/codecov-action@v4 27 | with: 28 | files: build/reports/jacoco/jacocoRootReport/jacocoRootReport.xml 29 | fail_ci_if_error: true 30 | verbose: true 31 | token: ${{ secrets.CODECOV_TOKEN }} 32 | - name: Upload reports and test results to GitHub 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: reports-and-test-results 36 | path: | 37 | build/reports/ 38 | build/test-results/ 39 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_0-webflux/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-springboot3_0-webflux 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the 10 | [backstopper-spring-web-flux](../../backstopper-spring-web-flux) module's functionality works as expected in 11 | Spring Boot 3.0.x WebFlux (Netty) environments, for both classpath-scanning and direct-import Backstopper configuration 12 | use cases. 13 | 14 | ## More Info 15 | 16 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 17 | source code and javadocs for all further information. 18 | 19 | ## License 20 | 21 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 22 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_1-webflux/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-springboot3_1-webflux 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the 10 | [backstopper-spring-web-flux](../../backstopper-spring-web-flux) module's functionality works as expected in 11 | Spring Boot 3.1.x WebFlux (Netty) environments, for both classpath-scanning and direct-import Backstopper configuration 12 | use cases. 13 | 14 | ## More Info 15 | 16 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 17 | source code and javadocs for all further information. 18 | 19 | ## License 20 | 21 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 22 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_2-webflux/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-springboot3_2-webflux 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the 10 | [backstopper-spring-web-flux](../../backstopper-spring-web-flux) module's functionality works as expected in 11 | Spring Boot 3.2.x WebFlux (Netty) environments, for both classpath-scanning and direct-import Backstopper configuration 12 | use cases. 13 | 14 | ## More Info 15 | 16 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 17 | source code and javadocs for all further information. 18 | 19 | ## License 20 | 21 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 22 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_3-webflux/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-springboot3_3-webflux 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the 10 | [backstopper-spring-web-flux](../../backstopper-spring-web-flux) module's functionality works as expected in 11 | Spring Boot 3.3.x WebFlux (Netty) environments, for both classpath-scanning and direct-import Backstopper configuration 12 | use cases. 13 | 14 | ## More Info 15 | 16 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 17 | source code and javadocs for all further information. 18 | 19 | ## License 20 | 21 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 22 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_0-webmvc/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-springboot3_0-webmvc 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the 10 | [backstopper-spring-boot3-webmvc](../../backstopper-spring-boot3-webmvc) module's functionality works as expected in 11 | Spring Boot 3.0.x Web MVC (Servlet) environments, for both classpath-scanning and direct-import Backstopper configuration 12 | use cases. 13 | 14 | ## More Info 15 | 16 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 17 | source code and javadocs for all further information. 18 | 19 | ## License 20 | 21 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 22 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_1-webmvc/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-springboot3_1-webmvc 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the 10 | [backstopper-spring-boot3-webmvc](../../backstopper-spring-boot3-webmvc) module's functionality works as expected in 11 | Spring Boot 3.1.x Web MVC (Servlet) environments, for both classpath-scanning and direct-import Backstopper configuration 12 | use cases. 13 | 14 | ## More Info 15 | 16 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 17 | source code and javadocs for all further information. 18 | 19 | ## License 20 | 21 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 22 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_2-webmvc/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-springboot3_2-webmvc 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the 10 | [backstopper-spring-boot3-webmvc](../../backstopper-spring-boot3-webmvc) module's functionality works as expected in 11 | Spring Boot 3.2.x Web MVC (Servlet) environments, for both classpath-scanning and direct-import Backstopper configuration 12 | use cases. 13 | 14 | ## More Info 15 | 16 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 17 | source code and javadocs for all further information. 18 | 19 | ## License 20 | 21 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 22 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_3-webmvc/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - testonly-springboot3_3-webmvc 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This submodule contains tests to verify that the 10 | [backstopper-spring-boot3-webmvc](../../backstopper-spring-boot3-webmvc) module's functionality works as expected in 11 | Spring Boot 3.3.x Web MVC (Servlet) environments, for both classpath-scanning and direct-import Backstopper configuration 12 | use cases. 13 | 14 | ## More Info 15 | 16 | See the [base project README.md](../../README.md), [User Guide](../../USER_GUIDE.md), and Backstopper repository 17 | source code and javadocs for all further information. 18 | 19 | ## License 20 | 21 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 22 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/exception/network/NetworkExceptionBase.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception.network; 2 | 3 | /** 4 | * Base class for network exceptions that occur (e.g. during HTTP client calls to other systems). 5 | * 6 | * @author Nic Munroe 7 | */ 8 | public abstract class NetworkExceptionBase extends RuntimeException { 9 | 10 | private final String connectionType; 11 | 12 | @SuppressWarnings("WeakerAccess") 13 | public NetworkExceptionBase(Throwable cause, String connectionType) { 14 | super(cause); 15 | this.connectionType = connectionType; 16 | } 17 | 18 | /** 19 | * @return The type of connection that failed. Answers the question: "what server was I trying to talk to when the 20 | * exception occurred?". Useful in case the exception handler needs to know what server was being talked 21 | * to in order to know how to process any response data contained in the exception, for example how to 22 | * parse a HTTP Status Code 400 error for useful validation data to return to the end user. 23 | */ 24 | public String getConnectionType() { 25 | return connectionType; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/apierror/sample/SampleProjectApiErrorsBaseTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.apierror.sample; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrorsTestBase; 6 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRange; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Extension of {@link ProjectApiErrorsTestBase} that tests {@link SampleProjectApiErrorsBase}. 12 | * 13 | * @author Nic Munroe 14 | */ 15 | public class SampleProjectApiErrorsBaseTest extends ProjectApiErrorsTestBase { 16 | 17 | private static final ProjectApiErrors testProjectApiErrors = new SampleProjectApiErrorsBase() { 18 | @Override 19 | protected List getProjectSpecificApiErrors() { 20 | return null; 21 | } 22 | 23 | @Override 24 | protected ProjectSpecificErrorCodeRange getProjectSpecificErrorCodeRange() { 25 | return null; 26 | } 27 | }; 28 | 29 | @Override 30 | protected ProjectApiErrors getProjectApiErrors() { 31 | return testProjectApiErrors; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/src/main/java/com/nike/backstopper/springsample/model/RgbColor.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springsample.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | 5 | /** 6 | * An enum used by {@link SampleModel} for showing how 7 | * {@link com.nike.backstopper.validation.constraints.StringConvertsToClassType} can work with enums. Note 8 | * the {@link #toRgbColor(String)} annotated with {@link JsonCreator}, which allows callers to pass in lower or 9 | * mixed case versions of the enum values and still have them automatically deserialized to the correct enum. 10 | * This special {@link JsonCreator} method is only necessary if you want to support case-insensitive enum validation 11 | * when deserializing. 12 | */ 13 | public enum RgbColor { 14 | RED, GREEN, BLUE; 15 | 16 | @JsonCreator 17 | @SuppressWarnings("unused") 18 | public static RgbColor toRgbColor(String colorString) { 19 | for (RgbColor color : values()) { 20 | if (color.name().equalsIgnoreCase(colorString)) 21 | return color; 22 | } 23 | throw new IllegalArgumentException( 24 | "Cannot convert the string: \"" + colorString + "\" to a valid RgbColor enum value." 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/exception/ServersideValidationErrorTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | import jakarta.validation.ConstraintViolation; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | 13 | /** 14 | * Tests the functionality of {@link ServersideValidationError}. Since there isn't really much functionality, 15 | * this just verifies that the constructors/etc work without blowing up. 16 | * 17 | * @author Nic Munroe 18 | */ 19 | public class ServersideValidationErrorTest { 20 | 21 | @Test 22 | public void verifyObjectThatFailedValidationIsSet() { 23 | Object someObj = new Object(); 24 | ServersideValidationError ex = new ServersideValidationError(someObj, null); 25 | assertThat(ex.getObjectThatFailedValidation(), is(someObj)); 26 | } 27 | 28 | @Test 29 | public void verifyViolationsIsSet() { 30 | Set> violations = new HashSet<>(); 31 | ServersideValidationError ex = new ServersideValidationError(null, violations); 32 | assertThat(ex.getViolations(), is(violations)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/main/java/com/nike/backstopper/springboot3webmvcsample/model/RgbColor.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springboot3webmvcsample.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | 5 | /** 6 | * An enum used by {@link SampleModel} for showing how 7 | * {@link com.nike.backstopper.validation.constraints.StringConvertsToClassType} can work with enums. Note 8 | * the {@link #toRgbColor(String)} annotated with {@link JsonCreator}, which allows callers to pass in lower or 9 | * mixed case versions of the enum values and still have them automatically deserialized to the correct enum. 10 | * This special {@link JsonCreator} method is only necessary if you want to support case-insensitive enum validation 11 | * when deserializing. 12 | */ 13 | public enum RgbColor { 14 | RED, GREEN, BLUE; 15 | 16 | @JsonCreator 17 | @SuppressWarnings("unused") 18 | public static RgbColor toRgbColor(String colorString) { 19 | for (RgbColor color : values()) { 20 | if (color.name().equalsIgnoreCase(colorString)) 21 | return color; 22 | } 23 | throw new IllegalArgumentException( 24 | "Cannot convert the string: \"" + colorString + "\" to a valid RgbColor enum value." 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/main/java/com/nike/backstopper/springboot3webfluxsample/model/RgbColor.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springboot3webfluxsample.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | 5 | /** 6 | * An enum used by {@link SampleModel} for showing how 7 | * {@link com.nike.backstopper.validation.constraints.StringConvertsToClassType} can work with enums. Note 8 | * the {@link #toRgbColor(String)} annotated with {@link JsonCreator}, which allows callers to pass in lower or 9 | * mixed case versions of the enum values and still have them automatically deserialized to the correct enum. 10 | * This special {@link JsonCreator} method is only necessary if you want to support case-insensitive enum validation 11 | * when deserializing. 12 | */ 13 | public enum RgbColor { 14 | RED, GREEN, BLUE; 15 | 16 | @JsonCreator 17 | @SuppressWarnings("unused") 18 | public static RgbColor toRgbColor(String colorString) { 19 | for (RgbColor color : values()) { 20 | if (color.name().equalsIgnoreCase(colorString)) 21 | return color; 22 | } 23 | throw new IllegalArgumentException( 24 | "Cannot convert the string: \"" + colorString + "\" to a valid RgbColor enum value." 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/src/main/java/testonly/componenttest/spring/reusable/model/RgbColor.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | 5 | /** 6 | * An enum used by {@link SampleModel} for showing how 7 | * {@link com.nike.backstopper.validation.constraints.StringConvertsToClassType} can work with enums. Note 8 | * the {@link #toRgbColor(String)} annotated with {@link JsonCreator}, which allows callers to pass in lower or 9 | * mixed case versions of the enum values and still have them automatically deserialized to the correct enum. 10 | * This special {@link JsonCreator} method is only necessary if you want to support case-insensitive enum validation 11 | * when deserializing. 12 | */ 13 | public enum RgbColor { 14 | RED, GREEN, BLUE; 15 | 16 | @JsonCreator 17 | @SuppressWarnings("unused") 18 | public static RgbColor toRgbColor(String colorString) { 19 | for (RgbColor color : values()) { 20 | if (color.name().equalsIgnoreCase(colorString)) 21 | return color; 22 | } 23 | throw new IllegalArgumentException( 24 | "Cannot convert the string: \"" + colorString + "\" to a valid RgbColor enum value." 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webmvc-reusable-test-support/src/main/java/testonly/componenttest/spring/reusable/model/RgbColor.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | 5 | /** 6 | * An enum used by {@link SampleModel} for showing how 7 | * {@link com.nike.backstopper.validation.constraints.StringConvertsToClassType} can work with enums. Note 8 | * the {@link #toRgbColor(String)} annotated with {@link JsonCreator}, which allows callers to pass in lower or 9 | * mixed case versions of the enum values and still have them automatically deserialized to the correct enum. 10 | * This special {@link JsonCreator} method is only necessary if you want to support case-insensitive enum validation 11 | * when deserializing. 12 | */ 13 | public enum RgbColor { 14 | RED, GREEN, BLUE; 15 | 16 | @JsonCreator 17 | @SuppressWarnings("unused") 18 | public static RgbColor toRgbColor(String colorString) { 19 | for (RgbColor color : values()) { 20 | if (color.name().equalsIgnoreCase(colorString)) 21 | return color; 22 | } 23 | throw new IllegalArgumentException( 24 | "Cannot convert the string: \"" + colorString + "\" to a valid RgbColor enum value." 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/exception/StackTraceLoggingBehavior.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception; 2 | 3 | /** 4 | * An enum representing the options available for whether the stack trace is logged for a given exception caught and 5 | * handled by Backstopper. {@link ApiException} includes this as an optional field you can set which Backstopper will 6 | * honor when handling the {@link ApiException}. 7 | */ 8 | public enum StackTraceLoggingBehavior { 9 | /** 10 | * This option forces Backstopper to log the stack trace of the exception, even if it would normally not log the 11 | * stack trace (e.g. if the exception represents a 4xx error). 12 | */ 13 | FORCE_STACK_TRACE, 14 | /** 15 | * This option forces Backstopper to *not* log the stack trace of the exception, even if it would normally log the 16 | * stack trace (e.g. if the exception represents a 5xx error). 17 | */ 18 | FORCE_NO_STACK_TRACE, 19 | /** 20 | * This option lets Backstopper decide whether or not the stack trace of the exception should be logged. This 21 | * usually means the stack trace will be logged for an exception representing a 5xx error, and no stack trace for 22 | * exceptions representing a 4xx error. 23 | */ 24 | DEFER_TO_DEFAULT_BEHAVIOR 25 | } 26 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webmvc-reusable-test-support/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | test { 4 | useJUnitPlatform() 5 | } 6 | 7 | dependencies { 8 | compileOnly( 9 | project(":backstopper-spring-web-mvc"), 10 | project(":backstopper-custom-validators"), 11 | "org.springframework:spring-webmvc:$spring6_0Version", 12 | "org.eclipse.jetty:jetty-webapp:$jettyVersion", 13 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 14 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 15 | "jakarta.servlet:jakarta.servlet-api:$servletApiVersion", 16 | "com.fasterxml.jackson.core:jackson-core:$jacksonVersion", 17 | "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", 18 | "io.rest-assured:rest-assured:$restAssuredVersion", 19 | // Pulling in commons-codec manually to avoid vulnerability warning coming from RestAssured transitive dep. 20 | "commons-codec:commons-codec:$commonsCodecVersion", 21 | "org.assertj:assertj-core:$assertJVersion", 22 | ) 23 | testImplementation( 24 | project(":backstopper-reusable-tests-junit5"), 25 | "org.springframework:spring-webmvc:$spring6_0Version", 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/src/test/java/jsr303convention/VerifyJsr303ContractTest.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.backstopper.apierror.contract.jsr303convention.VerifyJsr303ValidationMessagesPointToApiErrorsTest; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 6 | 7 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 8 | 9 | /** 10 | * Verifies that *ALL* non-excluded JSR 303 validation annotations in this project have a message defined that maps to a 11 | * {@link com.nike.backstopper.apierror.ApiError} enum name from this project's {@link SampleProjectApiErrorsImpl}. 12 | */ 13 | public class VerifyJsr303ContractTest extends VerifyJsr303ValidationMessagesPointToApiErrorsTest { 14 | 15 | private static final ProjectApiErrors PROJECT_API_ERRORS = new SampleProjectApiErrorsImpl(); 16 | 17 | @Override 18 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 19 | return ApplicationJsr303AnnotationTroller.getInstance(); 20 | } 21 | 22 | @Override 23 | protected ProjectApiErrors getProjectApiErrors() { 24 | return PROJECT_API_ERRORS; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webmvc-reusable-test-support/src/test/java/jsr303convention/VerifyJsr303ContractTest.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.backstopper.apierror.contract.jsr303convention.VerifyJsr303ValidationMessagesPointToApiErrorsTest; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 6 | 7 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 8 | 9 | /** 10 | * Verifies that *ALL* non-excluded JSR 303 validation annotations in this project have a message defined that maps to a 11 | * {@link com.nike.backstopper.apierror.ApiError} enum name from this project's {@link SampleProjectApiErrorsImpl}. 12 | */ 13 | public class VerifyJsr303ContractTest extends VerifyJsr303ValidationMessagesPointToApiErrorsTest { 14 | 15 | private static final ProjectApiErrors PROJECT_API_ERRORS = new SampleProjectApiErrorsImpl(); 16 | 17 | @Override 18 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 19 | return ApplicationJsr303AnnotationTroller.getInstance(); 20 | } 21 | 22 | @Override 23 | protected ProjectApiErrors getProjectApiErrors() { 24 | return PROJECT_API_ERRORS; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'backstopper' 2 | 3 | // Published-artifact modules 4 | include "nike-internal-util", 5 | "backstopper-core", 6 | "backstopper-custom-validators", 7 | "backstopper-reusable-tests-junit5", 8 | "backstopper-jackson", 9 | "backstopper-servlet-api", 10 | "backstopper-spring-web", 11 | "backstopper-spring-web-mvc", 12 | "backstopper-spring-web-flux", 13 | "backstopper-spring-boot3-webmvc", 14 | // Test-only modules (not published) 15 | "testonly:testonly-spring-webmvc-reusable-test-support", 16 | "testonly:testonly-spring-webflux-reusable-test-support", 17 | "testonly:testonly-spring-6_0-webmvc", 18 | "testonly:testonly-spring-6_1-webmvc", 19 | "testonly:testonly-springboot3_0-webmvc", 20 | "testonly:testonly-springboot3_0-webflux", 21 | "testonly:testonly-springboot3_1-webmvc", 22 | "testonly:testonly-springboot3_1-webflux", 23 | "testonly:testonly-springboot3_2-webmvc", 24 | "testonly:testonly-springboot3_2-webflux", 25 | "testonly:testonly-springboot3_3-webmvc", 26 | "testonly:testonly-springboot3_3-webflux", 27 | // Sample modules (not published) 28 | "samples:sample-spring-web-mvc", 29 | "samples:sample-spring-boot3-webmvc", 30 | "samples:sample-spring-boot3-webflux" -------------------------------------------------------------------------------- /testonly/testonly-spring-6_0-webmvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | test { 4 | useJUnitPlatform() 5 | } 6 | 7 | dependencies { 8 | implementation( 9 | project(":backstopper-spring-web-mvc"), 10 | project(":backstopper-custom-validators"), 11 | "org.springframework:spring-webmvc:$spring6_0Version", 12 | "ch.qos.logback:logback-classic:$logbackVersion", 13 | "com.fasterxml.jackson.core:jackson-core:$jacksonVersion", 14 | "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", 15 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 16 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 17 | "org.eclipse.jetty:jetty-webapp:$jettyVersion", 18 | ) 19 | testImplementation( 20 | project(":backstopper-reusable-tests-junit5"), 21 | project(":testonly:testonly-spring-webmvc-reusable-test-support"), 22 | "org.junit.jupiter:junit-jupiter:$junit5Version", 23 | "org.mockito:mockito-core:$mockitoVersion", 24 | "org.assertj:assertj-core:$assertJVersion", 25 | "io.rest-assured:rest-assured:$restAssuredVersion", 26 | // Pulling in commons-codec manually to avoid vulnerability warning coming from RestAssured transitive dep. 27 | "commons-codec:commons-codec:$commonsCodecVersion", 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /testonly/testonly-spring-6_1-webmvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | test { 4 | useJUnitPlatform() 5 | } 6 | 7 | dependencies { 8 | implementation( 9 | project(":backstopper-spring-web-mvc"), 10 | project(":backstopper-custom-validators"), 11 | "org.springframework:spring-webmvc:$spring6_1Version", 12 | "ch.qos.logback:logback-classic:$logbackVersion", 13 | "com.fasterxml.jackson.core:jackson-core:$jacksonVersion", 14 | "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", 15 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 16 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 17 | "org.eclipse.jetty:jetty-webapp:$jettyVersion", 18 | ) 19 | testImplementation( 20 | project(":backstopper-reusable-tests-junit5"), 21 | project(":testonly:testonly-spring-webmvc-reusable-test-support"), 22 | "org.junit.jupiter:junit-jupiter:$junit5Version", 23 | "org.mockito:mockito-core:$mockitoVersion", 24 | "org.assertj:assertj-core:$assertJVersion", 25 | "io.rest-assured:rest-assured:$restAssuredVersion", 26 | // Pulling in commons-codec manually to avoid vulnerability warning coming from RestAssured transitive dep. 27 | "commons-codec:commons-codec:$commonsCodecVersion", 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/src/test/java/com/nike/backstopper/apierror/contract/jsr303convention/VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.apierror.contract.jsr303convention; 2 | 3 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 4 | 5 | /** 6 | * Makes sure that any Enums referenced by {@link StringConvertsToClassType} JSR 303 annotations are case insensitive if 7 | * they are marked with {@link StringConvertsToClassType#allowCaseInsensitiveEnumMatch()} set to true. 8 | * 9 | *

You can exclude annotation declarations (e.g. for unit test classes that are intended to violate the naming 10 | * convention) by making sure that the {@link ApplicationJsr303AnnotationTroller#ignoreAllAnnotationsAssociatedWithTheseProjectClasses()} 11 | * and {@link ApplicationJsr303AnnotationTroller#specificAnnotationDeclarationExclusionsForProject()} methods return 12 | * what you need, but you should not exclude any annotations in production code under normal circumstances. 13 | * 14 | * @author Nic Munroe 15 | */ 16 | public class VerifyStringConvertsToClassTypeAnnotationsAreValidTest 17 | extends VerifyEnumsReferencedByStringConvertsToClassTypeJsr303AnnotationsAreJacksonCaseInsensitiveTest { 18 | 19 | @Override 20 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 21 | return ApplicationJsr303AnnotationTroller.getInstance(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backstopper-spring-web-flux/src/test/java/com/nike/backstopper/handler/spring/webflux/componenttest/model/SampleModel.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.spring.webflux.componenttest.model; 2 | 3 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 4 | 5 | import org.hibernate.validator.constraints.Range; 6 | 7 | import jakarta.validation.constraints.NotBlank; 8 | import jakarta.validation.constraints.NotNull; 9 | 10 | public class SampleModel { 11 | @NotBlank(message = "FOO_STRING_CANNOT_BE_BLANK") 12 | public final String foo; 13 | 14 | @Range(message = "INVALID_RANGE_VALUE", min = 0, max = 42) 15 | public final String range_0_to_42; 16 | 17 | @NotNull(message = "RGB_COLOR_CANNOT_BE_NULL") 18 | @StringConvertsToClassType( 19 | message = "NOT_RGB_COLOR_ENUM", classType = RgbColor.class, allowCaseInsensitiveEnumMatch = true 20 | ) 21 | public final String rgb_color; 22 | 23 | public final Boolean throw_manual_error; 24 | 25 | @SuppressWarnings("unused") 26 | // Intentionally protected - here for deserialization support. 27 | protected SampleModel() { 28 | this(null, null, null, null); 29 | } 30 | 31 | public SampleModel(String foo, String range_0_to_42, String rgb_color, Boolean throw_manual_error) { 32 | this.foo = foo; 33 | this.range_0_to_42 = range_0_to_42; 34 | this.rgb_color = rgb_color; 35 | this.throw_manual_error = throw_manual_error; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /backstopper-reusable-tests-junit5/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | ext { 4 | mockito3Version = '3.12.4' 5 | javassistJava8Version = '3.29.0-GA' 6 | orgReflectionsUpdatedVersion = '0.9.12' 7 | } 8 | 9 | test { 10 | useJUnitPlatform() 11 | } 12 | 13 | dependencies { 14 | api( 15 | project(":backstopper-core"), 16 | project(":backstopper-custom-validators"), 17 | "com.fasterxml.jackson.core:jackson-core:$jacksonVersion", 18 | "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", 19 | "org.junit.jupiter:junit-jupiter:$junit5Version", 20 | "org.mockito:mockito-core:$mockito3Version", 21 | "org.assertj:assertj-core:$assertJVersion", 22 | "org.reflections:reflections:$orgReflectionsUpdatedVersion", 23 | "org.javassist:javassist:$javassistJava8Version" 24 | ) 25 | compileOnly( 26 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 27 | ) 28 | testImplementation( 29 | "org.junit.jupiter:junit-jupiter:$junit5Version", 30 | "org.slf4j:slf4j-api:$slf4jVersion", 31 | "ch.qos.logback:logback-classic:$logbackVersion", 32 | ) 33 | // Make gradle happy for gradle 9. 34 | // See: https://docs.gradle.org/8.10/userguide/upgrading_version_8.html#test_framework_implementation_dependencies 35 | testRuntimeOnly( 36 | "org.junit.platform:junit-platform-launcher" 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | test { 4 | useJUnitPlatform() 5 | } 6 | 7 | dependencies { 8 | implementation( 9 | project(":backstopper-spring-web-mvc"), 10 | project(":backstopper-custom-validators"), 11 | "org.springframework:spring-webmvc:$spring6_0Version", 12 | "ch.qos.logback:logback-classic:$logbackVersion", 13 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 14 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 15 | "org.eclipse.jetty:jetty-webapp:$jettyVersion" 16 | ) 17 | compileOnly( 18 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" 19 | ) 20 | testImplementation( 21 | project(":backstopper-reusable-tests-junit5"), 22 | "org.junit.jupiter:junit-jupiter:$junit5Version", 23 | "org.mockito:mockito-core:$mockitoVersion", 24 | "org.assertj:assertj-core:$assertJVersion", 25 | "io.rest-assured:rest-assured:$restAssuredVersion", 26 | // Pulling in commons-codec manually to avoid vulnerability warning coming from RestAssured transitive dep. 27 | "commons-codec:commons-codec:$commonsCodecVersion", 28 | ) 29 | } 30 | 31 | apply plugin: "application" 32 | application { 33 | setMainClass("com.nike.backstopper.springsample.Main") 34 | } 35 | 36 | run { 37 | systemProperties(System.getProperties()) 38 | } 39 | -------------------------------------------------------------------------------- /backstopper-reusable-tests-junit5/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - reusable-tests 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem.) 8 | 9 | This library contains some reusable unit test classes that should be integrated into every Backstopper-enabled 10 | project to guarantee that the conventions and rules that Backstopper requires are followed. This is a fairly easy 11 | process and is described in detail in the Backstopper User Guide in the [Reusable Unit Tests for Enforcing 12 | Backstopper Rules and Conventions](../USER_GUIDE.md#reusable_tests) section. 13 | 14 | Beyond that, the classes in this reusable-tests library are heavily documented with extensive javadocs, and the 15 | [sample applications](../README.md#samples) show concrete usage of these tests to enforce the rules and conventions. 16 | Please explore the source code and samples to learn more. 17 | 18 | ## More Info 19 | 20 | See the [base project README.md](../README.md), [User Guide](../USER_GUIDE.md), and Backstopper repository source 21 | code and javadocs for all further information. 22 | 23 | ## License 24 | 25 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 26 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/exception/WrapperException.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception; 2 | 3 | /** 4 | * Simple wrapper exception that you can use when you want the error handling system to handle the 5 | * {@link WrapperException#getCause()} of this instance rather than this instance itself, but still log the entire 6 | * stack trace (including this instance). This is often necessary in asynchronous scenarios where you want to add 7 | * stack trace info for the logs but don't want to obscure the true cause of the error. 8 | * 9 | * @author Nic Munroe 10 | */ 11 | @SuppressWarnings("WeakerAccess") 12 | public class WrapperException extends RuntimeException { 13 | 14 | protected String toStringCache; 15 | 16 | public WrapperException(String message, Throwable cause) { 17 | super(message, cause); 18 | } 19 | 20 | public WrapperException(Throwable cause) { 21 | super(cause); 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | if (toStringCache == null) { 27 | String cacheVal = super.toString(); 28 | if (getCause() != null) { 29 | String causeToString = getCause().toString(); 30 | if (!cacheVal.endsWith(causeToString)) { 31 | cacheVal += " -- Wrapped toString(): " + getCause().toString(); 32 | } 33 | } 34 | 35 | toStringCache = cacheVal; 36 | } 37 | 38 | return toStringCache; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webmvc-reusable-test-support/src/main/java/testonly/componenttest/spring/reusable/error/SampleProjectApiErrorsImpl.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.error; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRange; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRangeIntegerImpl; 6 | import com.nike.backstopper.apierror.sample.SampleProjectApiErrorsBase; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import jakarta.inject.Singleton; 13 | 14 | /** 15 | * Returns the project specific errors for the {@code testonly-spring*} component tests. 16 | */ 17 | @Singleton 18 | public class SampleProjectApiErrorsImpl extends SampleProjectApiErrorsBase { 19 | 20 | private static final List projectSpecificApiErrors = 21 | new ArrayList<>(Arrays.asList(SampleProjectApiError.values())); 22 | 23 | private static final ProjectSpecificErrorCodeRange errorCodeRange = new ProjectSpecificErrorCodeRangeIntegerImpl( 24 | 99100, 99200, "SAMPLE_PROJECT_API_ERRORS" 25 | ); 26 | 27 | @Override 28 | protected List getProjectSpecificApiErrors() { 29 | return projectSpecificApiErrors; 30 | } 31 | 32 | @Override 33 | protected ProjectSpecificErrorCodeRange getProjectSpecificErrorCodeRange() { 34 | return errorCodeRange; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/src/main/java/testonly/componenttest/spring/reusable/error/SampleProjectApiErrorsImpl.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.error; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRange; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRangeIntegerImpl; 6 | import com.nike.backstopper.apierror.sample.SampleProjectApiErrorsBase; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import jakarta.inject.Singleton; 13 | 14 | /** 15 | * Returns the project specific errors for the {@code testonly-spring*} component tests. 16 | */ 17 | @Singleton 18 | public class SampleProjectApiErrorsImpl extends SampleProjectApiErrorsBase { 19 | 20 | private static final List projectSpecificApiErrors = 21 | new ArrayList<>(Arrays.asList(SampleProjectApiError.values())); 22 | 23 | private static final ProjectSpecificErrorCodeRange errorCodeRange = new ProjectSpecificErrorCodeRangeIntegerImpl( 24 | 99100, 99200, "SAMPLE_PROJECT_API_ERRORS" 25 | ); 26 | 27 | @Override 28 | protected List getProjectSpecificApiErrors() { 29 | return projectSpecificApiErrors; 30 | } 31 | 32 | @Override 33 | protected ProjectSpecificErrorCodeRange getProjectSpecificErrorCodeRange() { 34 | return errorCodeRange; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /nike-internal-util/src/main/java/com/nike/internal/util/MapBuilder.java: -------------------------------------------------------------------------------- 1 | package com.nike.internal.util; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Simple fluent map builder that allows you to easily initialize static map fields with hardcoded data. We could use 8 | * the Guava library's ImmutableMap, but there's no reason to require pulling in all of Guava just for this one simple 9 | * thing. 10 | * 11 | * @author Nic Munroe 12 | */ 13 | @SuppressWarnings("WeakerAccess") 14 | public class MapBuilder { 15 | 16 | private final Map map = new HashMap<>(); 17 | 18 | private MapBuilder() { /* private to enforce builder pattern */ } 19 | 20 | public static MapBuilder builder() { 21 | return new MapBuilder<>(); 22 | } 23 | 24 | public static MapBuilder builder(K firstKey, V firstVal) { 25 | MapBuilder builder = new MapBuilder<>(); 26 | builder.put(firstKey, firstVal); 27 | return builder; 28 | } 29 | 30 | public MapBuilder put(K key, V value) { 31 | map.put(key, value); 32 | return this; 33 | } 34 | 35 | @SuppressWarnings("UnusedReturnValue") 36 | public MapBuilder putAll(Map otherMap) { 37 | for (Map.Entry entry : otherMap.entrySet()) { 38 | put(entry.getKey(), entry.getValue()); 39 | } 40 | return this; 41 | } 42 | 43 | public Map build() { 44 | return new HashMap<>(map); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /backstopper-spring-web-mvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":backstopper-servlet-api"), 6 | project(":backstopper-jackson"), 7 | project(":backstopper-spring-web"), 8 | ) 9 | compileOnly( 10 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 11 | "org.springframework:spring-webmvc:$spring6_0Version", 12 | "jakarta.servlet:jakarta.servlet-api:$servletApiVersion", 13 | ) 14 | testImplementation( 15 | project(":backstopper-core").sourceSets.test.output, 16 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 17 | "junit:junit:$junitVersion", 18 | "org.mockito:mockito-core:$mockitoVersion", 19 | "ch.qos.logback:logback-classic:$logbackVersion", 20 | "org.assertj:assertj-core:$assertJVersion", 21 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 22 | "org.hamcrest:hamcrest-all:$hamcrestVersion", 23 | "org.springframework:spring-test:$spring6_0Version", 24 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 25 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 26 | "jakarta.servlet:jakarta.servlet-api:$servletApiVersion", 27 | "org.springframework:spring-webmvc:$spring6_0Version", 28 | "org.springframework.security:spring-security-core:$springSecurityVersion", 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /backstopper-jackson/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - jackson 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem.) 8 | 9 | This library contains some helper classes for working in an environment that uses Jackson with Backstopper. 10 | 11 | ## Helper classes 12 | 13 | * **`JsonUtilWithDefaultErrorContractDTOSupport`** - A general-purpose JSON serializer that has built-in support for the 14 | default Backstopper error contract DTO. In particular: 15 | * If an error code is parseable to an integer then the JSON field for that error code will be serialized as a number 16 | rather than a string. 17 | * If an error has an empty metadata section then it will be omitted from the serialized JSON. 18 | * You can create a Jackson `ObjectMapper` with any combination of the above rules turned on or off by using the 19 | `generateErrorContractObjectMapper(...)` static factory method. 20 | 21 | ## More Info 22 | 23 | See the [base project README.md](../README.md), [User Guide](../USER_GUIDE.md), and Backstopper repository source code 24 | and javadocs for all further information. 25 | 26 | ## License 27 | 28 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 29 | -------------------------------------------------------------------------------- /backstopper-spring-web/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":backstopper-core"), 6 | ) 7 | compileOnly( 8 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 9 | "org.springframework:spring-web:$spring6_0Version", 10 | "org.springframework:spring-context:$spring6_0Version", 11 | ) 12 | testImplementation( 13 | project(":backstopper-core").sourceSets.test.output, 14 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 15 | "junit:junit:$junitVersion", 16 | "org.hamcrest:hamcrest-all:$hamcrestVersion", 17 | "org.mockito:mockito-core:$mockitoVersion", 18 | "ch.qos.logback:logback-classic:$logbackVersion", 19 | "org.assertj:assertj-core:$assertJVersion", 20 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 21 | "com.fasterxml.jackson.core:jackson-core:$jacksonVersion", 22 | "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion", 23 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 24 | "org.springframework:spring-web:$spring6_0Version", 25 | "org.springframework:spring-context:$spring6_0Version", 26 | "org.springframework.security:spring-security-core:$springSecurityVersion", 27 | "org.springframework:spring-webmvc:$spring6_0Version", 28 | "jakarta.servlet:jakarta.servlet-api:$servletApiVersion", 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /testonly/testonly-spring-6_0-webmvc/src/test/java/serverconfig/classpathscan/Spring_6_0_WebMvcClasspathScanConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.classpathscan; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 9 | 10 | import jakarta.validation.Validation; 11 | import jakarta.validation.Validator; 12 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 13 | 14 | /** 15 | * Spring config that uses {@link ComponentScan} to integrate Backstopper via classpath scanning of the 16 | * {@code com.nike.backstopper} package. 17 | * 18 | * @author Nic Munroe 19 | */ 20 | @Configuration 21 | @ComponentScan(basePackages = { 22 | // Component scan the core Backstopper+Spring support. 23 | "com.nike.backstopper", 24 | // Component scan the controller. 25 | "testonly.componenttest.spring.reusable.controller" 26 | }) 27 | @EnableWebMvc 28 | @SuppressWarnings("unused") 29 | public class Spring_6_0_WebMvcClasspathScanConfig { 30 | 31 | @Bean 32 | public ProjectApiErrors getProjectApiErrors() { 33 | return new SampleProjectApiErrorsImpl(); 34 | } 35 | 36 | @Bean 37 | public Validator getJsr303Validator() { 38 | //noinspection resource 39 | return Validation.buildDefaultValidatorFactory().getValidator(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /testonly/testonly-spring-6_1-webmvc/src/test/java/serverconfig/classpathscan/Spring_6_1_WebMvcClasspathScanConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.classpathscan; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 9 | 10 | import jakarta.validation.Validation; 11 | import jakarta.validation.Validator; 12 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 13 | 14 | /** 15 | * Spring config that uses {@link ComponentScan} to integrate Backstopper via classpath scanning of the 16 | * {@code com.nike.backstopper} package. 17 | * 18 | * @author Nic Munroe 19 | */ 20 | @Configuration 21 | @ComponentScan(basePackages = { 22 | // Component scan the core Backstopper+Spring support. 23 | "com.nike.backstopper", 24 | // Component scan the controller. 25 | "testonly.componenttest.spring.reusable.controller" 26 | }) 27 | @EnableWebMvc 28 | @SuppressWarnings("unused") 29 | public class Spring_6_1_WebMvcClasspathScanConfig { 30 | 31 | @Bean 32 | public ProjectApiErrors getProjectApiErrors() { 33 | return new SampleProjectApiErrorsImpl(); 34 | } 35 | 36 | @Bean 37 | public Validator getJsr303Validator() { 38 | //noinspection resource 39 | return Validation.buildDefaultValidatorFactory().getValidator(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /backstopper-spring-boot3-webmvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":backstopper-spring-web-mvc"), 6 | ) 7 | compileOnly( 8 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 9 | "org.springframework.boot:spring-boot-autoconfigure:$springboot3_3Version", 10 | "org.springframework:spring-webmvc:$spring6_0Version", 11 | "jakarta.servlet:jakarta.servlet-api:$servletApiVersion", 12 | ) 13 | testImplementation( 14 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 15 | "junit:junit:$junitVersion", 16 | "org.mockito:mockito-core:$mockitoVersion", 17 | "ch.qos.logback:logback-classic:$logbackVersion", 18 | "org.assertj:assertj-core:$assertJVersion", 19 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 20 | "jakarta.servlet:jakarta.servlet-api:$servletApiVersion", 21 | "io.rest-assured:rest-assured:$restAssuredVersion", 22 | // Pulling in commons-codec manually to avoid vulnerability warning coming from RestAssured transitive dep. 23 | "commons-codec:commons-codec:$commonsCodecVersion", 24 | "jakarta.servlet:jakarta.servlet-api:$servletApiVersion", 25 | "org.springframework.boot:spring-boot-starter-web:$springboot3_3Version", 26 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_0-webmvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_0Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-boot3-webmvc"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_0Version", 25 | "org.springframework.boot:spring-boot-starter-web", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | testImplementation( 30 | project(":backstopper-reusable-tests-junit5"), 31 | project(":testonly:testonly-spring-webmvc-reusable-test-support"), 32 | "org.junit.jupiter:junit-jupiter:$junit5Version", 33 | "org.mockito:mockito-core:$mockitoVersion", 34 | "org.assertj:assertj-core:$assertJVersion", 35 | "io.rest-assured:rest-assured", 36 | ) 37 | } 38 | 39 | // We're just running tests, not trying to stand up a real Springboot 3 server from gradle. 40 | // Disable the bootJar task so gradle doesn't fall over. 41 | bootJar.enabled = false 42 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_1-webmvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_1Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-boot3-webmvc"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_1Version", 25 | "org.springframework.boot:spring-boot-starter-web", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | testImplementation( 30 | project(":backstopper-reusable-tests-junit5"), 31 | project(":testonly:testonly-spring-webmvc-reusable-test-support"), 32 | "org.junit.jupiter:junit-jupiter:$junit5Version", 33 | "org.mockito:mockito-core:$mockitoVersion", 34 | "org.assertj:assertj-core:$assertJVersion", 35 | "io.rest-assured:rest-assured", 36 | ) 37 | } 38 | 39 | // We're just running tests, not trying to stand up a real Springboot 3 server from gradle. 40 | // Disable the bootJar task so gradle doesn't fall over. 41 | bootJar.enabled = false 42 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_2-webmvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_2Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-boot3-webmvc"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_2Version", 25 | "org.springframework.boot:spring-boot-starter-web", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | testImplementation( 30 | project(":backstopper-reusable-tests-junit5"), 31 | project(":testonly:testonly-spring-webmvc-reusable-test-support"), 32 | "org.junit.jupiter:junit-jupiter:$junit5Version", 33 | "org.mockito:mockito-core:$mockitoVersion", 34 | "org.assertj:assertj-core:$assertJVersion", 35 | "io.rest-assured:rest-assured", 36 | ) 37 | } 38 | 39 | // We're just running tests, not trying to stand up a real Springboot 3 server from gradle. 40 | // Disable the bootJar task so gradle doesn't fall over. 41 | bootJar.enabled = false 42 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_3-webmvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_3Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-boot3-webmvc"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_3Version", 25 | "org.springframework.boot:spring-boot-starter-web", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | testImplementation( 30 | project(":backstopper-reusable-tests-junit5"), 31 | project(":testonly:testonly-spring-webmvc-reusable-test-support"), 32 | "org.junit.jupiter:junit-jupiter:$junit5Version", 33 | "org.mockito:mockito-core:$mockitoVersion", 34 | "org.assertj:assertj-core:$assertJVersion", 35 | "io.rest-assured:rest-assured", 36 | ) 37 | } 38 | 39 | // We're just running tests, not trying to stand up a real Springboot 3 server from gradle. 40 | // Disable the bootJar task so gradle doesn't fall over. 41 | bootJar.enabled = false 42 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webmvc-reusable-test-support/src/test/java/jsr303convention/ApplicationJsr303AnnotationTroller.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.internal.util.Pair; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.AnnotatedElement; 8 | import java.util.List; 9 | import java.util.function.Predicate; 10 | 11 | /** 12 | * Extension of {@link ReflectionBasedJsr303AnnotationTrollerBase} used by {@link VerifyJsr303ContractTest} and 13 | * {@link VerifyStringConvertsToClassTypeAnnotationsAreValidTest}). 14 | */ 15 | public final class ApplicationJsr303AnnotationTroller extends ReflectionBasedJsr303AnnotationTrollerBase { 16 | 17 | private static final ApplicationJsr303AnnotationTroller INSTANCE = new ApplicationJsr303AnnotationTroller(); 18 | 19 | @SuppressWarnings("WeakerAccess") 20 | public static ApplicationJsr303AnnotationTroller getInstance() { 21 | return INSTANCE; 22 | } 23 | 24 | // Intentionally private - use {@code getInstance()} to retrieve the singleton instance of this class. 25 | private ApplicationJsr303AnnotationTroller() { 26 | super(); 27 | } 28 | 29 | @Override 30 | protected List> ignoreAllAnnotationsAssociatedWithTheseProjectClasses() { 31 | return null; 32 | } 33 | 34 | @Override 35 | protected List>> specificAnnotationDeclarationExclusionsForProject() { 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/src/test/java/jsr303convention/ApplicationJsr303AnnotationTroller.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.internal.util.Pair; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.AnnotatedElement; 8 | import java.util.List; 9 | import java.util.function.Predicate; 10 | 11 | /** 12 | * Extension of {@link ReflectionBasedJsr303AnnotationTrollerBase} used by {@link VerifyJsr303ContractTest} and 13 | * {@link VerifyStringConvertsToClassTypeAnnotationsAreValidTest}). 14 | */ 15 | public final class ApplicationJsr303AnnotationTroller extends ReflectionBasedJsr303AnnotationTrollerBase { 16 | 17 | private static final ApplicationJsr303AnnotationTroller INSTANCE = new ApplicationJsr303AnnotationTroller(); 18 | 19 | @SuppressWarnings("WeakerAccess") 20 | public static ApplicationJsr303AnnotationTroller getInstance() { 21 | return INSTANCE; 22 | } 23 | 24 | // Intentionally private - use {@code getInstance()} to retrieve the singleton instance of this class. 25 | private ApplicationJsr303AnnotationTroller() { 26 | super(); 27 | } 28 | 29 | @Override 30 | protected List> ignoreAllAnnotationsAssociatedWithTheseProjectClasses() { 31 | return null; 32 | } 33 | 34 | @Override 35 | protected List>> specificAnnotationDeclarationExclusionsForProject() { 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backstopper-spring-web-flux/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | dependencies { 4 | api( 5 | project(":backstopper-jackson"), 6 | project(":backstopper-spring-web"), 7 | ) 8 | compileOnly( 9 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 10 | "org.springframework:spring-webflux:$spring6_0Version", 11 | "org.springframework:spring-context:$spring6_0Version", 12 | ) 13 | testImplementation( 14 | project(":backstopper-core").sourceSets.test.output, 15 | project(":backstopper-custom-validators"), 16 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 17 | "junit:junit:$junitVersion", 18 | "org.mockito:mockito-core:$mockitoVersion", 19 | "ch.qos.logback:logback-classic:$logbackVersion", 20 | "org.assertj:assertj-core:$assertJVersion", 21 | "com.tngtech.java:junit-dataprovider:$junitDataproviderVersion", 22 | "io.rest-assured:rest-assured:$restAssuredVersion", 23 | // Pulling in commons-codec manually to avoid vulnerability warning coming from RestAssured transitive dep. 24 | "commons-codec:commons-codec:$commonsCodecVersion", 25 | "org.springframework.boot:spring-boot-starter-webflux:$springboot3_3Version", 26 | "jakarta.validation:jakarta.validation-api:$jakartaValidationVersion", 27 | "org.hibernate.validator:hibernate-validator:$hibernateValidatorVersion", 28 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /backstopper-spring-web-mvc/src/test/java/com/nike/backstopper/handler/spring/SpringApiExceptionHandlerUtilsTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.spring; 2 | 3 | import com.nike.backstopper.apierror.testing.base.BaseSpringEnabledValidationTestCase; 4 | import com.nike.backstopper.apierror.testutil.BarebonesCoreApiErrorForTesting; 5 | import com.nike.backstopper.model.DefaultErrorContractDTO; 6 | 7 | import org.junit.Test; 8 | import org.springframework.web.servlet.ModelAndView; 9 | 10 | import java.util.Arrays; 11 | 12 | import static org.hamcrest.CoreMatchers.is; 13 | import static org.hamcrest.MatcherAssert.assertThat; 14 | 15 | /** 16 | * Tests the functionality of {@link com.nike.backstopper.handler.ApiExceptionHandlerUtils}. 17 | * 18 | * @author Nic Munroe 19 | */ 20 | public class SpringApiExceptionHandlerUtilsTest extends BaseSpringEnabledValidationTestCase { 21 | 22 | @Test 23 | public void generateModelAndViewForErrorResponseShouldGenerateModelAndViewWithErrorContractAsOnlyModelObject() { 24 | DefaultErrorContractDTO 25 | erv = new DefaultErrorContractDTO("someRequestId", Arrays.asList(BarebonesCoreApiErrorForTesting.NO_ACCEPTABLE_REPRESENTATION, 26 | BarebonesCoreApiErrorForTesting.UNSUPPORTED_MEDIA_TYPE)); 27 | 28 | ModelAndView mav = new SpringApiExceptionHandlerUtils().generateModelAndViewForErrorResponse(erv, -1, null, null, null); 29 | assertThat(mav.getModel().size(), is(1)); 30 | assertThat(mav.getModel().values().iterator().next() == erv, is(true)); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_3Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-boot3-webmvc"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_3Version", 25 | "org.springframework.boot:spring-boot-starter-web", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | compileOnly( 30 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" 31 | ) 32 | testImplementation( 33 | project(":backstopper-reusable-tests-junit5"), 34 | "org.junit.jupiter:junit-jupiter:$junit5Version", 35 | "org.mockito:mockito-core:$mockitoVersion", 36 | "org.assertj:assertj-core:$assertJVersion", 37 | "io.rest-assured:rest-assured", 38 | ) 39 | } 40 | 41 | apply plugin: "application" 42 | application { 43 | setMainClass("com.nike.backstopper.springboot3webmvcsample.Main") 44 | } 45 | 46 | run { 47 | systemProperties(System.getProperties()) 48 | } 49 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/handler/listener/ApiExceptionHandlerListener.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.listener; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.handler.ApiExceptionHandlerBase; 5 | 6 | /** 7 | * An exception handler listener intended to be used by {@link ApiExceptionHandlerBase}. A concrete implementation of 8 | * this will look for exceptions matching some criteria and return a {@link ApiExceptionHandlerListenerResult} 9 | * representing either an "I want to handle this exception by using these {@link ApiError}s and this logging info" 10 | * result or an "I don't want to handle this exception" result. 11 | * 12 | * @author Nic Munroe 13 | */ 14 | public interface ApiExceptionHandlerListener { 15 | 16 | /** 17 | * @return A {@link ApiExceptionHandlerListenerResult} representing whether or not this instance wishes to handle 18 | * the exception. If {@link ApiExceptionHandlerListenerResult#shouldHandleResponse} is true then 19 | * {@link ApiExceptionHandlerBase} should handle the exception by using 20 | * {@link ApiExceptionHandlerListenerResult#errors} for the info returned to the client and should log the 21 | * info from {@link ApiExceptionHandlerListenerResult#extraDetailsForLogging}. Otherwise if 22 | * {@link ApiExceptionHandlerListenerResult#shouldHandleResponse} is false then 23 | * {@link ApiExceptionHandlerBase} should move on to the next listener. 24 | */ 25 | ApiExceptionHandlerListenerResult shouldHandleException(Throwable ex); 26 | 27 | } 28 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/test/java/jsr303convention/VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.backstopper.apierror.contract.jsr303convention.VerifyEnumsReferencedByStringConvertsToClassTypeJsr303AnnotationsAreJacksonCaseInsensitiveTest; 5 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 6 | 7 | /** 8 | * Makes sure that any Enums referenced by {@link StringConvertsToClassType} JSR 303 annotations are case insensitive if 9 | * they are marked with {@link StringConvertsToClassType#allowCaseInsensitiveEnumMatch()} set to true. 10 | * 11 | *

You can exclude annotation declarations (e.g. for unit test classes that are intended to violate the naming 12 | * convention) by making sure that the {@link ApplicationJsr303AnnotationTroller#ignoreAllAnnotationsAssociatedWithTheseProjectClasses()} 13 | * and {@link ApplicationJsr303AnnotationTroller#specificAnnotationDeclarationExclusionsForProject()} methods return 14 | * what you need, but you should not exclude any annotations in production code under normal circumstances. 15 | * 16 | * @author Nic Munroe 17 | */ 18 | public class VerifyStringConvertsToClassTypeAnnotationsAreValidTest 19 | extends VerifyEnumsReferencedByStringConvertsToClassTypeJsr303AnnotationsAreJacksonCaseInsensitiveTest { 20 | 21 | @Override 22 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 23 | return ApplicationJsr303AnnotationTroller.getInstance(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/test/java/jsr303convention/VerifyStringConvertsToClassTypeAnnotationsAreValidTest.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.backstopper.apierror.contract.jsr303convention.VerifyEnumsReferencedByStringConvertsToClassTypeJsr303AnnotationsAreJacksonCaseInsensitiveTest; 5 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 6 | 7 | /** 8 | * Makes sure that any Enums referenced by {@link StringConvertsToClassType} JSR 303 annotations are case insensitive if 9 | * they are marked with {@link StringConvertsToClassType#allowCaseInsensitiveEnumMatch()} set to true. 10 | * 11 | *

You can exclude annotation declarations (e.g. for unit test classes that are intended to violate the naming 12 | * convention) by making sure that the {@link ApplicationJsr303AnnotationTroller#ignoreAllAnnotationsAssociatedWithTheseProjectClasses()} 13 | * and {@link ApplicationJsr303AnnotationTroller#specificAnnotationDeclarationExclusionsForProject()} methods return 14 | * what you need, but you should not exclude any annotations in production code under normal circumstances. 15 | * 16 | * @author Nic Munroe 17 | */ 18 | public class VerifyStringConvertsToClassTypeAnnotationsAreValidTest 19 | extends VerifyEnumsReferencedByStringConvertsToClassTypeJsr303AnnotationsAreJacksonCaseInsensitiveTest { 20 | 21 | @Override 22 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 23 | return ApplicationJsr303AnnotationTroller.getInstance(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /testonly/testonly-spring-6_0-webmvc/src/test/java/serverconfig/directimport/Spring_6_0_WebMvcDirectImportConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.directimport; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.handler.spring.config.BackstopperSpringWebMvcConfig; 5 | 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 10 | 11 | import jakarta.validation.Validation; 12 | import jakarta.validation.Validator; 13 | import testonly.componenttest.spring.reusable.controller.SampleController; 14 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 15 | 16 | /** 17 | * Spring config that uses {@link Import} to integrate Backstopper via direct import of 18 | * {@link BackstopperSpringWebMvcConfig}. 19 | * 20 | * @author Nic Munroe 21 | */ 22 | @Configuration 23 | @Import({ 24 | // Import core Backstopper+Spring support. 25 | BackstopperSpringWebMvcConfig.class, 26 | // Import the controller. 27 | SampleController.class 28 | }) 29 | @EnableWebMvc 30 | @SuppressWarnings("unused") 31 | public class Spring_6_0_WebMvcDirectImportConfig { 32 | 33 | @Bean 34 | public ProjectApiErrors getProjectApiErrors() { 35 | return new SampleProjectApiErrorsImpl(); 36 | } 37 | 38 | @Bean 39 | public Validator getJsr303Validator() { 40 | //noinspection resource 41 | return Validation.buildDefaultValidatorFactory().getValidator(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /testonly/testonly-spring-6_1-webmvc/src/test/java/serverconfig/directimport/Spring_6_1_WebMvcDirectImportConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.directimport; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.handler.spring.config.BackstopperSpringWebMvcConfig; 5 | 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | import org.springframework.web.servlet.config.annotation.EnableWebMvc; 10 | 11 | import jakarta.validation.Validation; 12 | import jakarta.validation.Validator; 13 | import testonly.componenttest.spring.reusable.controller.SampleController; 14 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 15 | 16 | /** 17 | * Spring config that uses {@link Import} to integrate Backstopper via direct import of 18 | * {@link BackstopperSpringWebMvcConfig}. 19 | * 20 | * @author Nic Munroe 21 | */ 22 | @Configuration 23 | @Import({ 24 | // Import core Backstopper+Spring support. 25 | BackstopperSpringWebMvcConfig.class, 26 | // Import the controller. 27 | SampleController.class 28 | }) 29 | @EnableWebMvc 30 | @SuppressWarnings("unused") 31 | public class Spring_6_1_WebMvcDirectImportConfig { 32 | 33 | @Bean 34 | public ProjectApiErrors getProjectApiErrors() { 35 | return new SampleProjectApiErrorsImpl(); 36 | } 37 | 38 | @Bean 39 | public Validator getJsr303Validator() { 40 | //noinspection resource 41 | return Validation.buildDefaultValidatorFactory().getValidator(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_0-webflux/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_0Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-web-flux"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_0Version", 25 | "org.springframework.boot:spring-boot-starter-webflux", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | testImplementation( 30 | project(":backstopper-reusable-tests-junit5"), 31 | project(":testonly:testonly-spring-webflux-reusable-test-support"), 32 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 33 | "org.junit.jupiter:junit-jupiter:$junit5Version", 34 | "org.mockito:mockito-core:$mockitoVersion", 35 | "org.assertj:assertj-core:$assertJVersion", 36 | "io.rest-assured:rest-assured", 37 | ) 38 | } 39 | 40 | // We're just running tests, not trying to stand up a real Springboot 3 server from gradle. 41 | // Disable the bootJar task so gradle doesn't fall over. 42 | bootJar.enabled = false 43 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_1-webflux/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_1Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-web-flux"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_1Version", 25 | "org.springframework.boot:spring-boot-starter-webflux", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | testImplementation( 30 | project(":backstopper-reusable-tests-junit5"), 31 | project(":testonly:testonly-spring-webflux-reusable-test-support"), 32 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 33 | "org.junit.jupiter:junit-jupiter:$junit5Version", 34 | "org.mockito:mockito-core:$mockitoVersion", 35 | "org.assertj:assertj-core:$assertJVersion", 36 | "io.rest-assured:rest-assured", 37 | ) 38 | } 39 | 40 | // We're just running tests, not trying to stand up a real Springboot 3 server from gradle. 41 | // Disable the bootJar task so gradle doesn't fall over. 42 | bootJar.enabled = false 43 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_2-webflux/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_2Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-web-flux"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_2Version", 25 | "org.springframework.boot:spring-boot-starter-webflux", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | testImplementation( 30 | project(":backstopper-reusable-tests-junit5"), 31 | project(":testonly:testonly-spring-webflux-reusable-test-support"), 32 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 33 | "org.junit.jupiter:junit-jupiter:$junit5Version", 34 | "org.mockito:mockito-core:$mockitoVersion", 35 | "org.assertj:assertj-core:$assertJVersion", 36 | "io.rest-assured:rest-assured", 37 | ) 38 | } 39 | 40 | // We're just running tests, not trying to stand up a real Springboot 3 server from gradle. 41 | // Disable the bootJar task so gradle doesn't fall over. 42 | bootJar.enabled = false 43 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_3-webflux/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_3Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-web-flux"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_3Version", 25 | "org.springframework.boot:spring-boot-starter-webflux", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | testImplementation( 30 | project(":backstopper-reusable-tests-junit5"), 31 | project(":testonly:testonly-spring-webflux-reusable-test-support"), 32 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion", 33 | "org.junit.jupiter:junit-jupiter:$junit5Version", 34 | "org.mockito:mockito-core:$mockitoVersion", 35 | "org.assertj:assertj-core:$assertJVersion", 36 | "io.rest-assured:rest-assured", 37 | ) 38 | } 39 | 40 | // We're just running tests, not trying to stand up a real Springboot 3 server from gradle. 41 | // Disable the bootJar task so gradle doesn't fall over. 42 | bootJar.enabled = false 43 | -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/handler/ErrorResponseInfoTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler; 2 | 3 | import com.nike.internal.util.MapBuilder; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | import static org.hamcrest.Matchers.notNullValue; 12 | import static org.hamcrest.Matchers.nullValue; 13 | import static org.hamcrest.core.Is.is; 14 | 15 | /** 16 | * Unit test for {@link ErrorResponseInfo} 17 | */ 18 | public class ErrorResponseInfoTest { 19 | 20 | @Test 21 | public void constructorSetsValues() { 22 | Object frameworkObj = new Object(); 23 | Map> headersMap = MapBuilder 24 | .>builder() 25 | .put("header1", List.of("val1")) 26 | .put("header2", List.of("h2val1, h2val2")) 27 | .build(); 28 | ErrorResponseInfo responseInfo = new ErrorResponseInfo<>(42, frameworkObj, headersMap); 29 | assertThat(responseInfo.frameworkRepresentationObj, is(frameworkObj)); 30 | assertThat(responseInfo.headersToAddToResponse, is(headersMap)); 31 | assertThat(responseInfo.httpStatusCode, is(42)); 32 | } 33 | 34 | @Test 35 | public void constructorHandlesNullValuesGracefully() { 36 | ErrorResponseInfo responseInfo = new ErrorResponseInfo<>(42, null, null); 37 | assertThat(responseInfo.frameworkRepresentationObj, nullValue()); 38 | assertThat(responseInfo.headersToAddToResponse, notNullValue()); 39 | assertThat(responseInfo.headersToAddToResponse.isEmpty(), is(true)); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /testonly/testonly-spring-webmvc-reusable-test-support/src/main/java/testonly/componenttest/spring/reusable/testutil/ExplodingServletFilter.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.testutil; 2 | 3 | import com.nike.backstopper.exception.ApiException; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.springframework.web.filter.OncePerRequestFilter; 7 | 8 | import java.io.IOException; 9 | 10 | import jakarta.servlet.FilterChain; 11 | import jakarta.servlet.ServletException; 12 | import jakarta.servlet.http.HttpServletRequest; 13 | import jakarta.servlet.http.HttpServletResponse; 14 | import testonly.componenttest.spring.reusable.error.SampleProjectApiError; 15 | 16 | /** 17 | * Servlet filter that will throw an {@link ApiException} for 18 | * {@link SampleProjectApiError#ERROR_THROWN_IN_SERVLET_FILTER_OUTSIDE_SPRING} when it sees the following header in 19 | * the request: {@code throw-servlet-filter-exception: true} 20 | * 21 | * @author Nic Munroe 22 | */ 23 | public class ExplodingServletFilter extends OncePerRequestFilter { 24 | 25 | @Override 26 | protected void doFilterInternal( 27 | HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain 28 | ) throws ServletException, IOException { 29 | if ("true".equals(request.getHeader("throw-servlet-filter-exception"))) { 30 | throw ApiException 31 | .newBuilder() 32 | .withApiErrors(SampleProjectApiError.ERROR_THROWN_IN_SERVLET_FILTER_OUTSIDE_SPRING) 33 | .withExceptionMessage("Exception thrown from Servlet Filter outside Spring") 34 | .build(); 35 | } 36 | filterChain.doFilter(request, response); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /backstopper-core/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - core 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem.) 8 | 9 | This `backstopper-core` library contains the key core components necessary for a Backstopper system to work. 10 | The [base project README.md](../README.md) and [User Guide](../USER_GUIDE.md) contain the main bulk of information 11 | regarding Backstopper, but in particular: 12 | 13 | * [Backstopper key components](../USER_GUIDE.md#key_components) - This describes the main classes contained in this core 14 | library and what they are for. See the source code and javadocs on classes for further information. 15 | * [Framework-specific modules](../README.md#framework_modules) - The list of specific framework plugin libraries 16 | Backstopper currently has support for. 17 | * [Sample applications](../README.md#samples) - The list of sample applications demonstrating how to integrate and use 18 | Backstopper in the various supported frameworks. 19 | * [Creating new framework integrations](../USER_GUIDE.md#new_framework_integrations) - Information on how to create new 20 | framework integrations. 21 | 22 | ## More Info 23 | 24 | See the [base project README.md](../README.md), [User Guide](../USER_GUIDE.md), and Backstopper repository source code 25 | and javadocs for all further information. 26 | 27 | ## License 28 | 29 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 30 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/util/ApiErrorUtil.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.util; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | 5 | import java.util.Objects; 6 | 7 | /** 8 | * Utility class to enable sharing code between {@link ApiError} implementations. Most Backstopper end-users 9 | * won't need to use this class unless you're creating custom {@link ApiError} implementations (which is not 10 | * common). 11 | */ 12 | public class ApiErrorUtil { 13 | 14 | private ApiErrorUtil() { 15 | // Do nothing. 16 | } 17 | 18 | /** 19 | * Method for generating a hashcode for the given {@link ApiError} . This can be used in implementations of 20 | * {@link ApiError}. 21 | */ 22 | public static int generateApiErrorHashCode(ApiError apiError) { 23 | return Objects.hash(apiError.getName(), apiError.getErrorCode(), apiError.getMessage(), apiError.getHttpStatusCode(), apiError.getMetadata()); 24 | } 25 | 26 | /** 27 | * Method for checking equality of two {@link ApiError}. This can be used in implementations of {@link ApiError} 28 | */ 29 | public static boolean isApiErrorEqual(ApiError apiError, Object o) { 30 | if (apiError == o) return true; 31 | if (apiError == null) return false; 32 | if (!(o instanceof ApiError that)) return false; 33 | return apiError.getHttpStatusCode() == that.getHttpStatusCode() && 34 | Objects.equals(apiError.getName(), that.getName()) && 35 | Objects.equals(apiError.getErrorCode(), that.getErrorCode()) && 36 | Objects.equals(apiError.getMessage(), that.getMessage()) && 37 | Objects.equals(apiError.getMetadata(), that.getMetadata()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /nike-internal-util/src/test/java/com/nike/internal/util/ImmutablePairTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.internal.util; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.UUID; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | /** 11 | * Tests the functionality of {@link ImmutablePair} 12 | */ 13 | public class ImmutablePairTest { 14 | 15 | @Test 16 | public void constructor_works_as_expected() { 17 | // given 18 | String left = UUID.randomUUID().toString(); 19 | String right = UUID.randomUUID().toString(); 20 | ImmutablePair pair = new ImmutablePair<>(left, right); 21 | 22 | // expect 23 | assertThat(pair.getLeft(), is(left)); 24 | assertThat(pair.getKey(), is(left)); 25 | assertThat(pair.getRight(), is(right)); 26 | assertThat(pair.getValue(), is(right)); 27 | } 28 | 29 | @Test 30 | public void static_constructor_works_as_expected() { 31 | // given 32 | String left = UUID.randomUUID().toString(); 33 | String right = UUID.randomUUID().toString(); 34 | ImmutablePair pair = ImmutablePair.of(left, right); 35 | 36 | // expect 37 | assertThat(pair.getLeft(), is(left)); 38 | assertThat(pair.getKey(), is(left)); 39 | assertThat(pair.getRight(), is(right)); 40 | assertThat(pair.getValue(), is(right)); 41 | } 42 | 43 | @Test(expected = UnsupportedOperationException.class) 44 | public void setValue_throws_UnsupportedOperationException() { 45 | // given 46 | ImmutablePair pair = ImmutablePair.of("foo", "bar"); 47 | 48 | // expect 49 | pair.setValue("blowup"); 50 | } 51 | } -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/src/main/java/testonly/componenttest/spring/reusable/filter/ExplodingWebFilter.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.filter; 2 | 3 | import com.nike.backstopper.exception.ApiException; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.web.server.ServerWebExchange; 8 | import org.springframework.web.server.WebFilter; 9 | import org.springframework.web.server.WebFilterChain; 10 | 11 | import reactor.core.publisher.Mono; 12 | import testonly.componenttest.spring.reusable.error.SampleProjectApiError; 13 | 14 | public class ExplodingWebFilter implements WebFilter { 15 | 16 | @Override 17 | public @NotNull Mono filter(ServerWebExchange exchange, @NotNull WebFilterChain chain) { 18 | HttpHeaders httpHeaders = exchange.getRequest().getHeaders(); 19 | 20 | if ("true".equals(httpHeaders.getFirst("throw-web-filter-exception"))) { 21 | throw ApiException 22 | .newBuilder() 23 | .withApiErrors(SampleProjectApiError.ERROR_THROWN_IN_WEB_FILTER) 24 | .withExceptionMessage("Exception thrown from WebFilter") 25 | .build(); 26 | } 27 | 28 | if ("true".equals(httpHeaders.getFirst("return-exception-in-web-filter-mono"))) { 29 | return Mono.error( 30 | ApiException 31 | .newBuilder() 32 | .withApiErrors(SampleProjectApiError.ERROR_RETURNED_IN_WEB_FILTER_MONO) 33 | .withExceptionMessage("Exception returned from WebFilter Mono") 34 | .build() 35 | ); 36 | } 37 | 38 | return chain.filter(exchange); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/src/test/java/com/nike/backstopper/apierror/contract/jsr303convention/VerifyJsr303ContractTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.apierror.contract.jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.springsample.error.SampleProjectApiErrorsImpl; 5 | 6 | /** 7 | * Verifies that *ALL* non-excluded JSR 303 validation annotations in this project have a message defined that maps to a 8 | * {@link com.nike.backstopper.apierror.ApiError} enum name from this project's {@link ProjectApiErrors}. This is how 9 | * the JSR 303 Bean Validation system is connected to the Backstopper error handling system and you should NOT disable 10 | * these tests. 11 | * 12 | *

You can exclude annotation declarations (e.g. for unit test classes that are intended to violate the naming 13 | * convention) by making sure that the {@link ApplicationJsr303AnnotationTroller#ignoreAllAnnotationsAssociatedWithTheseProjectClasses()} 14 | * and {@link ApplicationJsr303AnnotationTroller#specificAnnotationDeclarationExclusionsForProject()} methods return 15 | * what you need, but you should not exclude any annotations in production code under normal circumstances. 16 | * 17 | * @author Nic Munroe 18 | */ 19 | public class VerifyJsr303ContractTest extends VerifyJsr303ValidationMessagesPointToApiErrorsTest { 20 | 21 | private static final ProjectApiErrors PROJECT_API_ERRORS = new SampleProjectApiErrorsImpl(); 22 | 23 | @Override 24 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 25 | return ApplicationJsr303AnnotationTroller.getInstance(); 26 | } 27 | 28 | @Override 29 | protected ProjectApiErrors getProjectApiErrors() { 30 | return PROJECT_API_ERRORS; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/apierror/SortedApiErrorSet.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.apierror; 2 | 3 | import java.util.Collection; 4 | import java.util.Comparator; 5 | import java.util.TreeSet; 6 | 7 | import static java.util.Collections.singletonList; 8 | 9 | /** 10 | * A {@link java.util.SortedSet} that uses a built-in {@link ApiErrorComparator} to compare the {@link ApiError} 11 | * instances to each other. This should be used any time a {@code SortedSet} is called for. 12 | * 13 | *

There are some constructors to allow you to pass in a custom comparator rather than the default (a 14 | * {@link ApiErrorComparator}) - use them if you're sure the default behavior does not work for your use case. 15 | * 16 | * @author Nic Munroe 17 | */ 18 | public class SortedApiErrorSet extends TreeSet { 19 | 20 | protected static final Comparator DEFAULT_API_ERROR_COMPARATOR = new ApiErrorComparator(); 21 | 22 | public SortedApiErrorSet() { 23 | this(DEFAULT_API_ERROR_COMPARATOR); 24 | } 25 | 26 | public SortedApiErrorSet(Collection values) { 27 | this(DEFAULT_API_ERROR_COMPARATOR); 28 | addAll(values); 29 | } 30 | 31 | public SortedApiErrorSet(Comparator customComparator) { 32 | super(customComparator); 33 | } 34 | 35 | public SortedApiErrorSet(Collection values, Comparator customComparator) { 36 | this(customComparator); 37 | addAll(values); 38 | } 39 | 40 | /** 41 | * @return The given single {@link ApiError} after it has been wrapped in a new {@link SortedApiErrorSet}. 42 | */ 43 | public static SortedApiErrorSet singletonSortedSetOf(ApiError apiError) { 44 | return new SortedApiErrorSet(singletonList(apiError)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/build.gradle: -------------------------------------------------------------------------------- 1 | evaluationDependsOn(':') 2 | 3 | buildscript { 4 | repositories { 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath("org.springframework.boot:spring-boot-gradle-plugin:${springboot3_3Version}") 9 | } 10 | } 11 | 12 | apply plugin: 'org.springframework.boot' 13 | apply plugin: "io.spring.dependency-management" 14 | 15 | test { 16 | useJUnitPlatform() 17 | } 18 | 19 | dependencies { 20 | implementation( 21 | project(":backstopper-spring-web-flux"), 22 | project(":backstopper-custom-validators"), 23 | "ch.qos.logback:logback-classic", 24 | "org.springframework.boot:spring-boot-dependencies:$springboot3_3Version", 25 | "org.springframework.boot:spring-boot-starter-webflux", 26 | "org.hibernate.validator:hibernate-validator", 27 | "org.glassfish.expressly:expressly:$glassfishExpresslyVersion", 28 | ) 29 | compileOnly( 30 | "org.jetbrains:annotations:$jetbrainsAnnotationsVersion" 31 | ) 32 | testImplementation( 33 | project(":backstopper-reusable-tests-junit5"), 34 | "org.junit.jupiter:junit-jupiter:$junit5Version", 35 | "org.mockito:mockito-core:$mockitoVersion", 36 | "org.assertj:assertj-core:$assertJVersion", 37 | "io.rest-assured:rest-assured", 38 | ) 39 | // Make gradle happy for gradle 9. 40 | // See: https://docs.gradle.org/8.10/userguide/upgrading_version_8.html#test_framework_implementation_dependencies 41 | testRuntimeOnly( 42 | "org.junit.platform:junit-platform-launcher" 43 | ) 44 | } 45 | 46 | apply plugin: "application" 47 | application { 48 | setMainClass("com.nike.backstopper.springboot3webfluxsample.Main") 49 | } 50 | 51 | run { 52 | systemProperties(System.getProperties()) 53 | } 54 | -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/service/NoOpJsr303ValidatorTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.service; 2 | 3 | import org.junit.Test; 4 | 5 | import jakarta.validation.ValidationException; 6 | import jakarta.validation.Validator; 7 | import jakarta.validation.constraints.NotNull; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.assertj.core.api.Assertions.catchThrowable; 11 | 12 | /** 13 | * Tests the functionality of {@link NoOpJsr303Validator}. 14 | * 15 | * @author Nic Munroe 16 | */ 17 | public class NoOpJsr303ValidatorTest { 18 | 19 | private final Validator noOpValidator = NoOpJsr303Validator.SINGLETON_IMPL; 20 | private final FooClass constraintAnnotatedClass = new FooClass(); 21 | 22 | @Test 23 | public void validation_methods_return_empty_sets() { 24 | // expect 25 | assertThat(noOpValidator.validate(constraintAnnotatedClass)).isEmpty(); 26 | assertThat(noOpValidator.validateProperty(constraintAnnotatedClass, "notNullString")).isEmpty(); 27 | assertThat(noOpValidator.validateValue(FooClass.class, "notNullString", null)).isEmpty(); 28 | } 29 | 30 | @Test 31 | public void getConstraintsForClass_throws_ValidationException() { 32 | // when 33 | Throwable ex = catchThrowable(() -> noOpValidator.getConstraintsForClass(FooClass.class)); 34 | 35 | // then 36 | assertThat(ex).isInstanceOf(ValidationException.class); 37 | } 38 | 39 | @Test 40 | public void unwrap_throws_ValidationException() { 41 | // when 42 | Throwable ex = catchThrowable(() -> noOpValidator.unwrap(FooClass.class)); 43 | 44 | // then 45 | assertThat(ex).isInstanceOf(ValidationException.class); 46 | } 47 | 48 | private static class FooClass { 49 | @NotNull 50 | @SuppressWarnings("unused") 51 | public String notNullString = null; 52 | } 53 | } -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/src/main/java/com/nike/backstopper/springsample/error/SampleProjectApiErrorsImpl.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springsample.error; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRange; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRangeIntegerImpl; 6 | import com.nike.backstopper.apierror.sample.SampleProjectApiErrorsBase; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import jakarta.inject.Singleton; 13 | 14 | /** 15 | * Returns the project specific errors for this sample application. {@link #getProjectApiErrors()} will return a 16 | * combination of {@link SampleProjectApiErrorsBase#getCoreApiErrors()} and {@link #getProjectSpecificApiErrors()}. 17 | * This means that you have all the enum values of {@link com.nike.backstopper.apierror.sample.SampleCoreApiError} 18 | * and {@link SampleProjectApiError} at your disposal when throwing errors in this sample app. 19 | */ 20 | @Singleton 21 | public class SampleProjectApiErrorsImpl extends SampleProjectApiErrorsBase { 22 | 23 | private static final List projectSpecificApiErrors = 24 | new ArrayList<>(Arrays.asList(SampleProjectApiError.values())); 25 | 26 | // Set the valid range of non-core error codes for this project to be 99100-99200. 27 | private static final ProjectSpecificErrorCodeRange errorCodeRange = new ProjectSpecificErrorCodeRangeIntegerImpl( 28 | 99100, 99200, "SAMPLE_PROJECT_API_ERRORS" 29 | ); 30 | 31 | @Override 32 | protected List getProjectSpecificApiErrors() { 33 | return projectSpecificApiErrors; 34 | } 35 | 36 | @Override 37 | protected ProjectSpecificErrorCodeRange getProjectSpecificErrorCodeRange() { 38 | return errorCodeRange; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/main/java/com/nike/backstopper/springboot3webmvcsample/error/SampleProjectApiErrorsImpl.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springboot3webmvcsample.error; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRange; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRangeIntegerImpl; 6 | import com.nike.backstopper.apierror.sample.SampleProjectApiErrorsBase; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import jakarta.inject.Singleton; 13 | 14 | /** 15 | * Returns the project specific errors for this sample application. {@link #getProjectApiErrors()} will return a 16 | * combination of {@link SampleProjectApiErrorsBase#getCoreApiErrors()} and {@link #getProjectSpecificApiErrors()}. 17 | * This means that you have all the enum values of {@link com.nike.backstopper.apierror.sample.SampleCoreApiError} 18 | * and {@link SampleProjectApiError} at your disposal when throwing errors in this sample app. 19 | */ 20 | @Singleton 21 | public class SampleProjectApiErrorsImpl extends SampleProjectApiErrorsBase { 22 | 23 | private static final List projectSpecificApiErrors = 24 | new ArrayList<>(Arrays.asList(SampleProjectApiError.values())); 25 | 26 | // Set the valid range of non-core error codes for this project to be 99100-99200. 27 | private static final ProjectSpecificErrorCodeRange errorCodeRange = new ProjectSpecificErrorCodeRangeIntegerImpl( 28 | 99100, 99200, "SAMPLE_PROJECT_API_ERRORS" 29 | ); 30 | 31 | @Override 32 | protected List getProjectSpecificApiErrors() { 33 | return projectSpecificApiErrors; 34 | } 35 | 36 | @Override 37 | protected ProjectSpecificErrorCodeRange getProjectSpecificErrorCodeRange() { 38 | return errorCodeRange; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/main/java/com/nike/backstopper/springboot3webfluxsample/error/SampleProjectApiErrorsImpl.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springboot3webfluxsample.error; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRange; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRangeIntegerImpl; 6 | import com.nike.backstopper.apierror.sample.SampleProjectApiErrorsBase; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | 12 | import jakarta.inject.Singleton; 13 | 14 | /** 15 | * Returns the project specific errors for this sample application. {@link #getProjectApiErrors()} will return a 16 | * combination of {@link SampleProjectApiErrorsBase#getCoreApiErrors()} and {@link #getProjectSpecificApiErrors()}. 17 | * This means that you have all the enum values of {@link com.nike.backstopper.apierror.sample.SampleCoreApiError} 18 | * and {@link SampleProjectApiError} at your disposal when throwing errors in this sample app. 19 | */ 20 | @Singleton 21 | public class SampleProjectApiErrorsImpl extends SampleProjectApiErrorsBase { 22 | 23 | private static final List projectSpecificApiErrors = 24 | new ArrayList<>(Arrays.asList(SampleProjectApiError.values())); 25 | 26 | // Set the valid range of non-core error codes for this project to be 99100-99200. 27 | private static final ProjectSpecificErrorCodeRange errorCodeRange = new ProjectSpecificErrorCodeRangeIntegerImpl( 28 | 99100, 99200, "SAMPLE_PROJECT_API_ERRORS" 29 | ); 30 | 31 | @Override 32 | protected List getProjectSpecificApiErrors() { 33 | return projectSpecificApiErrors; 34 | } 35 | 36 | @Override 37 | protected ProjectSpecificErrorCodeRange getProjectSpecificErrorCodeRange() { 38 | return errorCodeRange; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/service/FailFastServersideValidationServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.service; 2 | 3 | import com.nike.backstopper.exception.ServersideValidationError; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.mockito.InjectMocks; 8 | import org.mockito.Mock; 9 | import org.mockito.MockitoAnnotations; 10 | 11 | import java.util.Collections; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | import jakarta.validation.ConstraintViolation; 16 | import jakarta.validation.Validator; 17 | 18 | import static org.mockito.Mockito.mock; 19 | import static org.mockito.Mockito.when; 20 | 21 | /** 22 | * Unit tests for {@link com.nike.backstopper.service.FailFastServersideValidationService} 23 | * 24 | * @author Nic Munroe 25 | */ 26 | public class FailFastServersideValidationServiceTest { 27 | 28 | @InjectMocks 29 | private FailFastServersideValidationService validationService; 30 | @Mock 31 | private Validator validator; 32 | 33 | @Before 34 | public void beforeMethod() { 35 | //noinspection resource 36 | MockitoAnnotations.openMocks(this); 37 | } 38 | 39 | @Test 40 | public void shouldNotThrowExceptionIfValidatorComesBackClean() { 41 | Object validateMe = new Object(); 42 | when(validator.validate(validateMe)).thenReturn(new HashSet<>()); 43 | validationService.validateObjectFailFast(validateMe); 44 | } 45 | 46 | @Test(expected = ServersideValidationError.class) 47 | public void shouldThrowExceptionIfValidatorFindsConstraintViolations() { 48 | Object validateMe = new Object(); 49 | @SuppressWarnings("unchecked") 50 | Set> mockReturnVal = Collections.singleton(mock(ConstraintViolation.class)); 51 | when(validator.validate(validateMe)).thenReturn(mockReturnVal); 52 | validationService.validateObjectFailFast(validateMe); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/exception/network/ServerHttpStatusCodeExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception.network; 2 | 3 | import com.nike.internal.util.MapBuilder; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.UUID; 11 | 12 | import static java.util.Collections.singletonList; 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | /** 16 | * Tests the functionality of {@link ServerHttpStatusCodeException}. 17 | * 18 | * @author Nic Munroe 19 | */ 20 | public class ServerHttpStatusCodeExceptionTest { 21 | 22 | @Test 23 | public void constructor_sets_values_as_expected() { 24 | // given 25 | Throwable cause = new RuntimeException("cause"); 26 | String connectionType = "foo_conn"; 27 | Throwable details = new RuntimeException("details"); 28 | int responseStatusCode = 500; 29 | Map> responseHeaders = MapBuilder 30 | .builder("foo", singletonList(UUID.randomUUID().toString())) 31 | .put("bar", Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) 32 | .build(); 33 | String rawResponseBody = UUID.randomUUID().toString(); 34 | 35 | // when 36 | ServerHttpStatusCodeException ex = new ServerHttpStatusCodeException( 37 | cause, connectionType, details, responseStatusCode, responseHeaders, rawResponseBody 38 | ); 39 | 40 | // then 41 | assertThat(ex).hasCause(cause); 42 | assertThat(ex.getConnectionType()).isEqualTo(connectionType); 43 | assertThat(ex.getDetails()).isEqualTo(details); 44 | assertThat(ex.getResponseStatusCode()).isEqualTo(responseStatusCode); 45 | assertThat(ex.getResponseHeaders()).isEqualTo(responseHeaders); 46 | assertThat(ex.getRawResponseBody()).isEqualTo(rawResponseBody); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/test/java/jsr303convention/VerifyJsr303ContractTest.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.backstopper.apierror.contract.jsr303convention.VerifyJsr303ValidationMessagesPointToApiErrorsTest; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 6 | import com.nike.backstopper.springboot3webmvcsample.error.SampleProjectApiErrorsImpl; 7 | 8 | /** 9 | * Verifies that *ALL* non-excluded JSR 303 validation annotations in this project have a message defined that maps to a 10 | * {@link com.nike.backstopper.apierror.ApiError} enum name from this project's {@link ProjectApiErrors}. This is how 11 | * the JSR 303 Bean Validation system is connected to the Backstopper error handling system and you should NOT disable 12 | * these tests. 13 | * 14 | *

You can exclude annotation declarations (e.g. for unit test classes that are intended to violate the naming 15 | * convention) by making sure that the {@link ApplicationJsr303AnnotationTroller#ignoreAllAnnotationsAssociatedWithTheseProjectClasses()} 16 | * and {@link ApplicationJsr303AnnotationTroller#specificAnnotationDeclarationExclusionsForProject()} methods return 17 | * what you need, but you should not exclude any annotations in production code under normal circumstances. 18 | * 19 | * @author Nic Munroe 20 | */ 21 | public class VerifyJsr303ContractTest extends VerifyJsr303ValidationMessagesPointToApiErrorsTest { 22 | 23 | private static final ProjectApiErrors PROJECT_API_ERRORS = new SampleProjectApiErrorsImpl(); 24 | 25 | @Override 26 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 27 | return ApplicationJsr303AnnotationTroller.getInstance(); 28 | } 29 | 30 | @Override 31 | protected ProjectApiErrors getProjectApiErrors() { 32 | return PROJECT_API_ERRORS; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/test/java/jsr303convention/VerifyJsr303ContractTest.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.backstopper.apierror.contract.jsr303convention.VerifyJsr303ValidationMessagesPointToApiErrorsTest; 5 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 6 | import com.nike.backstopper.springboot3webfluxsample.error.SampleProjectApiErrorsImpl; 7 | 8 | /** 9 | * Verifies that *ALL* non-excluded JSR 303 validation annotations in this project have a message defined that maps to a 10 | * {@link com.nike.backstopper.apierror.ApiError} enum name from this project's {@link ProjectApiErrors}. This is how 11 | * the JSR 303 Bean Validation system is connected to the Backstopper error handling system and you should NOT disable 12 | * these tests. 13 | * 14 | *

You can exclude annotation declarations (e.g. for unit test classes that are intended to violate the naming 15 | * convention) by making sure that the {@link ApplicationJsr303AnnotationTroller#ignoreAllAnnotationsAssociatedWithTheseProjectClasses()} 16 | * and {@link ApplicationJsr303AnnotationTroller#specificAnnotationDeclarationExclusionsForProject()} methods return 17 | * what you need, but you should not exclude any annotations in production code under normal circumstances. 18 | * 19 | * @author Nic Munroe 20 | */ 21 | public class VerifyJsr303ContractTest extends VerifyJsr303ValidationMessagesPointToApiErrorsTest { 22 | 23 | private static final ProjectApiErrors PROJECT_API_ERRORS = new SampleProjectApiErrorsImpl(); 24 | 25 | @Override 26 | protected ReflectionBasedJsr303AnnotationTrollerBase getAnnotationTroller() { 27 | return ApplicationJsr303AnnotationTroller.getInstance(); 28 | } 29 | 30 | @Override 31 | protected ProjectApiErrors getProjectApiErrors() { 32 | return PROJECT_API_ERRORS; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_0-webmvc/src/test/java/serverconfig/classpathscan/Springboot3_0WebMvcClasspathScanConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.classpathscan; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.ComponentScan; 9 | import org.springframework.core.Ordered; 10 | 11 | import jakarta.validation.Validation; 12 | import jakarta.validation.Validator; 13 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 14 | import testonly.componenttest.spring.reusable.testutil.ExplodingServletFilter; 15 | 16 | /** 17 | * Springboot config that uses {@link ComponentScan} to integrate Backstopper via classpath scanning of the 18 | * {@code com.nike.backstopper} package. 19 | * 20 | * @author Nic Munroe 21 | */ 22 | @SpringBootApplication 23 | @ComponentScan(basePackages = { 24 | // Component scan the core Backstopper+Springboot support. 25 | "com.nike.backstopper", 26 | // Component scan the controller. 27 | "testonly.componenttest.spring.reusable.controller" 28 | }) 29 | @SuppressWarnings("unused") 30 | public class Springboot3_0WebMvcClasspathScanConfig { 31 | 32 | @Bean 33 | public ProjectApiErrors getProjectApiErrors() { 34 | return new SampleProjectApiErrorsImpl(); 35 | } 36 | 37 | @Bean 38 | public Validator getJsr303Validator() { 39 | //noinspection resource 40 | return Validation.buildDefaultValidatorFactory().getValidator(); 41 | } 42 | 43 | @Bean 44 | public FilterRegistrationBean explodingServletFilter() { 45 | FilterRegistrationBean frb = new FilterRegistrationBean<>(new ExplodingServletFilter()); 46 | frb.setOrder(Ordered.HIGHEST_PRECEDENCE); 47 | return frb; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_1-webmvc/src/test/java/serverconfig/classpathscan/Springboot3_1WebMvcClasspathScanConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.classpathscan; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.ComponentScan; 9 | import org.springframework.core.Ordered; 10 | 11 | import jakarta.validation.Validation; 12 | import jakarta.validation.Validator; 13 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 14 | import testonly.componenttest.spring.reusable.testutil.ExplodingServletFilter; 15 | 16 | /** 17 | * Springboot config that uses {@link ComponentScan} to integrate Backstopper via classpath scanning of the 18 | * {@code com.nike.backstopper} package. 19 | * 20 | * @author Nic Munroe 21 | */ 22 | @SpringBootApplication 23 | @ComponentScan(basePackages = { 24 | // Component scan the core Backstopper+Springboot support. 25 | "com.nike.backstopper", 26 | // Component scan the controller. 27 | "testonly.componenttest.spring.reusable.controller" 28 | }) 29 | @SuppressWarnings("unused") 30 | public class Springboot3_1WebMvcClasspathScanConfig { 31 | 32 | @Bean 33 | public ProjectApiErrors getProjectApiErrors() { 34 | return new SampleProjectApiErrorsImpl(); 35 | } 36 | 37 | @Bean 38 | public Validator getJsr303Validator() { 39 | //noinspection resource 40 | return Validation.buildDefaultValidatorFactory().getValidator(); 41 | } 42 | 43 | @Bean 44 | public FilterRegistrationBean explodingServletFilter() { 45 | FilterRegistrationBean frb = new FilterRegistrationBean<>(new ExplodingServletFilter()); 46 | frb.setOrder(Ordered.HIGHEST_PRECEDENCE); 47 | return frb; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_2-webmvc/src/test/java/serverconfig/classpathscan/Springboot3_2WebMvcClasspathScanConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.classpathscan; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.ComponentScan; 9 | import org.springframework.core.Ordered; 10 | 11 | import jakarta.validation.Validation; 12 | import jakarta.validation.Validator; 13 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 14 | import testonly.componenttest.spring.reusable.testutil.ExplodingServletFilter; 15 | 16 | /** 17 | * Springboot config that uses {@link ComponentScan} to integrate Backstopper via classpath scanning of the 18 | * {@code com.nike.backstopper} package. 19 | * 20 | * @author Nic Munroe 21 | */ 22 | @SpringBootApplication 23 | @ComponentScan(basePackages = { 24 | // Component scan the core Backstopper+Springboot support. 25 | "com.nike.backstopper", 26 | // Component scan the controller. 27 | "testonly.componenttest.spring.reusable.controller" 28 | }) 29 | @SuppressWarnings("unused") 30 | public class Springboot3_2WebMvcClasspathScanConfig { 31 | 32 | @Bean 33 | public ProjectApiErrors getProjectApiErrors() { 34 | return new SampleProjectApiErrorsImpl(); 35 | } 36 | 37 | @Bean 38 | public Validator getJsr303Validator() { 39 | //noinspection resource 40 | return Validation.buildDefaultValidatorFactory().getValidator(); 41 | } 42 | 43 | @Bean 44 | public FilterRegistrationBean explodingServletFilter() { 45 | FilterRegistrationBean frb = new FilterRegistrationBean<>(new ExplodingServletFilter()); 46 | frb.setOrder(Ordered.HIGHEST_PRECEDENCE); 47 | return frb; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_3-webmvc/src/test/java/serverconfig/classpathscan/Springboot3_3WebMvcClasspathScanConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.classpathscan; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.ComponentScan; 9 | import org.springframework.core.Ordered; 10 | 11 | import jakarta.validation.Validation; 12 | import jakarta.validation.Validator; 13 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 14 | import testonly.componenttest.spring.reusable.testutil.ExplodingServletFilter; 15 | 16 | /** 17 | * Springboot config that uses {@link ComponentScan} to integrate Backstopper via classpath scanning of the 18 | * {@code com.nike.backstopper} package. 19 | * 20 | * @author Nic Munroe 21 | */ 22 | @SpringBootApplication 23 | @ComponentScan(basePackages = { 24 | // Component scan the core Backstopper+Springboot support. 25 | "com.nike.backstopper", 26 | // Component scan the controller. 27 | "testonly.componenttest.spring.reusable.controller" 28 | }) 29 | @SuppressWarnings("unused") 30 | public class Springboot3_3WebMvcClasspathScanConfig { 31 | 32 | @Bean 33 | public ProjectApiErrors getProjectApiErrors() { 34 | return new SampleProjectApiErrorsImpl(); 35 | } 36 | 37 | @Bean 38 | public Validator getJsr303Validator() { 39 | //noinspection resource 40 | return Validation.buildDefaultValidatorFactory().getValidator(); 41 | } 42 | 43 | @Bean 44 | public FilterRegistrationBean explodingServletFilter() { 45 | FilterRegistrationBean frb = new FilterRegistrationBean<>(new ExplodingServletFilter()); 46 | frb.setOrder(Ordered.HIGHEST_PRECEDENCE); 47 | return frb; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/exception/network/ServerUnknownHttpStatusCodeExceptionTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception.network; 2 | 3 | import com.nike.internal.util.MapBuilder; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.UUID; 11 | 12 | import static java.util.Collections.singletonList; 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | /** 16 | * Tests the functionality of {@link ServerUnknownHttpStatusCodeException}. 17 | * 18 | * @author Nic Munroe 19 | */ 20 | public class ServerUnknownHttpStatusCodeExceptionTest { 21 | 22 | @Test 23 | public void constructor_sets_values_as_expected() { 24 | // given 25 | Throwable cause = new RuntimeException("cause"); 26 | String connectionType = "foo_conn"; 27 | Throwable details = new RuntimeException("details"); 28 | int responseStatusCode = 500; 29 | Map> responseHeaders = MapBuilder 30 | .builder("foo", singletonList(UUID.randomUUID().toString())) 31 | .put("bar", Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) 32 | .build(); 33 | String rawResponseBody = UUID.randomUUID().toString(); 34 | 35 | // when 36 | ServerUnknownHttpStatusCodeException ex = new ServerUnknownHttpStatusCodeException( 37 | cause, connectionType, details, responseStatusCode, responseHeaders, rawResponseBody 38 | ); 39 | 40 | // then 41 | assertThat(ex).hasCause(cause); 42 | assertThat(ex.getConnectionType()).isEqualTo(connectionType); 43 | assertThat(ex.getDetails()).isEqualTo(details); 44 | assertThat(ex.getResponseStatusCode()).isEqualTo(responseStatusCode); 45 | assertThat(ex.getResponseHeaders()).isEqualTo(responseHeaders); 46 | assertThat(ex.getRawResponseBody()).isEqualTo(rawResponseBody); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /testonly/testonly-springboot3_0-webmvc/src/test/java/serverconfig/directimport/Springboot3_0WebMvcDirectImportConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.directimport; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.handler.springboot.config.BackstopperSpringboot3WebMvcConfig; 5 | 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Import; 10 | import org.springframework.core.Ordered; 11 | 12 | import jakarta.validation.Validation; 13 | import jakarta.validation.Validator; 14 | import testonly.componenttest.spring.reusable.controller.SampleController; 15 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 16 | import testonly.componenttest.spring.reusable.testutil.ExplodingServletFilter; 17 | 18 | /** 19 | * Springboot config that uses {@link Import} to integrate Backstopper via direct import of 20 | * {@link BackstopperSpringboot3WebMvcConfig}. 21 | * 22 | * @author Nic Munroe 23 | */ 24 | @SpringBootApplication 25 | @Import({ 26 | // Import core Backstopper+Springboot support. 27 | BackstopperSpringboot3WebMvcConfig.class, 28 | // Import the controller. 29 | SampleController.class 30 | }) 31 | @SuppressWarnings("unused") 32 | public class Springboot3_0WebMvcDirectImportConfig { 33 | 34 | @Bean 35 | public ProjectApiErrors getProjectApiErrors() { 36 | return new SampleProjectApiErrorsImpl(); 37 | } 38 | 39 | @Bean 40 | public Validator getJsr303Validator() { 41 | //noinspection resource 42 | return Validation.buildDefaultValidatorFactory().getValidator(); 43 | } 44 | 45 | @Bean 46 | public FilterRegistrationBean explodingServletFilter() { 47 | FilterRegistrationBean frb = new FilterRegistrationBean<>(new ExplodingServletFilter()); 48 | frb.setOrder(Ordered.HIGHEST_PRECEDENCE); 49 | return frb; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_1-webmvc/src/test/java/serverconfig/directimport/Springboot3_1WebMvcDirectImportConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.directimport; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.handler.springboot.config.BackstopperSpringboot3WebMvcConfig; 5 | 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Import; 10 | import org.springframework.core.Ordered; 11 | 12 | import jakarta.validation.Validation; 13 | import jakarta.validation.Validator; 14 | import testonly.componenttest.spring.reusable.controller.SampleController; 15 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 16 | import testonly.componenttest.spring.reusable.testutil.ExplodingServletFilter; 17 | 18 | /** 19 | * Springboot config that uses {@link Import} to integrate Backstopper via direct import of 20 | * {@link BackstopperSpringboot3WebMvcConfig}. 21 | * 22 | * @author Nic Munroe 23 | */ 24 | @SpringBootApplication 25 | @Import({ 26 | // Import core Backstopper+Springboot support. 27 | BackstopperSpringboot3WebMvcConfig.class, 28 | // Import the controller. 29 | SampleController.class 30 | }) 31 | @SuppressWarnings("unused") 32 | public class Springboot3_1WebMvcDirectImportConfig { 33 | 34 | @Bean 35 | public ProjectApiErrors getProjectApiErrors() { 36 | return new SampleProjectApiErrorsImpl(); 37 | } 38 | 39 | @Bean 40 | public Validator getJsr303Validator() { 41 | //noinspection resource 42 | return Validation.buildDefaultValidatorFactory().getValidator(); 43 | } 44 | 45 | @Bean 46 | public FilterRegistrationBean explodingServletFilter() { 47 | FilterRegistrationBean frb = new FilterRegistrationBean<>(new ExplodingServletFilter()); 48 | frb.setOrder(Ordered.HIGHEST_PRECEDENCE); 49 | return frb; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_2-webmvc/src/test/java/serverconfig/directimport/Springboot3_2WebMvcDirectImportConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.directimport; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.handler.springboot.config.BackstopperSpringboot3WebMvcConfig; 5 | 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Import; 10 | import org.springframework.core.Ordered; 11 | 12 | import jakarta.validation.Validation; 13 | import jakarta.validation.Validator; 14 | import testonly.componenttest.spring.reusable.controller.SampleController; 15 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 16 | import testonly.componenttest.spring.reusable.testutil.ExplodingServletFilter; 17 | 18 | /** 19 | * Springboot config that uses {@link Import} to integrate Backstopper via direct import of 20 | * {@link BackstopperSpringboot3WebMvcConfig}. 21 | * 22 | * @author Nic Munroe 23 | */ 24 | @SpringBootApplication 25 | @Import({ 26 | // Import core Backstopper+Springboot support. 27 | BackstopperSpringboot3WebMvcConfig.class, 28 | // Import the controller. 29 | SampleController.class 30 | }) 31 | @SuppressWarnings("unused") 32 | public class Springboot3_2WebMvcDirectImportConfig { 33 | 34 | @Bean 35 | public ProjectApiErrors getProjectApiErrors() { 36 | return new SampleProjectApiErrorsImpl(); 37 | } 38 | 39 | @Bean 40 | public Validator getJsr303Validator() { 41 | //noinspection resource 42 | return Validation.buildDefaultValidatorFactory().getValidator(); 43 | } 44 | 45 | @Bean 46 | public FilterRegistrationBean explodingServletFilter() { 47 | FilterRegistrationBean frb = new FilterRegistrationBean<>(new ExplodingServletFilter()); 48 | frb.setOrder(Ordered.HIGHEST_PRECEDENCE); 49 | return frb; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /testonly/testonly-springboot3_3-webmvc/src/test/java/serverconfig/directimport/Springboot3_3WebMvcDirectImportConfig.java: -------------------------------------------------------------------------------- 1 | package serverconfig.directimport; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | import com.nike.backstopper.handler.springboot.config.BackstopperSpringboot3WebMvcConfig; 5 | 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.web.servlet.FilterRegistrationBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Import; 10 | import org.springframework.core.Ordered; 11 | 12 | import jakarta.validation.Validation; 13 | import jakarta.validation.Validator; 14 | import testonly.componenttest.spring.reusable.controller.SampleController; 15 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 16 | import testonly.componenttest.spring.reusable.testutil.ExplodingServletFilter; 17 | 18 | /** 19 | * Springboot config that uses {@link Import} to integrate Backstopper via direct import of 20 | * {@link BackstopperSpringboot3WebMvcConfig}. 21 | * 22 | * @author Nic Munroe 23 | */ 24 | @SpringBootApplication 25 | @Import({ 26 | // Import core Backstopper+Springboot support. 27 | BackstopperSpringboot3WebMvcConfig.class, 28 | // Import the controller. 29 | SampleController.class 30 | }) 31 | @SuppressWarnings("unused") 32 | public class Springboot3_3WebMvcDirectImportConfig { 33 | 34 | @Bean 35 | public ProjectApiErrors getProjectApiErrors() { 36 | return new SampleProjectApiErrorsImpl(); 37 | } 38 | 39 | @Bean 40 | public Validator getJsr303Validator() { 41 | //noinspection resource 42 | return Validation.buildDefaultValidatorFactory().getValidator(); 43 | } 44 | 45 | @Bean 46 | public FilterRegistrationBean explodingServletFilter() { 47 | FilterRegistrationBean frb = new FilterRegistrationBean<>(new ExplodingServletFilter()); 48 | frb.setOrder(Ordered.HIGHEST_PRECEDENCE); 49 | return frb; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/handler/ErrorResponseInfo.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | /** 8 | * Object that contains data related to the response for an error handled by {@link ApiExceptionHandlerBase} or 9 | * {@link UnhandledExceptionHandlerBase}. Frameworks should use this information to generate the final response 10 | * to send back to the caller. 11 | * 12 | * @author Nic Munroe 13 | */ 14 | public class ErrorResponseInfo { 15 | 16 | /** 17 | * The HTTP status code that should be returned in the response to the caller. This is not automatically registered 18 | * on the framework's response - you should set this yourself on the response after you call an error handler. 19 | */ 20 | public final int httpStatusCode; 21 | /** 22 | * The framework representation object. Might represent response body content, or the whole response - it's up to 23 | * each framework/implementation to know what to do with this. 24 | */ 25 | public final T frameworkRepresentationObj; 26 | /** 27 | * Extra headers that were generated during error handling (e.g. error_uid) that should be added as headers to the 28 | * response sent to the user. These are not automatically registered on the framework's response - you should set 29 | * these yourself on the response after you call an error handler. This will never be null - it will be an empty map 30 | * if there are no headers to add. 31 | */ 32 | public final Map> headersToAddToResponse = new HashMap<>(); 33 | 34 | public ErrorResponseInfo(int httpStatusCode, T frameworkRepresentationObj, 35 | Map> headersToAddToResponse) { 36 | 37 | this.httpStatusCode = httpStatusCode; 38 | this.frameworkRepresentationObj = frameworkRepresentationObj; 39 | if (headersToAddToResponse != null) 40 | this.headersToAddToResponse.putAll(headersToAddToResponse); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /backstopper-core/src/test/java/com/nike/backstopper/apierror/projectspecificinfo/range/IntegerRangeTest.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.apierror.projectspecificinfo.range; 2 | 3 | import com.tngtech.java.junit.dataprovider.DataProvider; 4 | import com.tngtech.java.junit.dataprovider.DataProviderRunner; 5 | 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.assertj.core.api.Assertions.catchThrowable; 11 | 12 | /** 13 | * Tests the functionality of {@link IntegerRange}. 14 | * 15 | * @author Nic Munroe 16 | */ 17 | @RunWith(DataProviderRunner.class) 18 | public class IntegerRangeTest { 19 | 20 | private final IntegerRange rangeOfOneToFour = IntegerRange.of(1, 4); 21 | 22 | @Test 23 | public void constructor_throws_exception_if_upper_range_less_than_lower_range() { 24 | // when 25 | Throwable ex = catchThrowable(() -> IntegerRange.of(5, 4)); 26 | 27 | // then 28 | assertThat(ex).isInstanceOf(IllegalArgumentException.class); 29 | } 30 | 31 | @DataProvider(value = { 32 | "0 | false", 33 | "1 | true", 34 | "2 | true", 35 | "4 | true", 36 | "5 | false", 37 | }, splitBy = "\\|") 38 | @Test 39 | public void isInRange_int_arg_works_as_expected(int arg, boolean expectedResult) { 40 | // expect 41 | assertThat(rangeOfOneToFour.isInRange(arg)).isEqualTo(expectedResult); 42 | } 43 | 44 | @DataProvider(value = { 45 | "0 | false", 46 | "1 | true", 47 | "2 | true", 48 | "4 | true", 49 | "5 | false", 50 | }, splitBy = "\\|") 51 | @Test 52 | public void isInRange_string_arg_works_as_expected(String arg, boolean expectedResult) { 53 | // expect 54 | assertThat(rangeOfOneToFour.isInRange(arg)).isEqualTo(expectedResult); 55 | } 56 | 57 | @Test 58 | public void isInRange_string_arg_returns_false_if_arg_cannot_be_parsed_to_an_int() { 59 | // expect 60 | assertThat(rangeOfOneToFour.isInRange("foo")).isFalse(); 61 | } 62 | } -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/src/main/java/testonly/componenttest/spring/reusable/filter/ExplodingHandlerFilterFunction.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.filter; 2 | 3 | import com.nike.backstopper.exception.ApiException; 4 | 5 | import org.jetbrains.annotations.NotNull; 6 | import org.springframework.http.HttpHeaders; 7 | import org.springframework.web.reactive.function.server.HandlerFilterFunction; 8 | import org.springframework.web.reactive.function.server.HandlerFunction; 9 | import org.springframework.web.reactive.function.server.ServerRequest; 10 | import org.springframework.web.reactive.function.server.ServerResponse; 11 | 12 | import reactor.core.publisher.Mono; 13 | import testonly.componenttest.spring.reusable.error.SampleProjectApiError; 14 | 15 | public class ExplodingHandlerFilterFunction implements HandlerFilterFunction { 16 | 17 | @Override 18 | public @NotNull Mono filter( 19 | @NotNull ServerRequest serverRequest, 20 | @NotNull HandlerFunction handlerFunction 21 | ) { 22 | HttpHeaders httpHeaders = serverRequest.headers().asHttpHeaders(); 23 | 24 | if ("true".equals(httpHeaders.getFirst("throw-handler-filter-function-exception"))) { 25 | throw ApiException 26 | .newBuilder() 27 | .withApiErrors(SampleProjectApiError.ERROR_THROWN_IN_HANDLER_FILTER_FUNCTION) 28 | .withExceptionMessage("Exception thrown from HandlerFilterFunction") 29 | .build(); 30 | } 31 | 32 | if ("true".equals(httpHeaders.getFirst("return-exception-in-handler-filter-function-mono"))) { 33 | return Mono.error( 34 | ApiException 35 | .newBuilder() 36 | .withApiErrors(SampleProjectApiError.ERROR_RETURNED_IN_HANDLER_FILTER_FUNCTION_MONO) 37 | .withExceptionMessage("Exception returned from HandlerFilterFunction Mono") 38 | .build() 39 | ); 40 | } 41 | 42 | return handlerFunction.handle(serverRequest); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/apierror/ApiErrorComparator.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.apierror; 2 | 3 | import com.nike.backstopper.util.ApiErrorUtil; 4 | 5 | import java.util.Comparator; 6 | 7 | import static com.nike.backstopper.util.ApiErrorUtil.generateApiErrorHashCode; 8 | import static com.nike.backstopper.util.ApiErrorUtil.isApiErrorEqual; 9 | 10 | /** 11 | * A comparator that knows how to compare {@link ApiError} instances by {@link ApiError#getName()} first, 12 | * then by {@link ApiError#getErrorCode()}, and finally by everything else by comparing hashcodes using 13 | * {@link ApiErrorUtil#generateApiErrorHashCode(ApiError)}. 14 | *

15 | *

Note that this means two {@link ApiError}s that are identical other than metadata will be considered 16 | * different by this comparator. 17 | * 18 | * @author Nic Munroe 19 | */ 20 | @SuppressWarnings("WeakerAccess") 21 | public class ApiErrorComparator implements Comparator { 22 | 23 | @Override 24 | public int compare(ApiError o1, ApiError o2) { 25 | // Use Objects.equals to account for both being null and/or allow impls to specify custom equality logic. 26 | if (isApiErrorEqual(o1, o2)) { 27 | return 0; 28 | } 29 | 30 | // They're not *both* null, but *one* of them might still be null. 31 | if (o1 == null) { 32 | return -1; 33 | } 34 | 35 | if (o2 == null) { 36 | return 1; 37 | } 38 | 39 | // Null checks are now out of the way - both are non-null. Since the name should be unique we can just use that 40 | // for most use cases. 41 | int nameComparison = o1.getName().compareTo(o2.getName()); 42 | if (nameComparison != 0) 43 | return nameComparison; 44 | 45 | // compare error codes after name if names are equal 46 | int errorCodeComparison = o1.getErrorCode().compareTo(o2.getErrorCode()); 47 | if (errorCodeComparison != 0) 48 | return errorCodeComparison; 49 | 50 | // At this point we just need something deterministic to compare that will always end up with the same result. 51 | return Integer.compare(generateApiErrorHashCode(o1), generateApiErrorHashCode(o2)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /backstopper-spring-web-flux/src/main/java/com/nike/backstopper/handler/spring/webflux/listener/impl/OneOffSpringWebFluxFrameworkExceptionHandlerListener.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.spring.webflux.listener.impl; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 5 | import com.nike.backstopper.handler.ApiExceptionHandlerUtils; 6 | import com.nike.backstopper.handler.listener.ApiExceptionHandlerListenerResult; 7 | import com.nike.backstopper.handler.spring.listener.impl.OneOffSpringCommonFrameworkExceptionHandlerListener; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import jakarta.inject.Inject; 12 | import jakarta.inject.Named; 13 | import jakarta.inject.Singleton; 14 | 15 | /** 16 | * An extension and concrete implementation of {@link OneOffSpringCommonFrameworkExceptionHandlerListener} that 17 | * knows how to handle Spring WebFlux specific exceptions. 18 | * 19 | * @author Nic Munroe 20 | */ 21 | @Named 22 | @Singleton 23 | @SuppressWarnings("WeakerAccess") 24 | public class OneOffSpringWebFluxFrameworkExceptionHandlerListener 25 | extends OneOffSpringCommonFrameworkExceptionHandlerListener { 26 | 27 | /** 28 | * @param projectApiErrors The {@link ProjectApiErrors} that should be used by this instance when finding {@link 29 | * ApiError}s. Cannot be null. 30 | * @param utils The {@link ApiExceptionHandlerUtils} that should be used by this instance. You can pass in 31 | * {@link ApiExceptionHandlerUtils#DEFAULT_IMPL} if you don't need custom logic. 32 | */ 33 | @Inject 34 | public OneOffSpringWebFluxFrameworkExceptionHandlerListener(ProjectApiErrors projectApiErrors, 35 | ApiExceptionHandlerUtils utils) { 36 | super(projectApiErrors, utils); 37 | } 38 | 39 | @Override 40 | protected @NotNull ApiExceptionHandlerListenerResult handleSpringMvcOrWebfluxSpecificFrameworkExceptions( 41 | @NotNull Throwable ex 42 | ) { 43 | // If/when we get webflux specific exceptions, they would be handled here. 44 | 45 | // This exception is not handled here. 46 | return ApiExceptionHandlerListenerResult.ignoreResponse(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | There are a few guidelines that we need contributors to follow so that we are able to process requests as efficiently as possible. If you have any questions or concerns please feel free to contact us at [opensource@nike.com](mailto:opensource@nike.com). 4 | 5 | ## Getting Started 6 | 7 | * Review our [Code of Conduct](https://github.com/Nike-Inc/nike-inc.github.io/blob/master/CONDUCT.md) 8 | * Submit the [Individual Contributor License Agreement](https://www.clahub.com/agreements/Nike-Inc/backstopper) 9 | * Make sure you have a [GitHub account](https://github.com/signup/free) 10 | * Submit a ticket for your issue, assuming one does not already exist. 11 | * Clearly describe the issue including steps to reproduce when it is a bug. 12 | * Make sure you fill in the earliest version that you know has the issue. 13 | * Fork the repository on GitHub 14 | 15 | ## Making Changes 16 | 17 | * Create a topic branch off of `main` before you start your work. 18 | * Please avoid working directly on the `main` branch. 19 | * Make commits of logical units. 20 | * You may be asked to squash unnecessary commits down to logical units. 21 | * Check for unnecessary whitespace with `git diff --check` before committing. 22 | * Write meaningful, descriptive commit messages. 23 | * Please follow existing code conventions when working on a file. 24 | 25 | ## Submitting Changes 26 | 27 | * Push your changes to a topic branch in your fork of the repository. 28 | * Submit a pull request to the repository in the Nike-Inc organization. 29 | * After feedback has been given we expect responses from you within two weeks. After two weeks we may close the pull request if it isn't showing any activity. 30 | * Bug fixes or features that lack appropriate tests may not be considered for merge. 31 | * Changes that lower test coverage may not be considered for merge. 32 | 33 | # Additional Resources 34 | 35 | * [General GitHub documentation](https://help.github.com/) 36 | * [GitHub pull request documentation](https://help.github.com/send-pull-requests/) 37 | * [Nike's Code of Conduct](https://github.com/Nike-Inc/nike-inc.github.io/blob/master/CONDUCT.md) 38 | * [Nike's Individual Contributor License Agreement](https://www.clahub.com/agreements/Nike-Inc/backstopper) 39 | * [Nike OSS](https://nike-inc.github.io/) 40 | -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/src/test/java/com/nike/backstopper/apierror/contract/jsr303convention/ApplicationJsr303AnnotationTroller.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.apierror.contract.jsr303convention; 2 | 3 | import com.nike.internal.util.Pair; 4 | 5 | import java.lang.annotation.Annotation; 6 | import java.lang.reflect.AnnotatedElement; 7 | import java.util.List; 8 | import java.util.function.Predicate; 9 | 10 | /** 11 | * Extension of {@link ReflectionBasedJsr303AnnotationTrollerBase} for use with this sample project. This is used by JSR 12 | * 303 annotation convention enforcement tests (e.g. {@link VerifyJsr303ContractTest} and {@link 13 | * VerifyStringConvertsToClassTypeAnnotationsAreValidTest}). 14 | * 15 | *

NOTE: If you want to exclude classes or specific JSR 303 annotations from triggering convention violation errors 16 | * in those tests you can do so by populating the return values of the {@link #ignoreAllAnnotationsAssociatedWithTheseProjectClasses()} 17 | * and {@link #specificAnnotationDeclarationExclusionsForProject()} methods. IMPORTANT - this should only be done 18 | * if you *really* know what you're doing. Usually it's only done for unit test classes that are intended to violate the 19 | * convention. It should not be done for production code under normal circumstances. See the javadocs for the super 20 | * class for those methods if you need to use them. 21 | * 22 | * @author Nic Munroe 23 | */ 24 | public final class ApplicationJsr303AnnotationTroller extends ReflectionBasedJsr303AnnotationTrollerBase { 25 | 26 | public static final ApplicationJsr303AnnotationTroller INSTANCE = new ApplicationJsr303AnnotationTroller(); 27 | 28 | public static ApplicationJsr303AnnotationTroller getInstance() { 29 | return INSTANCE; 30 | } 31 | 32 | // Intentionally private - use {@code getInstance()} to retrieve the singleton instance of this class. 33 | private ApplicationJsr303AnnotationTroller() { 34 | super(); 35 | } 36 | 37 | @Override 38 | protected List> ignoreAllAnnotationsAssociatedWithTheseProjectClasses() { 39 | return null; 40 | } 41 | 42 | @Override 43 | protected List>> specificAnnotationDeclarationExclusionsForProject() { 44 | return null; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /backstopper-spring-web/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - spring-web 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem. The Backstopper 1.x releases also contain support for Spring 4 and 5, and Springboot 1 and 2.) 8 | 9 | This `backstopper-spring-web` module is not meant to be used standalone. It is here to provide common code for any 10 | `spring-web*` based application, including both Spring Web MVC and Spring WebFlux applications. But this module 11 | does not provide Spring+Backstopper integration by itself. 12 | 13 | To integrate Backstopper with your Spring application, please choose the correct concrete integration library, 14 | depending on which Spring environment your application is running in: 15 | 16 | ### Spring WebFlux based applications 17 | 18 | * [backstopper-spring-web-flux](../backstopper-spring-web-flux) - For Spring WebFlux applications. 19 | 20 | ### Spring Web MVC based applications 21 | 22 | * [backstopper-spring-boot3-webmvc](../backstopper-spring-boot3-webmvc) - For Spring Boot 3 applications using the 23 | Spring MVC (Servlet) framework. If you want Spring Boot 3 with Spring WebFlux (Netty) framework, then see 24 | [backstopper-spring-web-flux](../backstopper-spring-web-flux) instead. 25 | * [backstopper-spring-web-mvc](../backstopper-spring-web-mvc) - For Spring Web MVC applications that are not 26 | Spring Boot. 27 | 28 | The links above will take you to an integration-focused readme that will tell you how to integrate Backstopper into 29 | your Spring Web MVC or WebFlux application. If you are looking for a different framework integration check out the 30 | [relevant section](../README.md#framework_modules) of the base readme to see if one already exists. 31 | 32 | ## More Info 33 | 34 | See the [base project README.md](../README.md), [User Guide](../USER_GUIDE.md), and Backstopper repository source 35 | code and javadocs for all further information. 36 | 37 | ## License 38 | 39 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 40 | -------------------------------------------------------------------------------- /backstopper-spring-web-flux/src/main/java/com/nike/backstopper/handler/spring/webflux/DisconnectedClientHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2002-2024 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.nike.backstopper.handler.spring.webflux; 18 | 19 | import org.springframework.core.NestedExceptionUtils; 20 | 21 | import java.util.Set; 22 | 23 | /** 24 | * Copied from spring-web-6.1.12. We need some of the same logic in Backstopper. 25 | * Modified slightly from the original to make the methods static, get rid of the logging and methods we don't need, etc. 26 | */ 27 | class DisconnectedClientHelper { 28 | 29 | private static final Set EXCEPTION_PHRASES = 30 | Set.of("broken pipe", "connection reset"); 31 | 32 | private static final Set EXCEPTION_TYPE_NAMES = 33 | Set.of("AbortedException", "ClientAbortException", 34 | "EOFException", "EofException", "AsyncRequestNotUsableException"); 35 | 36 | /** 37 | * Whether the given exception indicates the client has gone away. 38 | *

Known cases covered: 39 | *

    40 | *
  • ClientAbortException or EOFException for Tomcat 41 | *
  • EofException for Jetty 42 | *
  • IOException "Broken pipe" or "connection reset by peer" 43 | *
  • SocketException "Connection reset" 44 | *
45 | */ 46 | public static boolean isClientDisconnectedException(Throwable ex) { 47 | String message = NestedExceptionUtils.getMostSpecificCause(ex).getMessage(); 48 | if (message != null) { 49 | String text = message.toLowerCase(); 50 | for (String phrase : EXCEPTION_PHRASES) { 51 | if (text.contains(phrase)) { 52 | return true; 53 | } 54 | } 55 | } 56 | return EXCEPTION_TYPE_NAMES.contains(ex.getClass().getSimpleName()); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/exception/ServersideValidationError.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.exception; 2 | 3 | import com.nike.backstopper.apierror.projectspecificinfo.ProjectApiErrors; 4 | 5 | import java.util.Set; 6 | 7 | import jakarta.validation.ConstraintViolation; 8 | 9 | /** 10 | * A runtime exception representing a SERVERSIDE JSR 303 validation failure (i.e. a validation error with 11 | * communication between serverside dependencies; something that the client has no control over and likely cannot fix 12 | * by changing what they send us). 13 | * 14 | *

If a controller (or its delegate) throws one of these then it will be caught by a 15 | * {@link com.nike.backstopper.handler.ApiExceptionHandlerBase} via 16 | * {@link com.nike.backstopper.handler.listener.impl.ServersideValidationErrorHandlerListener} and 17 | * turned into a {@link ProjectApiErrors#getServersideValidationApiError()} for the client (usually a 500 error with 18 | * no details on exactly what went wrong - no information leak about the internal server error), 19 | * along with logging as much data about this error as possible for debugging purposes. 20 | * 21 | *

NOTE: As mentioned previously this exception is for validation errors with the communication between 22 | * serverside dependencies that the client likely cannot do anything about. If you have a validation error with data 23 | * sent by the client where they violated contracts and therefore the problem is the client's and they need to fix it, 24 | * then you should use {@link com.nike.backstopper.exception.ClientDataValidationError} instead. 25 | * 26 | * @author Nic Munroe 27 | */ 28 | public class ServersideValidationError extends RuntimeException { 29 | private final Object objectThatFailedValidation; 30 | private final Set> violations; 31 | 32 | public ServersideValidationError(Object objectThatFailedValidation, Set> violations) { 33 | this.objectThatFailedValidation = objectThatFailedValidation; 34 | this.violations = violations; 35 | } 36 | 37 | public Object getObjectThatFailedValidation() { 38 | return objectThatFailedValidation; 39 | } 40 | 41 | public Set> getViolations() { 42 | return violations; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/test/java/jsr303convention/ApplicationJsr303AnnotationTroller.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.internal.util.Pair; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.AnnotatedElement; 8 | import java.util.List; 9 | import java.util.function.Predicate; 10 | 11 | /** 12 | * Extension of {@link ReflectionBasedJsr303AnnotationTrollerBase} for use with this sample project. This is used by JSR 13 | * 303 annotation convention enforcement tests (e.g. {@link VerifyJsr303ContractTest} and {@link 14 | * VerifyStringConvertsToClassTypeAnnotationsAreValidTest}). 15 | * 16 | *

NOTE: If you want to exclude classes or specific JSR 303 annotations from triggering convention violation errors 17 | * in those tests you can do so by populating the return values of the {@link #ignoreAllAnnotationsAssociatedWithTheseProjectClasses()} 18 | * and {@link #specificAnnotationDeclarationExclusionsForProject()} methods. IMPORTANT - this should only be done 19 | * if you *really* know what you're doing. Usually it's only done for unit test classes that are intended to violate the 20 | * convention. It should not be done for production code under normal circumstances. See the javadocs for the super 21 | * class for those methods if you need to use them. 22 | * 23 | * @author Nic Munroe 24 | */ 25 | public final class ApplicationJsr303AnnotationTroller extends ReflectionBasedJsr303AnnotationTrollerBase { 26 | 27 | public static final ApplicationJsr303AnnotationTroller INSTANCE = new ApplicationJsr303AnnotationTroller(); 28 | 29 | public static ApplicationJsr303AnnotationTroller getInstance() { 30 | return INSTANCE; 31 | } 32 | 33 | // Intentionally private - use {@code getInstance()} to retrieve the singleton instance of this class. 34 | private ApplicationJsr303AnnotationTroller() { 35 | super(); 36 | } 37 | 38 | @Override 39 | protected List> ignoreAllAnnotationsAssociatedWithTheseProjectClasses() { 40 | return null; 41 | } 42 | 43 | @Override 44 | protected List>> specificAnnotationDeclarationExclusionsForProject() { 45 | return null; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/test/java/jsr303convention/ApplicationJsr303AnnotationTroller.java: -------------------------------------------------------------------------------- 1 | package jsr303convention; 2 | 3 | import com.nike.backstopper.apierror.contract.jsr303convention.ReflectionBasedJsr303AnnotationTrollerBase; 4 | import com.nike.internal.util.Pair; 5 | 6 | import java.lang.annotation.Annotation; 7 | import java.lang.reflect.AnnotatedElement; 8 | import java.util.List; 9 | import java.util.function.Predicate; 10 | 11 | /** 12 | * Extension of {@link ReflectionBasedJsr303AnnotationTrollerBase} for use with this sample project. This is used by JSR 13 | * 303 annotation convention enforcement tests (e.g. {@link VerifyJsr303ContractTest} and {@link 14 | * VerifyStringConvertsToClassTypeAnnotationsAreValidTest}). 15 | * 16 | *

NOTE: If you want to exclude classes or specific JSR 303 annotations from triggering convention violation errors 17 | * in those tests you can do so by populating the return values of the {@link #ignoreAllAnnotationsAssociatedWithTheseProjectClasses()} 18 | * and {@link #specificAnnotationDeclarationExclusionsForProject()} methods. IMPORTANT - this should only be done 19 | * if you *really* know what you're doing. Usually it's only done for unit test classes that are intended to violate the 20 | * convention. It should not be done for production code under normal circumstances. See the javadocs for the super 21 | * class for those methods if you need to use them. 22 | * 23 | * @author Nic Munroe 24 | */ 25 | public final class ApplicationJsr303AnnotationTroller extends ReflectionBasedJsr303AnnotationTrollerBase { 26 | 27 | private static final ApplicationJsr303AnnotationTroller INSTANCE = new ApplicationJsr303AnnotationTroller(); 28 | 29 | @SuppressWarnings("WeakerAccess") 30 | public static ApplicationJsr303AnnotationTroller getInstance() { 31 | return INSTANCE; 32 | } 33 | 34 | // Intentionally private - use {@code getInstance()} to retrieve the singleton instance of this class. 35 | private ApplicationJsr303AnnotationTroller() { 36 | super(); 37 | } 38 | 39 | @Override 40 | protected List> ignoreAllAnnotationsAssociatedWithTheseProjectClasses() { 41 | return null; 42 | } 43 | 44 | @Override 45 | protected List>> specificAnnotationDeclarationExclusionsForProject() { 46 | return null; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /samples/sample-spring-web-mvc/src/main/java/com/nike/backstopper/springsample/model/SampleModel.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springsample.model; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.sample.SampleCoreApiError; 5 | import com.nike.backstopper.springsample.error.SampleProjectApiError; 6 | import com.nike.backstopper.springsample.error.SampleProjectApiErrorsImpl; 7 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 8 | 9 | import org.hibernate.validator.constraints.Range; 10 | 11 | import jakarta.validation.constraints.NotBlank; 12 | import jakarta.validation.constraints.NotNull; 13 | 14 | /** 15 | * Simple model class showing the JSR 303 Bean Validation integration in Backstopper. Each message for a JSR 303 16 | * annotation must match an {@link ApiError#getName()} from one of the errors returned by this project's 17 | * {@link SampleProjectApiErrorsImpl#getProjectApiErrors()}. In this case that means you can use any of the enum 18 | * names from {@link SampleCoreApiError} or {@link SampleProjectApiError}. 19 | * 20 | *

If you have a typo or forget to add a message that matches an error name then the {@code VerifyJsr303ContractTest} 21 | * unit test will catch your error and the project will fail to build - the test will give you info on exactly which 22 | * classes, fields, and annotations don't conform to the necessary convention. 23 | */ 24 | public class SampleModel { 25 | @NotBlank(message = "FOO_STRING_CANNOT_BE_BLANK") 26 | public final String foo; 27 | 28 | @Range(message = "INVALID_RANGE_VALUE", min = 0, max = 42) 29 | public final String range_0_to_42; 30 | 31 | @NotNull(message = "RGB_COLOR_CANNOT_BE_NULL") 32 | @StringConvertsToClassType( 33 | message = "NOT_RGB_COLOR_ENUM", classType = RgbColor.class, allowCaseInsensitiveEnumMatch = true 34 | ) 35 | public final String rgb_color; 36 | 37 | public final Boolean throw_manual_error; 38 | 39 | @SuppressWarnings("unused") 40 | // Intentionally protected - here for deserialization support. 41 | protected SampleModel() { 42 | this(null, null, null, null); 43 | } 44 | 45 | public SampleModel(String foo, String range_0_to_42, String rgb_color, Boolean throw_manual_error) { 46 | this.foo = foo; 47 | this.range_0_to_42 = range_0_to_42; 48 | this.rgb_color = rgb_color; 49 | this.throw_manual_error = throw_manual_error; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /backstopper-custom-validators/README.md: -------------------------------------------------------------------------------- 1 | # Backstopper - custom-validators 2 | 3 | Backstopper is a framework-agnostic API error handling and (optional) model validation solution for Java 17 and greater. 4 | 5 | (NOTE: The [Backstopper 1.x branch](https://github.com/Nike-Inc/backstopper/tree/v1.x) contains a version of 6 | Backstopper for Java 7+, and for the `javax` ecosystem. The current Backstopper supports Java 17+ and the `jakarta` 7 | ecosystem.) 8 | 9 | This library contains JSR 303 Bean Validation annotations that have proven to be useful and reusable. These are entirely 10 | optional. They are also largely dependency free so this library is usable in non-Backstopper projects that utilize JSR 11 | 303 validations. 12 | 13 | ## Custom JSR 303 Validation Constraints 14 | 15 | * **`StringConvertsToClassType`** - Validates that the annotated element (of type String) can be converted to the 16 | desired `classType`. The `classType` can be any of the following: 17 | * Any boxed primitive class type (e.g. `Integer.class`). 18 | * Any raw primitive class type (e.g. `int.class`). 19 | * `String.class` - a String can always be converted to a String, so this validator will always return true in this 20 | case. 21 | * Any enum class type - validation is done by comparing the string value to `Enum.name()`. The value of the 22 | `allowCaseInsensitiveEnumMatch()` constraint property determines if the validation is done in a case sensitive or 23 | case insensitive manner. 24 | * `null` is always considered valid - if you need to enforce non-null then you should place an additional `@NotNull` 25 | constraint on the field as well. 26 | * More information and usage instructions can be found in the javadocs for `StringConvertsToClassType`, but here's 27 | an example showing how you would mark a model field that you wanted to guarantee was convertible to a `RgbColor` 28 | enum after passing JSR 303 validation: 29 | 30 | ``` java 31 | @StringConvertsToClassType( 32 | message = "NOT_RGB_COLOR_ENUM", classType = RgbColor.class, allowCaseInsensitiveEnumMatch = true 33 | ) 34 | public final String rgb_color; 35 | ``` 36 | 37 | ## More Info 38 | 39 | See the [base project README.md](../README.md), [User Guide](../USER_GUIDE.md), and Backstopper repository source code 40 | and javadocs for all further information. 41 | 42 | ## License 43 | 44 | Backstopper is released under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 45 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/handler/listener/impl/GenericApiExceptionHandlerListener.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.handler.listener.impl; 2 | 3 | import com.nike.backstopper.apierror.SortedApiErrorSet; 4 | import com.nike.backstopper.exception.ApiException; 5 | import com.nike.backstopper.handler.listener.ApiExceptionHandlerListener; 6 | import com.nike.backstopper.handler.listener.ApiExceptionHandlerListenerResult; 7 | import com.nike.internal.util.Pair; 8 | import com.nike.internal.util.StringUtils; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import jakarta.inject.Named; 14 | import jakarta.inject.Singleton; 15 | 16 | /** 17 | * Handles generic {@link ApiException} errors by simply setting {@link ApiExceptionHandlerListenerResult#errors} to 18 | * {@link ApiException#getApiErrors()} and adding any {@link ApiException#getExtraDetailsForLogging()} and/or 19 | * {@link ApiException#getExtraResponseHeaders()}. 20 | */ 21 | @Named 22 | @Singleton 23 | public class GenericApiExceptionHandlerListener implements ApiExceptionHandlerListener { 24 | @Override 25 | public ApiExceptionHandlerListenerResult shouldHandleException(Throwable ex) { 26 | // We only care about ApiExceptions. 27 | if (!(ex instanceof ApiException apiException)) 28 | return ApiExceptionHandlerListenerResult.ignoreResponse(); 29 | 30 | // Add all the ApiErrors from the exception. 31 | SortedApiErrorSet errors = new SortedApiErrorSet(); 32 | errors.addAll(apiException.getApiErrors()); 33 | 34 | // Add all the extra details for logging from the exception. 35 | List> messages = new ArrayList<>(apiException.getExtraDetailsForLogging()); 36 | 37 | // Add all the extra response headers from the exception. 38 | List>> headers = new ArrayList<>(apiException.getExtraResponseHeaders()); 39 | 40 | // Include the ApiException's message as a logged key/value pair. 41 | if (StringUtils.isNotBlank(ex.getMessage())) 42 | messages.add(Pair.of("api_exception_message", ex.getMessage())); 43 | 44 | if (ex.getCause() != null) { 45 | messages.add(Pair.of("exception_cause_class", ex.getCause().getClass().getName())); 46 | messages.add(Pair.of("exception_cause_message", ex.getCause().getMessage())); 47 | } 48 | 49 | return ApiExceptionHandlerListenerResult.handleResponse(errors, messages, headers); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webmvc/src/main/java/com/nike/backstopper/springboot3webmvcsample/model/SampleModel.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springboot3webmvcsample.model; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.sample.SampleCoreApiError; 5 | import com.nike.backstopper.springboot3webmvcsample.error.SampleProjectApiError; 6 | import com.nike.backstopper.springboot3webmvcsample.error.SampleProjectApiErrorsImpl; 7 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 8 | 9 | import org.hibernate.validator.constraints.Range; 10 | 11 | import jakarta.validation.constraints.NotBlank; 12 | import jakarta.validation.constraints.NotNull; 13 | 14 | /** 15 | * Simple model class showing the JSR 303 Bean Validation integration in Backstopper. Each message for a JSR 303 16 | * annotation must match an {@link ApiError#getName()} from one of the errors returned by this project's 17 | * {@link SampleProjectApiErrorsImpl#getProjectApiErrors()}. In this case that means you can use any of the enum 18 | * names from {@link SampleCoreApiError} or {@link SampleProjectApiError}. 19 | * 20 | *

If you have a typo or forget to add a message that matches an error name then the {@code VerifyJsr303ContractTest} 21 | * unit test will catch your error and the project will fail to build - the test will give you info on exactly which 22 | * classes, fields, and annotations don't conform to the necessary convention. 23 | */ 24 | public class SampleModel { 25 | @NotBlank(message = "FOO_STRING_CANNOT_BE_BLANK") 26 | public final String foo; 27 | 28 | @Range(message = "INVALID_RANGE_VALUE", min = 0, max = 42) 29 | public final String range_0_to_42; 30 | 31 | @NotNull(message = "RGB_COLOR_CANNOT_BE_NULL") 32 | @StringConvertsToClassType( 33 | message = "NOT_RGB_COLOR_ENUM", classType = RgbColor.class, allowCaseInsensitiveEnumMatch = true 34 | ) 35 | public final String rgb_color; 36 | 37 | public final Boolean throw_manual_error; 38 | 39 | @SuppressWarnings("unused") 40 | // Intentionally protected - here for deserialization support. 41 | protected SampleModel() { 42 | this(null, null, null, null); 43 | } 44 | 45 | public SampleModel(String foo, String range_0_to_42, String rgb_color, Boolean throw_manual_error) { 46 | this.foo = foo; 47 | this.range_0_to_42 = range_0_to_42; 48 | this.rgb_color = rgb_color; 49 | this.throw_manual_error = throw_manual_error; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /samples/sample-spring-boot3-webflux/src/main/java/com/nike/backstopper/springboot3webfluxsample/model/SampleModel.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.springboot3webfluxsample.model; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.sample.SampleCoreApiError; 5 | import com.nike.backstopper.springboot3webfluxsample.error.SampleProjectApiError; 6 | import com.nike.backstopper.springboot3webfluxsample.error.SampleProjectApiErrorsImpl; 7 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 8 | 9 | import org.hibernate.validator.constraints.Range; 10 | 11 | import jakarta.validation.constraints.NotBlank; 12 | import jakarta.validation.constraints.NotNull; 13 | 14 | /** 15 | * Simple model class showing the JSR 303 Bean Validation integration in Backstopper. Each message for a JSR 303 16 | * annotation must match an {@link ApiError#getName()} from one of the errors returned by this project's 17 | * {@link SampleProjectApiErrorsImpl#getProjectApiErrors()}. In this case that means you can use any of the enum 18 | * names from {@link SampleCoreApiError} or {@link SampleProjectApiError}. 19 | * 20 | *

If you have a typo or forget to add a message that matches an error name then the {@code VerifyJsr303ContractTest} 21 | * unit test will catch your error and the project will fail to build - the test will give you info on exactly which 22 | * classes, fields, and annotations don't conform to the necessary convention. 23 | */ 24 | public class SampleModel { 25 | @NotBlank(message = "FOO_STRING_CANNOT_BE_BLANK") 26 | public final String foo; 27 | 28 | @Range(message = "INVALID_RANGE_VALUE", min = 0, max = 42) 29 | public final String range_0_to_42; 30 | 31 | @NotNull(message = "RGB_COLOR_CANNOT_BE_NULL") 32 | @StringConvertsToClassType( 33 | message = "NOT_RGB_COLOR_ENUM", classType = RgbColor.class, allowCaseInsensitiveEnumMatch = true 34 | ) 35 | public final String rgb_color; 36 | 37 | public final Boolean throw_manual_error; 38 | 39 | @SuppressWarnings("unused") 40 | // Intentionally protected - here for deserialization support. 41 | protected SampleModel() { 42 | this(null, null, null, null); 43 | } 44 | 45 | public SampleModel(String foo, String range_0_to_42, String rgb_color, Boolean throw_manual_error) { 46 | this.foo = foo; 47 | this.range_0_to_42 = range_0_to_42; 48 | this.rgb_color = rgb_color; 49 | this.throw_manual_error = throw_manual_error; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /backstopper-core/src/main/java/com/nike/backstopper/apierror/projectspecificinfo/range/IntegerRange.java: -------------------------------------------------------------------------------- 1 | package com.nike.backstopper.apierror.projectspecificinfo.range; 2 | 3 | /** 4 | * Helper class useful for implementing the concept of an integer range for 5 | * {@link com.nike.backstopper.apierror.projectspecificinfo.ProjectSpecificErrorCodeRange}. 6 | * 7 | * @author Nic Munroe 8 | */ 9 | @SuppressWarnings("WeakerAccess") 10 | public class IntegerRange { 11 | 12 | /** 13 | * The lower bound for this range (inclusive). 14 | */ 15 | public final int lowerRangeInclusive; 16 | /** 17 | * The upper bound for this range (inclusive). 18 | */ 19 | public final int upperRangeInclusive; 20 | 21 | @SuppressWarnings("WeakerAccess") 22 | protected IntegerRange(int lowerRangeInclusive, int upperRangeInclusive) { 23 | if (upperRangeInclusive < lowerRangeInclusive) { 24 | throw new IllegalArgumentException( 25 | "upper range value (" + upperRangeInclusive + ") cannot be less than lower range value (" 26 | + lowerRangeInclusive + ")" 27 | ); 28 | } 29 | 30 | this.lowerRangeInclusive = lowerRangeInclusive; 31 | this.upperRangeInclusive = upperRangeInclusive; 32 | } 33 | 34 | /** 35 | * @param lowerRangeInclusive The lower bound of this range (inclusive). 36 | * @param upperRangeInclusive The upper bound of this range (inclusive). 37 | * @return A new instance with the given bounds. 38 | */ 39 | public static IntegerRange of(int lowerRangeInclusive, int upperRangeInclusive) { 40 | return new IntegerRange(lowerRangeInclusive, upperRangeInclusive); 41 | } 42 | 43 | /** 44 | * @return true if the given {@code valueString} can be parsed to an integer and is within the range bounds of this 45 | * instance, false otherwise. 46 | */ 47 | public boolean isInRange(String valueString) { 48 | try { 49 | int valueAsInt = Integer.parseInt(valueString); 50 | return isInRange(valueAsInt); 51 | } catch (NumberFormatException ex) { 52 | // Not an integer, so can't possibly be in range. 53 | return false; 54 | } 55 | } 56 | 57 | /** 58 | * @return true if the given {@code value} is within the range bounds of this instance, false otherwise. 59 | */ 60 | public boolean isInRange(int value) { 61 | return (value >= lowerRangeInclusive && value <= upperRangeInclusive); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /testonly/testonly-spring-webflux-reusable-test-support/src/main/java/testonly/componenttest/spring/reusable/model/SampleModel.java: -------------------------------------------------------------------------------- 1 | package testonly.componenttest.spring.reusable.model; 2 | 3 | import com.nike.backstopper.apierror.ApiError; 4 | import com.nike.backstopper.apierror.sample.SampleCoreApiError; 5 | import com.nike.backstopper.validation.constraints.StringConvertsToClassType; 6 | 7 | import org.hibernate.validator.constraints.Range; 8 | 9 | import jakarta.validation.constraints.NotBlank; 10 | import jakarta.validation.constraints.NotNull; 11 | import testonly.componenttest.spring.reusable.error.SampleProjectApiError; 12 | import testonly.componenttest.spring.reusable.error.SampleProjectApiErrorsImpl; 13 | 14 | /** 15 | * Simple model class showing the JSR 303 Bean Validation integration in Backstopper. Each message for a JSR 303 16 | * annotation must match an {@link ApiError#getName()} from one of the errors returned by this project's 17 | * {@link SampleProjectApiErrorsImpl#getProjectApiErrors()}. In this case that means you can use any of the enum 18 | * names from {@link SampleCoreApiError} or {@link SampleProjectApiError}. 19 | * 20 | *

If you have a typo or forget to add a message that matches an error name then the {@code VerifyJsr303ContractTest} 21 | * unit test will catch your error and the project will fail to build - the test will give you info on exactly which 22 | * classes, fields, and annotations don't conform to the necessary convention. 23 | */ 24 | @SuppressWarnings("WeakerAccess") 25 | public class SampleModel { 26 | @NotBlank(message = "FOO_STRING_CANNOT_BE_BLANK") 27 | public final String foo; 28 | 29 | @Range(message = "INVALID_RANGE_VALUE", min = 0, max = 42) 30 | public final String range_0_to_42; 31 | 32 | @NotNull(message = "RGB_COLOR_CANNOT_BE_NULL") 33 | @StringConvertsToClassType( 34 | message = "NOT_RGB_COLOR_ENUM", classType = RgbColor.class, allowCaseInsensitiveEnumMatch = true 35 | ) 36 | public final String rgb_color; 37 | 38 | public final Boolean throw_manual_error; 39 | 40 | @SuppressWarnings("unused") 41 | // Intentionally protected - here for deserialization support. 42 | protected SampleModel() { 43 | this(null, null, null, null); 44 | } 45 | 46 | public SampleModel(String foo, String range_0_to_42, String rgb_color, Boolean throw_manual_error) { 47 | this.foo = foo; 48 | this.range_0_to_42 = range_0_to_42; 49 | this.rgb_color = rgb_color; 50 | this.throw_manual_error = throw_manual_error; 51 | } 52 | } 53 | --------------------------------------------------------------------------------