Skip to content

Commit

Permalink
Merge pull request #716 from exacaster/unify_error_responses
Browse files Browse the repository at this point in the history
Unify error responses
  • Loading branch information
pdambrauskas authored Oct 21, 2023
2 parents b2d7847 + f962c64 commit 1289a4d
Show file tree
Hide file tree
Showing 15 changed files with 203 additions and 116 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.server.types.files.StreamedFile;
import io.swagger.v3.oas.annotations.Hidden;

import java.util.Optional;

@Hidden
@Controller("/lighter")
public class IndexController {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.exacaster.lighter.application.ApplicationState;
import com.exacaster.lighter.application.ApplicationType;
import com.exacaster.lighter.application.SubmitParams;
import com.exacaster.lighter.application.sessions.exceptions.InvalidSessionStateException;
import com.exacaster.lighter.application.sessions.processors.StatementHandler;
import com.exacaster.lighter.backend.Backend;
import com.exacaster.lighter.storage.ApplicationStorage;
Expand Down Expand Up @@ -107,14 +108,14 @@ public void killOne(Application app) {
applicationStorage.saveApplication(ApplicationBuilder.builder(app).setState(ApplicationState.KILLED).build());
}

public StatementCreationResult createStatement(String id, Statement statement) {
public Optional<Statement> createStatement(String id, Statement statement) {
return this.fetchOne(id, true).map(application -> {
if (!application.getState().isComplete()) {
return new StatementCreationResult.StatementCreated(statementHandler.processStatement(application.getId(), statement));
return statementHandler.processStatement(application.getId(), statement);
} else {
return new StatementCreationResult.SessionInInvalidState(application.getState());
throw new InvalidSessionStateException(application.getState());
}
}).orElse(new StatementCreationResult.NoSessionExists());
});
}

