Skip to content

Commit

Permalink
Release 2.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sajjaadalipour authored Dec 13, 2023
2 parents 41d2b04 + 875992a commit 99ff40a
Show file tree
Hide file tree
Showing 48 changed files with 447 additions and 739 deletions.
55 changes: 55 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Build
on:
push:
branches:
- master

jobs:
build:
name: Build
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: 21
distribution: 'oracle'
server-id: github
settings-path: ${{ github.workspace }}

- name: Cache SonarCloud packages
uses: actions/cache@v3
with:
path: ~/.sonar/cache
key: ${{ runner.os }}-sonar
restore-keys: ${{ runner.os }}-sonar

- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2

- name: Build and analyze
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=sajjaadalipour_errors-spring-boot-starter

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

- name: Publish to GitHub Packages Apache Maven
run: mvn deploy -s $GITHUB_WORKSPACE/settings.xml
env:
GITHUB_TOKEN: ${{ github.token }}
24 changes: 0 additions & 24 deletions .travis.yml

This file was deleted.

168 changes: 59 additions & 109 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
<p align="center">
<img width="300" height="300" src="https://imgur.com/cOcB3kB.png" />
<img width="300" height="300" src="https://imgur.com/cOcB3kB.png" alt="error-spring-boot-starter"/>
</p>

<h1 align="center">Errors Spring Boot Starter</h1>

