Skip to content

Commit

Permalink
Merge branch 'master' into group
Browse files Browse the repository at this point in the history
  • Loading branch information
rbygrave authored Dec 6, 2024
2 parents 195b23f + cd2363c commit 1a1b48c
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 36 deletions.
13 changes: 0 additions & 13 deletions avaje-jex/src/main/java/io/avaje/jex/DJex.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,13 @@ final class DJex implements Jex {

private final Routing routing = new DefaultRouting();
private final AppLifecycle lifecycle = new DefaultLifecycle();
private final Map<Class<?>, Object> attributes = new HashMap<>();
private final DJexConfig config = new DJexConfig();

@Override
public DJexConfig config() {
return config;
}

@Override
public <T> Jex attribute(Class<T> cls, T instance) {
attributes.put(cls, instance);
return this;
}

@Override
@SuppressWarnings("unchecked")
public <T> T attribute(Class<T> cls) {
return (T) attributes.get(cls);
}

@Override
public Jex routing(Routing.HttpService routes) {
routing.add(routes);
Expand Down
18 changes: 0 additions & 18 deletions avaje-jex/src/main/java/io/avaje/jex/Jex.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,6 @@ static Jex create() {
return new DJex();
}

/**
* Sets a custom attribute that can be accessed later by the Jex instance or its components.
*
* @param <T> The type of the attribute.
* @param cls The class of the attribute.
* @param instance The instance of the attribute.
*/
<T> Jex attribute(Class<T> cls, T instance);

/**
* Returns a custom attribute previously set using {@link #attribute(Class, Object)}.
*
* @param <T> The type of the attribute.
* @param cls The class of the attribute.
* @return The attribute instance, or null if not found.
*/
<T> T attribute(Class<T> cls);

/**
* Adds a new HTTP route and its associated handler to the Jex routing configuration.
*
Expand Down
12 changes: 11 additions & 1 deletion avaje-jex/src/main/java/io/avaje/jex/Routing.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,17 @@ public sealed interface Routing permits DefaultRouting {
*/
<T extends Exception> Routing error(Class<T> exceptionClass, ExceptionHandler<T> handler);

/** Add a group of route handlers with a common path prefix. */
/**
* Add a group of route handlers with a common path prefix.
*
* <pre>{@code
* routing.path("api", () -> {
* routing.get("/", ctx -> ctx.text("apiRoot"));
* routing.get("{id}", ctx -> ctx.text("api-" + ctx.pathParam("id")));
* });
*
* }</pre>
*/
Routing path(String path, Group group);

/** Add a HEAD handler. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,11 @@ private void unhandledException(JdkContext ctx, Exception e) {

private void defaultHandling(JdkContext ctx, HttpResponseException exception) {
ctx.status(exception.getStatus());
var jsonResponse = exception.jsonResponse();
if (exception.getStatus() == ErrorCode.REDIRECT.status()) {
ctx.performRedirect();
} else if (jsonResponse != null) {
ctx.json(jsonResponse);
} else if (useJson(ctx)) {
ctx.contentType(APPLICATION_JSON).write(asJsonContent(exception));
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,12 @@ JsonService initJsonService() {
if (jsonService != null) {
return jsonService;
}
return CoreServiceLoader.jsonService().orElseGet(this::defaultJsonService);

var json = CoreServiceLoader.jsonService().orElseGet(this::defaultJsonService);

if (json == null) log.log(Level.WARNING, "No Json library configured");

return json;
}

/** Create a reasonable default JsonService if Jackson or avaje-jsonb are present. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package io.avaje.jex.http;

/** Thrown when unable to find a route/resource */
/** Thrown when request is invalid */
public class BadRequestException extends HttpResponseException {

public BadRequestException(String message) {
super(400, message);
}

public BadRequestException(Object jsonResponse) {
super(400, jsonResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,38 @@

/**
* Throwing an uncaught {@code HttpResponseException} will interrupt http processing and set the
* status code and response body with the given message
* status code and response body with the given message or json body
*/
public class HttpResponseException extends RuntimeException {

private final int status;
private final Object jsonResponse;

/**
* @param status the http status to send
* @param message the exception message that will be sent back in the response
*/
public HttpResponseException(int status, String message) {
super(message);
this.status = status;
this.jsonResponse = null;
}

/**
* @param status the http status to send
* @param jsonResponse the response body that will be sent back as json
*/
public HttpResponseException(int status, Object jsonResponse) {

this.status = status;
this.jsonResponse = jsonResponse;
}

public int getStatus() {
return status;
}

public Object jsonResponse() {
return jsonResponse;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package io.avaje.jex.http;

/** Thrown when unable to find a route/resource */
/** Thrown when server has an internal error */
public class InternalServerErrorException extends HttpResponseException {

public InternalServerErrorException(String message) {
super(500, message);
}

public InternalServerErrorException(Object jsonResponse) {
super(500, jsonResponse);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ public class NotFoundException extends HttpResponseException {
public NotFoundException(String message) {
super(404, message);
}

public NotFoundException(Object jsonResponse) {
super(404, jsonResponse);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.avaje.jex.core;

import io.avaje.jex.Jex;
import io.avaje.jex.http.BadRequestException;
import io.avaje.jex.http.ErrorCode;
import io.avaje.jex.http.HttpResponseException;
import io.avaje.jsonb.JsonException;
Expand All @@ -9,6 +10,7 @@
import org.junit.jupiter.api.Test;

import java.net.http.HttpResponse;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

Expand All @@ -34,6 +36,9 @@ static TestPair init() {
.put("/nested", ctx -> {
throw new JsonException("hmm");
})
.patch("/patch", ctx -> {
throw new BadRequestException(Map.of("error","bad request"));
})
.error(NullPointerException.class, (ctx, exception) -> ctx.text("npe"))
.error(IllegalStateException.class, (ctx, exception) -> ctx.status(222).text("Handled IllegalStateException|" + exception.getMessage()))
.error(JsonException.class, (ctx, exception) -> {throw new IllegalStateException();}));
Expand All @@ -60,6 +65,14 @@ void post() {
assertThat(res.body()).isEqualTo("Handled IllegalStateException|foo");
}

@Test
void patch() {
HttpResponse<String> res = pair.request().path("patch").PATCH().asString();
assertThat(res.statusCode()).isEqualTo(400);
assertThat(res.body()).isEqualTo("{\"error\":\"bad request\"}");
assertThat(res.headers().firstValue("Content-Type").get()).contains("application/json");
}

@Test
void expect_fallback_to_fallback() {
HttpResponse<String> res = pair.request().path("nested").PUT().asString();
Expand Down

0 comments on commit 1a1b48c

Please sign in to comment.