Skip to content

Commit

Permalink
Merge pull request quarkusio#39705 from mkouba/issue-39186
Browse files Browse the repository at this point in the history
WebSockets Next: error handlers part 1
  • Loading branch information
mkouba authored Mar 27, 2024
2 parents b087d1e + bce9f55 commit 18d873e
Show file tree
Hide file tree
Showing 32 changed files with 1,578 additions and 202 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import java.util.Set;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodParameterInfo;
import org.jboss.jandex.Type;

import io.quarkus.gizmo.BytecodeCreator;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.websockets.next.OnClose;
import io.quarkus.websockets.next.OnError;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.WebSocketConnection;
import io.quarkus.websockets.next.WebSocketServerException;
Expand Down Expand Up @@ -49,10 +51,16 @@ interface ParameterContext {

/**
*
* @return the endpoint path
* @return the endpoint path or {@code null} for global error handlers
*/
String endpointPath();

/**
*
* @return the index that can be used to inspect parameter types
*/
IndexView index();

/**
*
* @return the callback marker annotation
Expand Down Expand Up @@ -88,17 +96,17 @@ interface InvocationBytecodeContext extends ParameterContext {
BytecodeCreator bytecode();

/**
* Obtains the message directly in the bytecode.
* Obtains the message or error directly in the bytecode.
*
* @return the message object or {@code null} for {@link OnOpen} and {@link OnClose} callbacks
* @return the message/error object or {@code null} for {@link OnOpen} and {@link OnClose} callbacks
*/
ResultHandle getMessage();
ResultHandle getPayload();

/**
* Attempts to obtain the decoded message directly in the bytecode.
*
* @param parameterType
* @return the decoded message object or {@code null} for {@link OnOpen} and {@link OnClose} callbacks
* @return the decoded message object or {@code null} for {@link OnOpen}, {@link OnClose} and {@link OnError} callbacks
*/
ResultHandle getDecodedMessage(Type parameterType);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package io.quarkus.websockets.next.deployment;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;

import io.quarkus.gizmo.ResultHandle;

class ErrorCallbackArgument implements CallbackArgument {

@Override
public boolean matches(ParameterContext context) {
return context.callbackAnnotation().name().equals(WebSocketDotNames.ON_ERROR)
&& isThrowable(context.index(), context.parameter().type().name());
}

@Override
public ResultHandle get(InvocationBytecodeContext context) {
return context.getPayload();
}

boolean isThrowable(IndexView index, DotName clazzName) {
if (clazzName.equals(WebSocketDotNames.THROWABLE)) {
return true;
}
ClassInfo clazz = index.getClassByName(clazzName);
if (clazz == null) {
throw new IllegalArgumentException("The class " + clazzName + " not found in the index");
}
if (clazz.superName().equals(DotName.OBJECT_NAME)
|| clazz.superName().equals(DotName.RECORD_NAME)
|| clazz.superName().equals(DotName.ENUM_NAME)) {
return false;
}
if (clazz.superName().equals(WebSocketDotNames.THROWABLE)) {
return true;
}
return isThrowable(index, clazz.superName());
}

public static boolean isError(CallbackArgument callbackArgument) {
return callbackArgument instanceof ErrorCallbackArgument;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package io.quarkus.websockets.next.deployment;

import java.util.List;

import io.quarkus.builder.item.SimpleBuildItem;
import io.quarkus.websockets.next.deployment.WebSocketServerProcessor.GlobalErrorHandler;

final class GlobalErrorHandlersBuildItem extends SimpleBuildItem {

final List<GlobalErrorHandler> handlers;

GlobalErrorHandlersBuildItem(List<GlobalErrorHandler> handlers) {
this.handlers = handlers;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ public int priotity() {
return DEFAULT_PRIORITY - 1;
}

public static boolean isMessage(CallbackArgument callbackArgument) {
return callbackArgument instanceof MessageCallbackArgument;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,21 @@ class PathParamCallbackArgument implements CallbackArgument {

@Override
public boolean matches(ParameterContext context) {
String paramName = getParamName(context);
if (paramName != null) {
String name = getParamName(context);
if (name != null) {
if (!context.parameter().type().name().equals(WebSocketDotNames.STRING)) {
throw new WebSocketServerException("Method parameter annotated with @PathParam must be java.lang.String: "
+ WebSocketServerProcessor.callbackToString(context.parameter().method()));
}
if (context.endpointPath() == null) {
throw new WebSocketServerException("Global error handlers may not accept @PathParam parameters: "
+ WebSocketServerProcessor.callbackToString(context.parameter().method()));
}
List<String> pathParams = getPathParamNames(context.endpointPath());
if (!pathParams.contains(paramName)) {
if (!pathParams.contains(name)) {
throw new WebSocketServerException(
String.format(
"@PathParam name [%s] must be used in the endpoint path [%s]: %s", paramName,
"@PathParam name [%s] must be used in the endpoint path [%s]: %s", name,
context.endpointPath(),
WebSocketServerProcessor.callbackToString(context.parameter().method())));
}
Expand All @@ -48,20 +52,20 @@ public ResultHandle get(InvocationBytecodeContext context) {
private String getParamName(ParameterContext context) {
AnnotationInstance pathParamAnnotation = Annotations.find(context.parameterAnnotations(), WebSocketDotNames.PATH_PARAM);
if (pathParamAnnotation != null) {
String paramName;
String name;
AnnotationValue nameVal = pathParamAnnotation.value();
if (nameVal != null) {
paramName = nameVal.asString();
name = nameVal.asString();
} else {
// Try to use the element name
paramName = context.parameter().name();
name = context.parameter().name();
}
if (paramName == null) {
if (name == null) {
throw new WebSocketServerException(String.format(
"Unable to extract the path parameter name - method parameter names not recorded for %s: compile the class with -parameters",
context.parameter().method().declaringClass().name()));
}
return paramName;
return name;
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import io.quarkus.websockets.next.OnBinaryMessage;
import io.quarkus.websockets.next.OnClose;
import io.quarkus.websockets.next.OnError;
import io.quarkus.websockets.next.OnOpen;
import io.quarkus.websockets.next.OnPongMessage;
import io.quarkus.websockets.next.OnTextMessage;
Expand All @@ -27,6 +28,7 @@ final class WebSocketDotNames {
static final DotName ON_BINARY_MESSAGE = DotName.createSimple(OnBinaryMessage.class);
static final DotName ON_PONG_MESSAGE = DotName.createSimple(OnPongMessage.class);
static final DotName ON_CLOSE = DotName.createSimple(OnClose.class);
static final DotName ON_ERROR = DotName.createSimple(OnError.class);
static final DotName UNI = DotName.createSimple(Uni.class);
static final DotName MULTI = DotName.createSimple(Multi.class);
static final DotName RUN_ON_VIRTUAL_THREAD = DotName.createSimple(RunOnVirtualThread.class);
Expand All @@ -38,4 +40,5 @@ final class WebSocketDotNames {
static final DotName VOID = DotName.createSimple(Void.class);
static final DotName PATH_PARAM = DotName.createSimple(PathParam.class);
static final DotName HANDSHAKE_REQUEST = DotName.createSimple(WebSocketConnection.HandshakeRequest.class);
static final DotName THROWABLE = DotName.createSimple(Throwable.class);
}
Loading

0 comments on commit 18d873e

Please sign in to comment.