[![Build Status](https://travis-ci.org/alimate/errors-spring-boot-starter.svg?branch=master)](https://travis-ci.org/alimate/errors-spring-boot-starter)
[![codecov](https://codecov.io/gh/alimate/errors-spring-boot-starter/branch/master/graph/badge.svg)](https://codecov.io/gh/alimate/errors-spring-boot-starter)
[![Maven Central](https://img.shields.io/maven-central/v/me.alidg/errors-spring-boot-starter.svg)](https://search.maven.org/search?q=g:me.alidg%20AND%20a:errors-spring-boot-starter)
[![Javadocs](http://www.javadoc.io/badge/me.alidg/errors-spring-boot-starter.svg)](http://www.javadoc.io/doc/me.alidg/errors-spring-boot-starter)
[![Sonatype](https://img.shields.io/static/v1.svg?label=snapshot&message=v1.5.0-SNAPSHOT&color=blueviolet)](https://oss.sonatype.org/service/local/artifact/maven/redirect?r=snapshots&g=me.alidg&a=errors-spring-boot-starter&v=1.5.0-SNAPSHOT&e=jar)
[![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/alimate_errors-spring-boot-starter?label=code%20quality&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=alimate_errors-spring-boot-starter)
[![Build Status](https://github.com/sajjaadalipour/errors-spring-boot-starter/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/sajjaadalipour/errors-spring-boot-starter/actions)
[![codecov](https://codecov.io/gh/sajjaadalipour/errors-spring-boot-starter/graph/badge.svg?token=LEBHWWZO97)](https://codecov.io/gh/sajjaadalipour/errors-spring-boot-starter)
[![Maven Central](https://img.shields.io/maven-central/v/sajjaadalipour/errors-spring-boot-starter.svg)](https://search.maven.org/search?q=g:me.alidg%20AND%20a:errors-spring-boot-starter)
[![Javadocs](http://www.javadoc.io/badge/sajjaadalipour/errors-spring-boot-starter.svg)](http://www.javadoc.io/doc/me.alidg/errors-spring-boot-starter)
[![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/sajjaadalipour_errors-spring-boot-starter?label=code%20quality&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/dashboard?id=sajjaadalipour_errors-spring-boot-starter)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)

<p align="center"><b>A Bootiful, Consistent and Opinionated Approach to Handle all sorts of Exceptions.</b></p>
<p align="center"><b>A Bountiful, Consistent and Opinionated Approach to Handle all sorts of Exceptions.</b></p>

## Table of Contents

Expand Down Expand Up @@ -40,8 +39,6 @@
+ [Logging Exceptions](#logging-exceptions)
+ [Post Processing Handled Exceptions](#post-processing-handled-exceptions)
+ [Registering Custom Handlers](#registering-custom-handlers)
+ [Test Support](#test-support)
* [Appendix](#appendix)
+ [Configuration](#configuration)
* [License](#license)

Expand All @@ -50,7 +47,7 @@ Built on top of Spring Boot's great exception handling mechanism, the `errors-sp
- A consistent approach to handle all exceptions. Doesn't matter if it's a validation/binding error or a
custom domain-specific error or even a Spring related error, All of them would be handled by a `WebErrorHandler`
implementation (No more `ErrorController` vs `@ExceptionHandler` vs `WebExceptionHandler`)
- Built-in support for application specific error codes, again, for all possible errors.
- Built-in support for application-specific error codes, again, for all possible errors.
- Simple error message interpolation using plain old `MessageSource`s.
- Customizable HTTP error representation.
- Exposing arguments from exceptions to error messages.
Expand All @@ -62,7 +59,7 @@ Built on top of Spring Boot's great exception handling mechanism, the `errors-sp

### Download

Download the [latest JAR](https://search.maven.org/remotecontent?filepath=me/alidg/errors-spring-boot-starter/1.4.0/errors-spring-boot-starter-1.4.0.jar) or grab via Maven:
#### Compatible With Spring Boot 2.2.0.RELEASE and Java 8+

```xml
<dependency>
Expand All @@ -77,29 +74,20 @@ or Gradle:
compile "me.alidg:errors-spring-boot-starter:1.4.0"
```

If you like to stay at the cutting edge, use our `1.5.0-SNAPSHOT` version. Of course you should define the following
snapshot repository:
#### Compatible With Spring Boot 3.2.0+ and Java 21

```xml
<repositories>
<repository>
<id>Sonatype</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
</repositories>
```
or:
```groovy
repositories {
maven {
url 'https://oss.sonatype.org/content/repositories/snapshots/'
}
}
<dependency>
<groupId>com.github.sajjaadalipour</groupId>
<artifactId>errors-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
```

### Prerequisites
The main dependency is JDK 8+. Tested with:
- JDK 8, JDK 9, JDK 10 and JDK 11 on Linux.
- Spring Boot `2.2.0.RELEASE` (Also, should work with any `2.0.0+`)
or Gradle:
```
compile "com.github.sajjaadalipour:errors-spring-boot-starter:2.0.0"
```

### Overview
The `WebErrorHandler` implementations are responsible for handling different kinds of exceptions. When an exception
Expand Down Expand Up @@ -161,13 +149,15 @@ In `errors-spring-boot-starter`, one can map exceptions to error codes in differ
```

### Error Message
Once the exception mapped to error code(s), we can add a companion and *Human Readable* error message. This can be done
by registering a Spring `MessageSource` to perform the *code-to-message* translation. For example, if we add the following
Once the exception is mapped to error code(s), we can add a companion and *Human Readable* error message.
This can be done
by registering a Spring `MessageSource` to perform the *code-to-message* translation.
For example, if we add the following
key-value pair in our message resource file:
```properties
user.already_exists=Another user with the same username already exists
```
Then if an exception of type `UserAlreadyExistsException` was thrown, you would see a `400 Bad Request` HTTP response
Then if an exception to type `UserAlreadyExistsException` was thrown, you would see a `400 Bad Request` HTTP response
with a body like:
```json
{
Expand Down Expand Up @@ -223,7 +213,8 @@ Then the `username` property from the `UserAlreadyExistsException` would be avai
return type. The `HandledException` class also accepts the *to-be-exposed* arguments in its constructor.

#### Exposing Named Arguments
By default error arguments will be used in message interpolation only. It is also possible to additionally get those
By default, error arguments will be used in message interpolation only.
It is also possible to additionally get those
arguments in error response by defining the configuration property `errors.expose-arguments`.
When enabled, you might get the following response payload:
```json
Expand All @@ -240,7 +231,7 @@ When enabled, you might get the following response payload:
}
```

The `errors.expose-arguments` property takes 3 possible values:
The `errors.expose-arguments` property takes three possible values:
- `NEVER` - named arguments will never be exposed. This is the default setting.
- `NON_EMPTY` - named arguments will be exposed only in case there are any. If error has no arguments,
result payload will not have `"arguments"` element.
Expand Down Expand Up @@ -388,56 +379,58 @@ common Spring Security exceptions:

#### Reactive Security
When the Spring Security is detected along with the Reactive stack, the starter registers two extra handlers to handle
all security related exceptions. In contrast with other handlers which register themselves automatically, in order to use these
all security-related exceptions. In contrast with other handlers which register themselves automatically, in order to use these
two handlers, you should register them in your security configuration manually as follows:
```java
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
public class WebFluxSecurityConfig {

// other configurations
public class ReactiveConfig {

@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ServerAccessDeniedHandler accessDeniedHandler,
ServerAuthenticationEntryPoint authenticationEntryPoint) {
http
.csrf().accessDeniedHandler(accessDeniedHandler)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint)
// other configurations

return http.build();
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http,
ServerAccessDeniedHandler accessDeniedHandler,
ServerAuthenticationEntryPoint authenticationEntryPoint) {
return http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.exceptionHandling(configurer -> configurer
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler))
.build();
}
}

```
The registered `ServerAccessDeniedHandler` and `ServerAuthenticationEntryPoint` are responsible for handling `AccessDeniedException`
and `AuthenticationException` exceptions, respectively.

#### Servlet Security
When the Spring Security is detected along with the traditional servlet stack, the starter registers two extra handlers to handle
all security related exceptions. In contrast with other handlers which register themselves automatically, in order to use these
all security-related exceptions. In contrast with other handlers which register themselves automatically, in order to use these
two handlers, you should register them in your security configuration manually as follows:
```java
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@EnableMethodSecurity
public class ServletConfig {

private final AccessDeniedHandler accessDeniedHandler;
private final AuthenticationEntryPoint authenticationEntryPoint;

public SecurityConfig(AccessDeniedHandler accessDeniedHandler, AuthenticationEntryPoint authenticationEntryPoint) {
public ServletConfig(AccessDeniedHandler accessDeniedHandler, AuthenticationEntryPoint authenticationEntryPoint) {
this.accessDeniedHandler = accessDeniedHandler;
this.authenticationEntryPoint = authenticationEntryPoint;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.anonymous(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.exceptionHandling(configurer -> configurer.authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler))
.build();
}

}
```
The registered `AccessDeniedHandler` and `AuthenticationEntryPoint` are responsible for handling `AccessDeniedException`
Expand All @@ -458,8 +451,8 @@ By default, errors would manifest themselves in the HTTP response bodies with th

#### Fingerprinting
There is also an option to generate error `fingerprint`. Fingerprint is a unique hash of error
event which might be used as a correlation ID of error presented to user, and reported in
application backend (e.g. in detailed log message). To generate error fingerprints, add
event that might be used as a correlation ID of error presented to user, and reported in
application backend (e.g., in a detailed log message). To generate error fingerprints, add
the configuration property `errors.add-fingerprint=true`.

We provide two fingerprint providers implementations:
Expand Down Expand Up @@ -514,7 +507,7 @@ public class CustomExceptionRefiner implements ExceptionRefiner {

### Logging Exceptions
By default, the starter issues a few `debug` logs under the `me.alidg.errors.WebErrorHandlers` logger name.
In order to customize the way we log exceptions, we just need to implement the `ExceptionLogger` interface and register it
To customize the way we log exceptions, we just need to implement the `ExceptionLogger` interface and register it
as a *Spring Bean*:
```java
@Component
Expand Down Expand Up @@ -555,7 +548,7 @@ public class LoggingErrorWebErrorHandlerPostProcessor implements WebErrorHandler
```

### Registering Custom Handlers
In order to provide a custom handler for a specific exception, just implement the `WebErrorHandler` interface for that
To provide a custom handler for a specific exception, just implement the `WebErrorHandler` interface for that
exception and register it as a *Spring Bean*:
```java
@Component
Expand All @@ -578,49 +571,6 @@ If you're going to register multiple handlers, you can change their priority usi
handlers would be registered after built-in exception handlers (Validation, `ExceptionMapping`, etc.). If you don't like
this idea, provide a custom *Bean* of type `WebErrorHandlers` and the default one would be discarded.

### Test Support
In order to enable our test support for `WebMvcTest`s, just add the `@AutoConfigureErrors` annotation to your test
class. That's how a `WebMvcTest` would look like with errors support enabled:
```java
@AutoConfigureErrors
@RunWith(SpringRunner.class)
@WebMvcTest(UserController.class)
public class UserControllerIT {

@Autowired private MockMvc mvc;

@Test
public void createUser_ShouldReturnBadRequestForInvalidBodies() throws Exception {
mvc.perform(post("/users").content("{}"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.errors[0].code").value("username.required"));
}
}
```
For `WebFluxTest`s, the test support is almost the same as the Servlet stack:
```java
@AutoConfigureErrors
@RunWith(SpringRunner.class)
@WebFluxTest(UserController.class)
@ImportAutoConfiguration(ErrorWebFluxAutoConfiguration.class) // Drop this if you're using Spring Boot 2.1.4+
public class UserControllerIT {

@Autowired private WebTestClient client;

@Test
public void createUser_ShouldReturnBadRequestForInvalidBodies() {
client.post()
.uri("/users")
.syncBody("{}").header(CONTENT_TYPE, APPLICATION_JSON_UTF8_VALUE)
.exchange()
.expectStatus().isBadRequest()
.expectBody().jsonPath("$.errors[0].code").isEqualTo("username.required");
}
}
```

## Appendix

### Configuration
Additional configuration of this starter can be provided by configuration properties - the Spring Boot way.
All configuration properties start with `errors`. Below is a list of supported properties:
Expand Down
Loading

0 comments on commit 99ff40a

Please sign in to comment.