Devbrat Dash
Devbrat Dash

Reputation: 41

Error in Controller layer error handling in spring webflux

So I want to achieve the functionality of saving an user for that first i check whether user is present if present throw an exception else save the user But when I throw an exception from service layer .flatMap(user -> Mono.error(new IllegalArgumentException("User Exists with email " + user.getEmail())))

@Service
@RequiredArgsConstructor
public class AppUserService {

    private final AppUserRepository appUserRepository;

    public Flux<AppUser> getAllUsers() {
        return appUserRepository.findAll();
    }

    public Mono<AppUser> saveUser(AppUser appUser) {
        return getUser(appUser.getEmail())
                .flatMap(user -> Mono.error(new IllegalArgumentException("User Exists with email " + user.getEmail())))
                .switchIfEmpty(Mono.defer(() -> appUserRepository.save(appUser))).cast(AppUser.class).log();
    }

    public Mono<AppUser> getUser(String email) {
        return appUserRepository.findFirstByEmail(email);
    }
}

and in the controller layer if I handle it like .onErrorResume(error -> ServerResponse.badRequest().bodyValue(error))

@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class AppUserController {

    private final AppUserService appUserService;
    private final PasswordEncoder encoder;

    @GetMapping
    public Flux<AppUser> getAllUsers(@RequestHeader("email") String email) {
        return appUserService.getAllUsers();
    }

    @PostMapping
    @CrossOrigin
    public Mono<ResponseEntity<Object>> saveUser(@RequestBody Mono<AppUser> appUserMono) {
        return appUserMono
        .doOnSuccess(appUser -> appUser.setPassword(encoder.encode(appUser.getPassword())))
        .subscribeOn(Schedulers.parallel())
        .flatMap(appUserService::saveUser)
        .flatMap(savedAppUser -> ResponseEntity.created(URI.create("/user/" + savedAppUser.getId())).build())
        .onErrorResume(error -> Response entity.badRequest().bodyValue(error))
        .log();
    }

}

it throws an error on the console

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.springframework.web.reactive.function.server.DefaultEntityResponseBuilder$DefaultEntityResponse and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS)

and returns 200 to the client

what am i doing wrong

After reading the error it seems its getting an empty value but if i debug the flow at the onErrorResume(error -> ..) the error variable has the error can't understand why it still throws the jackson error is it because jackson can't subscribe to ServerResponse or something around that

Upvotes: 2

Views: 2257

Answers (1)

Mustafa
Mustafa

Reputation: 6144

There are few issues in your code.

Here's how you can get it to run (without the repository):

package test;

import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class AppUser {
  private String id;

  private String username;

  private String firstName;

  private String password;

  private String email;
}

@Service
@RequiredArgsConstructor
public class AppUserService {

  public Mono<AppUser> saveUser(AppUser appUser) {
    return getUser(appUser.getEmail())
        .flatMap(user -> Mono.<AppUser>error(
            new IllegalArgumentException("User Exists with email " + user.getEmail())))
        .switchIfEmpty(Mono.defer(() -> Mono.just(AppUser.builder().username("mustafa").build())));
  }

  public Mono<AppUser> getUser(String email) {
//    return Mono.defer(() -> Mono.just(AppUser.builder().email(email).build()));
    return Mono.defer(Mono::empty);
  }
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class AppUserController {

  private final AppUserService appUserService;

  @PostMapping
  @CrossOrigin
  public Mono<ResponseEntity<AppUser>> saveUser(@RequestBody Mono<AppUser> appUserMono,
                                                @RequestHeader("email") String email) {
    return appUserMono
        .subscribeOn(Schedulers.parallel())
        .flatMap(appUserService::saveUser)
        .flatMap(savedAppUser -> Mono.just(
            ResponseEntity.created(URI.create("/user/" + savedAppUser.getId())).body(savedAppUser)))
        .onErrorResume(error -> Mono.just(ResponseEntity.badRequest().build()))
        .log();
  }
}

Your controller's return type is Mono<ResponseEntity<AppUser>> but you are returning ServerResponse. I fixed this and removed the cast from your service code.

You can experiment with producing a 400 or a valid result by commenting/uncommenting the getUser body.

Maybe a clearer way to write the email check logic is with the good old if/else statement:

  public Mono<AppUser> saveUser(AppUser appUser) {
    if (emailExists(appUser.getEmail())) {
      return Mono.error(new IllegalArgumentException("User Exists with email "
          + appUser.getEmail()));
    } else {
      return Mono.just(AppUser.builder().username("mustafa").build());
    }
  }

Upvotes: 2

Related Questions