Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On error validation #289

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,18 @@ public class WebSocketConstants {
public static final String CURLY_BRACE_END = "}";

public static final String DIAGNOSTIC_PATH_PARAMS_ANNOT_MISSING = "Parameters of type String, any Java primitive type, or boxed version thereof must be annotated with @PathParams.";
public static final String DIAGNOSTIC_CODE_PATH_PARMS_ANNOT = "AddPathParamsAnnotation";

public static final String DIAGNOSTIC_MAND_PARAMS_MISSING = "Mandatory parameters missing. %s requires to include mandatory parameters of type: \n- %s\n";
public static final String DIAGNOSTIC_DUP_PARAMS_TYPES = "Duplicate parameter types not allowed. %s requires to include only one parameter of type: \n- %s\n";

/* Diagnostic codes */
public static final String DIAGNOSTIC_CODE_PATH_PARMS_ANNOT = "AddPathParamsAnnotation";
public static final String DIAGNOSTIC_CODE_ON_OPEN_INVALID_PARAMS = "OnOpenChangeInvalidParam";
public static final String DIAGNOSTIC_CODE_ON_CLOSE_INVALID_PARAMS = "OnCloseChangeInvalidParam";
public static final String DIAGNOSTIC_CODE_ON_ERROR_INVALID_PARAMS = "OnErrorChangeInvalidParam";
public static final String DIAGNOSTIC_CODE_DUP_PARAMS_TYPES = "DuplicateParamsTypes";

public static final String DIAGNOSTIC_CODE_ON_ERROR_MAND_PARAMS_MISS = "OnErrorMandatoryParamMissing";

public static final String DIAGNOSTIC_SERVER_ENDPOINT_NO_SLASH = "Server endpoint paths must start with a leading '/'.";
public static final String DIAGNOSTIC_SERVER_ENDPOINT_NOT_LEVEL1 = "Server endpoint paths must be a URI-template (level-1) or a partial URI.";
public static final String DIAGNOSTIC_SERVER_ENDPOINT_RELATIVE = "Server endpoint paths must not contain the sequences '/../', '/./' or '//'.";
Expand All @@ -68,6 +74,7 @@ public class WebSocketConstants {
/* Annotations */
public static final String ON_OPEN = "OnOpen";
public static final String ON_CLOSE = "OnClose";
public static final String ON_ERROR = "OnError";

public static final String IS_ANNOTATION = "isAnnotation";

Expand All @@ -78,8 +85,18 @@ public class WebSocketConstants {
public static final Set<String> ON_OPEN_PARAM_OPT_TYPES= new HashSet<>(Arrays.asList("jakarta.websocket.EndpointConfig", "jakarta.websocket.Session"));
public static final Set<String> RAW_ON_OPEN_PARAM_OPT_TYPES= new HashSet<>(Arrays.asList("EndpointConfig", "Session"));

// For OnClose annotation
public static final Set<String> ON_CLOSE_PARAM_OPT_TYPES = new HashSet<>(Arrays.asList("jakarta.websocket.CloseReason", "jakarta.websocket.Session"));
public static final Set<String> RAW_ON_CLOSE_PARAM_OPT_TYPES = new HashSet<>(Arrays.asList("CloseReason", "Session"));

// For OnError annotation
// optional onError parameters
public static final Set<String> ON_ERROR_PARAM_OPT_TYPES = new HashSet<>(Arrays.asList("jakarta.websocket.Session"));
public static final Set<String> RAW_ON_ERROR_PARAM_OPT_TYPES = new HashSet<>(Arrays.asList("Session"));
Comment on lines +94 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect if you pull the latest changes from main, you'll be able to use the defined strings for these hashsets.


// mandatory onError parameters
public static final Set<String> ON_ERROR_PARAM_MAND_TYPES = new HashSet<>(Arrays.asList("java.lang.Throwable"));
public static final Set<String> RAW_ON_ERROR_PARAM_MAND_TYPES = new HashSet<>(Arrays.asList("Throwable"));

public static final Set<String> RAW_WRAPPER_OBJS = new HashSet<>(Arrays.asList("String", "Boolean", "Integer", "Long", "Double", "Float"));
public static final Set<String> WRAPPER_OBJS = RAW_WRAPPER_OBJS.stream().map(raw -> "java.lang.".concat(raw)).collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package org.eclipse.lsp4jakarta.jdt.core.websocket;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -77,13 +78,16 @@ public void collectDiagnostics(ICompilationUnit unit, List<Diagnostic> diagnosti
// checks if the class uses annotation to create a WebSocket endpoint
if (checkWSEnd.get(WebSocketConstants.IS_ANNOTATION)) {
// WebSocket Invalid Parameters Diagnostic
invalidParamsCheck(type, WebSocketConstants.ON_OPEN, WebSocketConstants.ON_OPEN_PARAM_OPT_TYPES,
WebSocketConstants.RAW_ON_OPEN_PARAM_OPT_TYPES,
WebSocketConstants.DIAGNOSTIC_CODE_ON_OPEN_INVALID_PARAMS, unit, diagnostics);
invalidParamsCheck(type, WebSocketConstants.ON_CLOSE, WebSocketConstants.ON_CLOSE_PARAM_OPT_TYPES,
WebSocketConstants.RAW_ON_CLOSE_PARAM_OPT_TYPES,
WebSocketConstants.DIAGNOSTIC_CODE_ON_CLOSE_INVALID_PARAMS, unit, diagnostics);

invalidParamsCheck(type, WebSocketConstants.ON_OPEN, WebSocketConstants.ON_OPEN_PARAM_OPT_TYPES,
WebSocketConstants.RAW_ON_OPEN_PARAM_OPT_TYPES, WebSocketConstants.DIAGNOSTIC_CODE_ON_OPEN_INVALID_PARAMS,
Collections.emptySet(), Collections.emptySet(), unit, diagnostics);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be a bit more space-efficient to pass in null instead of empty sets here and then have a check in the function?

invalidParamsCheck(type, WebSocketConstants.ON_CLOSE, WebSocketConstants.ON_CLOSE_PARAM_OPT_TYPES,
WebSocketConstants.RAW_ON_CLOSE_PARAM_OPT_TYPES, WebSocketConstants.DIAGNOSTIC_CODE_ON_CLOSE_INVALID_PARAMS,
Collections.emptySet(), Collections.emptySet(), unit, diagnostics);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above for empty sets

invalidParamsCheck(type, WebSocketConstants.ON_ERROR, WebSocketConstants.ON_ERROR_PARAM_OPT_TYPES,
WebSocketConstants.RAW_ON_ERROR_PARAM_OPT_TYPES, WebSocketConstants.DIAGNOSTIC_CODE_ON_ERROR_INVALID_PARAMS,
WebSocketConstants.ON_ERROR_PARAM_MAND_TYPES, WebSocketConstants.RAW_ON_ERROR_PARAM_MAND_TYPES, unit, diagnostics);

// PathParam URI Mismatch Warning Diagnostic
uriMismatchWarningCheck(type, diagnostics, unit);
// ServerEndpoint annotation diagnostics
Expand All @@ -95,9 +99,8 @@ public void collectDiagnostics(ICompilationUnit unit, List<Diagnostic> diagnosti
}
}

private void invalidParamsCheck(IType type, String methodAnnotTarget, Set<String> specialParamTypes,
Set<String> rawSpecialParamTypes, String diagnosticCode, ICompilationUnit unit,
List<Diagnostic> diagnostics) throws JavaModelException {
private void invalidParamsCheck(IType type, String methodAnnotTarget, Set<String> specialParamTypes, Set<String> rawSpecialParamTypes, String diagnosticCode,
Set<String> mandParamTypes, Set<String> rawMandParamTypes, ICompilationUnit unit, List<Diagnostic> diagnostics) throws JavaModelException {

IMethod[] allMethods = type.getMethods();

Expand All @@ -107,6 +110,9 @@ private void invalidParamsCheck(IType type, String methodAnnotTarget, Set<String
for (IAnnotation annotation : allAnnotations) {
if (annotation.getElementName().equals(methodAnnotTarget)) {
ILocalVariable[] allParams = method.getParameters();
HashMap<String, ILocalVariable> mandTypeCounter = initializeHashMap(mandParamTypes);
HashMap<String, ILocalVariable> rawMandTypeCounter = initializeHashMap(rawMandParamTypes);
Boolean isResolvedType = false;

for (ILocalVariable param : allParams) {
String signature = param.getTypeSignature();
Expand All @@ -116,21 +122,56 @@ private void invalidParamsCheck(IType type, String methodAnnotTarget, Set<String
boolean isPrimitive = JavaModelUtil.isPrimitive(formatSignature);
boolean isSpecialType;
boolean isPrimWrapped;
boolean isMandParam;

// general paramType
String genParamType;
Set<String> genSpecialParamTypes;
Set<String> genMandParamTypes;
HashMap<String, ILocalVariable> genMandTypeCounter;

if (resolvedTypeName != null) {
isSpecialType = specialParamTypes.contains(resolvedTypeName);
isPrimWrapped = isWrapper(resolvedTypeName);
genParamType = resolvedTypeName;
genSpecialParamTypes = specialParamTypes;
genMandParamTypes = mandParamTypes;
isResolvedType = true;
genMandTypeCounter = mandTypeCounter;
} else {
String simpleParamType = Signature.getSignatureSimpleName(signature);
isSpecialType = rawSpecialParamTypes.contains(simpleParamType);
isPrimWrapped = isWrapper(simpleParamType);
genParamType = simpleParamType;
genSpecialParamTypes = rawSpecialParamTypes;
genMandParamTypes = rawMandParamTypes;
genMandTypeCounter = rawMandTypeCounter;
}

isSpecialType = genSpecialParamTypes.contains(genParamType);
isPrimWrapped = isWrapper(genParamType);
isMandParam = genMandParamTypes.contains(genParamType);

if (isMandParam) {
if (genMandTypeCounter.get(genParamType) != null) {
String diagMsg = createParamTypeDiagMsg(WebSocketConstants.DIAGNOSTIC_DUP_PARAMS_TYPES,
methodAnnotTarget, specialParamTypes, mandParamTypes);
Diagnostic diagnostic = createDiagnostic(param, unit,
diagMsg, WebSocketConstants.DIAGNOSTIC_CODE_DUP_PARAMS_TYPES);
diagnostics.add(diagnostic);

Diagnostic diagnostic2 = createDiagnostic(genMandTypeCounter.get(genParamType), unit,
diagMsg, WebSocketConstants.DIAGNOSTIC_CODE_DUP_PARAMS_TYPES);
diagnostics.add(diagnostic2);
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redundant continue

} else {
genMandTypeCounter.put(genParamType, param);
}
continue;
}

// check parameters valid types
if (!(isSpecialType || isPrimWrapped || isPrimitive)) {
String diagMessage = createParamTypeDiagMsg(WebSocketConstants.PARAM_TYPE_DIAG_MSG,
methodAnnotTarget, specialParamTypes, mandParamTypes);
Diagnostic diagnostic = createDiagnostic(param, unit,
createParamTypeDiagMsg(specialParamTypes, methodAnnotTarget),
diagnosticCode);
diagMessage, diagnosticCode);
diagnostics.add(diagnostic);
continue;
}
Expand All @@ -149,11 +190,38 @@ private void invalidParamsCheck(IType type, String methodAnnotTarget, Set<String
}
}
}

HashMap<String, ILocalVariable> genMandTypeCounter = isResolvedType ? mandTypeCounter : rawMandTypeCounter;

// check that all mandatory parameters are present
for (HashMap.Entry<String, ILocalVariable> entry : genMandTypeCounter.entrySet()) {
if (entry.getValue() == null) {
String diagMessage = createParamTypeDiagMsg(WebSocketConstants.DIAGNOSTIC_MAND_PARAMS_MISSING,
methodAnnotTarget, Collections.emptySet(), mandParamTypes);
Diagnostic diagnostic = createDiagnostic(method, unit,
diagMessage, WebSocketConstants.DIAGNOSTIC_CODE_ON_ERROR_MAND_PARAMS_MISS);
diagnostics.add(diagnostic);
}
}
}
}
}
}


/**
* Creates a hashmap with the given set of keys and initializes the values to null.
* @param types set of parameter types
* @return hashmap with keys initialized to types
*/
private HashMap<String, ILocalVariable> initializeHashMap(Set<String> types) {
HashMap<String, ILocalVariable> numTypes = new HashMap<>();
for (String type : types) {
numTypes.put(type, null);
}
return numTypes;
}

/**
* Creates a warning diagnostic if a PathParam annotation does not match any
* variable parameters of the WebSocket EndPoint URI associated with the class
Expand Down Expand Up @@ -295,7 +363,7 @@ private List<String> findAndProcessEndpointURI(IType type) throws JavaModelExcep
}

/**
* Check if valueClass is a wrapper object for a primitive value Based on
* Check if valueClass is a wrapper object for a primitive value. Based on
* https://github.com/eclipse/lsp4mp/blob/9789a1a996811fade43029605c014c7825e8f1da/microprofile.jdt/org.eclipse.lsp4mp.jdt.core/src/main/java/org/eclipse/lsp4mp/jdt/core/utils/JDTTypeUtils.java#L294-L298
*
* @param valueClass the resolved type of valueClass in string or the simple
Expand Down Expand Up @@ -348,9 +416,21 @@ private HashMap<String, Boolean> isWSEndpoint(IType type) throws JavaModelExcept
return wsEndpoint;
}

private String createParamTypeDiagMsg(Set<String> methodParamOptTypes, String methodAnnotTarget) {
/**
* Creates a diagnotic message for parameter types that are not supported by
* @param initialMsg the initial message to be displayed
* @param methodAnnotTarget the annotation target of the method
* @param methodParamOptTypes the optional parameter types of the method
* @param mandParamTypes the mandatory parameter types of the method
* @return the final diagnostic message
*/
private String createParamTypeDiagMsg(String initialMsg, String methodAnnotTarget, Set<String> methodParamOptTypes, Set<String> mandParamTypes) {
String paramMessage = String.join("\n- ", methodParamOptTypes);
return String.format(WebSocketConstants.PARAM_TYPE_DIAG_MSG, "@" + methodAnnotTarget, paramMessage);

if (mandParamTypes.size() > 0) {
paramMessage += (methodParamOptTypes.size() > 0 ? "\n- " : "") + String.join("\n- ", mandParamTypes);
}
return String.format(initialMsg, "@" + methodAnnotTarget, paramMessage);
}

/**
Expand Down Expand Up @@ -384,3 +464,4 @@ private boolean hasDuplicateURIVariables(String uriString) {
return false;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.io.IOException;

import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.OnOpen;
import jakarta.websocket.server.ServerEndpoint;
import jakarta.websocket.Session;
Expand All @@ -24,4 +26,10 @@ public void OnOpen(Session session, String missingAnnotation) throws IOException
public void OnClose(Session session, Integer missingAnnotation1, String missingAnnotation2) {
System.out.println("Websocket opened: " + session.getId().toString());
}

// Used to check
@OnError
public void OnError(Session session, String missingAnnotation, Throwable throwable) {
System.out.println("Websocket opened: " + session.getId().toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.openliberty.sample.jakarta.websocket;

import java.lang.Throwable;

import jakarta.websocket.OnError;
import jakarta.websocket.server.ServerEndpoint;

/**
* Expected Diagnostics are related to validating that the parameters for the onError
* WebSocket method are not duplicate (diagnostic code: OnErrorChangeInvalidParam).
* See issue #249 (OnError)
*/
@ServerEndpoint(value = "/infos")
public class DuplicateParamCheck {
@OnError
public void OnError(Session session, Throwable error1, Throwable error2) throws IOException {
System.out.println("WebSocket closed for " + session.getId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import jakarta.websocket.CloseReason;
import jakarta.websocket.OnOpen;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.server.ServerEndpoint;

/**
Expand All @@ -24,4 +25,9 @@ public void OnOpen(Session session, Object invalidParam) throws IOException {
public void OnClose(Session session, CloseReason closeReason, Object invalidParam) throws IOException {
System.out.println("WebSocket closed for " + session.getId());
}

@OnError
public void OnError(Session session, Object invalidParam, Throwable throwable) throws IOException {
System.out.println("WebSocket closed for " + session.getId());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.openliberty.sample.jakarta.websocket;

import jakarta.websocket.OnError;
import jakarta.websocket.server.ServerEndpoint;

/**
* Expected Diagnostics are related to validating that the parameters for the
* OnError WebSocket methods are not duplicate values
* (diagnostic code: OnErrorMandatoryParamMissing). See issues #249 (OnError)
*/
@ServerEndpoint(value = "/infos")
public class MandatoryParamTypes {
@OnError
public void OnError(Session session) throws IOException {
System.out.println("WebSocket closed for " + session.getId());
}
}
Loading