public Statement getStatement(String id, String statementId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.exacaster.lighter.application.sessions.processors.Output;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.Nullable;

import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.StringJoiner;

Expand All @@ -26,6 +28,7 @@ public String getId() {
return id;
}

@NotNull
public String getCode() {
return code;
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.exacaster.lighter.application.sessions.exceptions;

import com.exacaster.lighter.application.ApplicationState;

public class InvalidSessionStateException extends RuntimeException {
private final ApplicationState sessionState;

public InvalidSessionStateException(ApplicationState state) {
super("Invalid session state: " + state);
this.sessionState = state;
}

public ApplicationState getSessionState() {
return sessionState;
}

@Override
public String toString() {
return "InvalidSessionStateException{" +
"sessionState=" + sessionState +
'}';
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@
import io.micronaut.http.annotation.Post;
import io.micronaut.http.annotation.QueryValue;
import io.micronaut.validation.Validated;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.tags.Tags;

import java.util.Optional;
import javax.validation.Valid;

@Tags(@Tag(name = "Batches"))
@Validated
@Controller("/lighter/api/batches")
public class BatchController {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import com.exacaster.lighter.configuration.AppConfiguration;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.tags.Tags;

@Tags(@Tag(name = "Configuration"))
@Controller("/lighter/api/configuration")
public class ConfigurationController {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
import com.exacaster.lighter.application.SubmitParams;
import com.exacaster.lighter.application.sessions.SessionService;
import com.exacaster.lighter.application.sessions.Statement;
import com.exacaster.lighter.application.sessions.StatementCreationResultMapper;
import com.exacaster.lighter.application.sessions.StatementList;
import com.exacaster.lighter.log.LogService;
import com.exacaster.lighter.rest.exceptions.ErrorResponse;
import com.exacaster.lighter.rest.magic.SessionList;
import com.exacaster.lighter.rest.magic.SparkMagicCompatibility;
import com.exacaster.lighter.rest.mappers.StatementCreationResultToApiResponseMapper;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.annotation.Body;
import io.micronaut.http.annotation.Controller;
Expand All @@ -28,6 +26,8 @@
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.tags.Tags;

import javax.validation.Valid;
import java.util.HashMap;
Expand All @@ -36,19 +36,18 @@
import java.util.Optional;

@Validated
@Tags(@Tag(name = "Sessions"))
@Controller("/lighter/api/sessions")
public class SessionController {

private final SessionService sessionService;
private final LogService logService;
private final SparkMagicCompatibility magicCompatibility;
private final StatementCreationResultMapper<HttpResponse> statementCreationResultMapper;

public SessionController(SessionService sessionService, LogService logService, SparkMagicCompatibility magicCompatibility) {
this.sessionService = sessionService;
this.logService = logService;
this.magicCompatibility = magicCompatibility;
this.statementCreationResultMapper = new StatementCreationResultToApiResponseMapper();
}

@Get
Expand All @@ -63,7 +62,7 @@ public Object get(@QueryValue(defaultValue = "0") Integer from,

@Post
@Status(HttpStatus.CREATED)
public Application create(@Body SubmitParams session) {
public Application create(@Valid @Body SubmitParams session) {
return sessionService.createSession(session);
}

Expand Down Expand Up @@ -103,11 +102,11 @@ public Optional<Object> getLog(@PathVariable String id, @Nullable @Header("X-Com
@Post("/{id}/statements")
@ApiResponses(value = {
@ApiResponse(responseCode = "201", description = "Statement created", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = Statement.class))}),
@ApiResponse(responseCode = "400", description = "Session in invalid state", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = StatementCreationResultToApiResponseMapper.InvalidSessionStateResponse.class))}),
@ApiResponse(responseCode = "404", description = "Session not found")
@ApiResponse(responseCode = "400", description = "Session in invalid state", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}),
@ApiResponse(responseCode = "404", description = "Session not found", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))})
})
public HttpResponse postStatements(@PathVariable String id, @Valid @Body Statement statement) {
return sessionService.createStatement(id, statement).map(statementCreationResultMapper);
public Optional<Statement> postStatements(@PathVariable String id, @Valid @Body Statement statement) {
return sessionService.createStatement(id, statement);
}

@Get("/{id}/statements")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.exacaster.lighter.rest.exceptions;

import io.micronaut.context.annotation.Primary;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.HttpMethod;
import io.micronaut.http.MediaType;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.server.exceptions.response.ErrorContext;
import io.micronaut.http.server.exceptions.response.ErrorResponseProcessor;

import javax.inject.Singleton;

@Primary
@Singleton
public class ApiErrorResponseProcessor implements ErrorResponseProcessor<ErrorResponse> {
@NonNull
@Override
public MutableHttpResponse<ErrorResponse> processResponse(@NonNull ErrorContext errorContext,
@NonNull MutableHttpResponse response) {
if (errorContext.getRequest().getMethod() == HttpMethod.HEAD) {
return (MutableHttpResponse<ErrorResponse>) response;
}
var path = errorContext.getRequest().getUri();
var error = new ErrorResponse(response.getStatus().getReason(), path, errorContext.getErrors());

return response.body(error).contentType(MediaType.APPLICATION_JSON_TYPE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.exacaster.lighter.rest.exceptions;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.http.server.exceptions.response.Error;

@Introspected
public class DetailedError implements Error {

private final String message;

private final Object details;

public DetailedError(String message, Object details) {
this.message = message;
this.details = details;
}

@Override
public String getMessage() {
return message;
}

public Object getDetails() {
return details;
}

@Override
public String toString() {
return "DetailedError{" +
"message='" + message + '\'' +
", details=" + details +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.exacaster.lighter.rest.exceptions;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.http.server.exceptions.response.Error;

import java.net.URI;
import java.util.List;

@Introspected
public class ErrorResponse {
private final String message;
private final URI path;
private final List<Error> errors;

public ErrorResponse(String message, URI path, List<Error> errors) {
this.message = message;
this.path = path;
this.errors = errors;
}

public ErrorResponse(String message, URI path) {
this(message, path, List.of());
}


public String getMessage() {
return message;
}

public URI getPath() {
return path;
}

public List<Error> getErrors() {
return errors;
}

@Override
public String toString() {
return "ErrorResponse{" +
"message='" + message + '\'' +
", path=" + path +
", errors=" + errors +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.exacaster.lighter.rest.exceptions;

import com.exacaster.lighter.application.sessions.exceptions.InvalidSessionStateException;
import io.micronaut.context.annotation.Requires;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.server.exceptions.ExceptionHandler;
import io.micronaut.http.server.exceptions.response.ErrorContext;
import io.micronaut.http.server.exceptions.response.ErrorResponseProcessor;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import java.util.List;
import java.util.Map;

@Produces
@Singleton
@Requires(classes = InvalidSessionStateException.class)
public class InvalidSessionStateExceptionHandler implements ExceptionHandler<InvalidSessionStateException, HttpResponse<?>> {

private final ErrorResponseProcessor<?> responseProcessor;

@Inject
public InvalidSessionStateExceptionHandler(ErrorResponseProcessor<?> responseProcessor) {
this.responseProcessor = responseProcessor;
}

@Override
public HttpResponse<?> handle(HttpRequest request, InvalidSessionStateException exception) {
final ErrorContext.Builder contextBuilder = ErrorContext.builder(request).cause(exception);
MutableHttpResponse<?> response = HttpResponse.badRequest();

return responseProcessor.processResponse(contextBuilder
.error(new DetailedError(exception.getMessage(), Map.of("sessionState", exception.getSessionState())))
.build(), response);
}
}
Loading

0 comments on commit 1289a4d

Please sign in to comment.