Skip to content

Commit

Permalink
Fix Incorrect Compilation Error when Port is Passed via a Variable wi…
Browse files Browse the repository at this point in the history
…th SSL Settings (#1850)

* [Automated] Update the native jar versions

* [Automated] Update the native jar versions

* Fix incorrect compilation error with SSL and port as a variable

* Apply suggestions from code review
  • Loading branch information
ThisaruGuruge committed Apr 29, 2024
1 parent 2403033 commit a3e5a83
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 38 deletions.
2 changes: 1 addition & 1 deletion ballerina-tests/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[ballerina]
dependencies-toml-version = "2"
distribution-version = "2201.9.0-20240410-095500-2653a74d"
distribution-version = "2201.9.0-20240425-195200-d5ce8c72"

[[package]]
org = "ballerina"
Expand Down
2 changes: 1 addition & 1 deletion ballerina/Dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[ballerina]
dependencies-toml-version = "2"
distribution-version = "2201.9.0-20240410-095500-2653a74d"
distribution-version = "2201.9.0-20240425-195200-d5ce8c72"

[[package]]
org = "ballerina"
Expand Down
11 changes: 3 additions & 8 deletions ballerina/listener.bal
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,9 @@ public class Listener {
}
self.wsListener = ();
self.graphiql = {};
int port = self.httpListener.getPort();
if configuration.secureSocket is () {
self.httpEndpoint = string `http://${LOCALHOST}:${port}`;
self.websocketEndpoint = string `ws://${LOCALHOST}:${port}`;
} else {
self.httpEndpoint = string `https://${LOCALHOST}:${port}`;
self.websocketEndpoint = string `wss://${LOCALHOST}:${port}`;
}
[string, string][httpEndpoint, websocketEndpoint] = getEndpoints(listenTo, configuration);
self.httpEndpoint = httpEndpoint;
self.websocketEndpoint = websocketEndpoint;
}

# Attaches the provided service to the Listener.
Expand Down
59 changes: 38 additions & 21 deletions ballerina/listener_utils.bal
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@
// specific language governing permissions and limitations
// under the License.

import graphql.parser;

import ballerina/http;
import ballerina/io;
import ballerina/jballerina.java;
import ballerina/mime;
import ballerina/websocket;

import graphql.parser;

isolated function handleGetRequests(Engine engine, Context context, http:Request request) returns http:Response {
string? query = request.getQueryParamValue(PARAM_QUERY);
if query is string && query != "" {
Expand Down Expand Up @@ -51,7 +51,7 @@ isolated function handlePostRequests(Engine engine, Context context, http:Reques
}

isolated function getResponseFromJsonPayload(Engine engine, Context context, http:Request request,
map<Upload|Upload[]> fileInfo = {}) returns http:Response {
map<Upload|Upload[]> fileInfo = {}) returns http:Response {
json|http:ClientError payload = request.getJsonPayload();
if payload is http:ClientError {
return createResponse("Invalid request body", http:STATUS_BAD_REQUEST);
Expand All @@ -69,7 +69,7 @@ isolated function getResponseFromJsonPayload(Engine engine, Context context, htt
}

isolated function getResponseFromQuery(Engine engine, string document, string? operationName, map<json>? variables,
Context context, map<Upload|Upload[]> fileInfo = {}) returns http:Response {
Context context, map<Upload|Upload[]> fileInfo = {}) returns http:Response {
parser:OperationNode|OutputObject validationResult = engine.validate(document, operationName, variables);
if validationResult is parser:OperationNode {
context.setFileInfo(fileInfo);
Expand Down Expand Up @@ -132,7 +132,7 @@ isolated function getResponseFromMultipartPayload(Engine engine, Context context
pathMap = paths;
} else {
return createResponse("Invalid type for multipart request field ‘map’",
http:STATUS_BAD_REQUEST);
http:STATUS_BAD_REQUEST);
}
} else {
return createResponse(paths.message(), http:STATUS_BAD_REQUEST);
Expand Down Expand Up @@ -197,7 +197,7 @@ isolated function getUploadValues(map<Upload> fileInfo, map<json> pathMap, map<j
}

isolated function validateRequestPathArray(map<Upload> fileInfo, map<json> variables, map<Upload> files,
json[] paths, string key) returns http:Response? {
json[] paths, string key) returns http:Response? {
if paths.length() == 0 {
return createResponse("Missing file path in multipart request ‘map’", http:STATUS_BAD_REQUEST);
}
Expand All @@ -208,7 +208,7 @@ isolated function validateRequestPathArray(map<Upload> fileInfo, map<json> varia
if path is string {
http:Response|string validateVariablePathResult = validateVariablePath(variables, path);
if validateVariablePathResult is string {
files[validateVariablePathResult] = <Upload> fileInfo[key];
files[validateVariablePathResult] = <Upload>fileInfo[key];
continue;
}
return validateVariablePathResult;
Expand All @@ -224,11 +224,11 @@ isolated function validateVariablePath(map<json> variables, string receivedPath)
if path.includes(PARAM_VARIABLES) && path.includes(".") {
path = path.substring((<int>path.indexOf(".")) + 1);
if path.includes(".") {
string varName = path.substring(0, <int> path.indexOf("."));
string indexPart = path.substring((<int> path.indexOf(".") + 1));
string varName = path.substring(0, <int>path.indexOf("."));
string indexPart = path.substring((<int>path.indexOf(".") + 1));
int|error index = 'int:fromString(indexPart);
if variables.hasKey(varName) && variables.get(varName) is json[] && index is int {
json[] arrayValue = <json[]> variables.get(varName);
json[] arrayValue = <json[]>variables.get(varName);
if index < arrayValue.length() {
if arrayValue[index] == null {
arrayValue[index] = path;
Expand Down Expand Up @@ -258,7 +258,7 @@ isolated function createUploadInfoMap(map<Upload> files, map<json> variables)
Upload[] fileArray = [];
foreach json filePath in value {
if filePath is string {
fileArray.push(<Upload> files[filePath]);
fileArray.push(<Upload>files[filePath]);
} else {
return createResponse("File content is missing in multipart request", http:STATUS_BAD_REQUEST);
}
Expand All @@ -272,7 +272,7 @@ isolated function createUploadInfoMap(map<Upload> files, map<json> variables)
}

isolated function forwardMultipartRequestToExecution(map<Upload|Upload[]> fileInfo, Engine engine,
Context context, json payload) returns http:Response {
Context context, json payload) returns http:Response {
if payload != () {
http:Request request = new;
request.setJsonPayload(payload);
Expand All @@ -298,7 +298,7 @@ isolated function getHttpService(Engine gqlEngine, GraphqlServiceConfig? service
private final ContextInit contextInit = contextInitFunction;

isolated resource function get .(http:RequestContext requestContext,
http:Request request) returns http:Response {
http:Request request) returns http:Response {
Context|http:Response context = self.initContext(requestContext, request);
if context is http:Response {
return context;
Expand All @@ -314,7 +314,7 @@ isolated function getHttpService(Engine gqlEngine, GraphqlServiceConfig? service
}

isolated resource function post .(http:RequestContext requestContext,
http:Request request) returns http:Response {
http:Request request) returns http:Response {
Context|http:Response context = self.initContext(requestContext, request);
if context is http:Response {
return context;
Expand All @@ -330,7 +330,7 @@ isolated function getHttpService(Engine gqlEngine, GraphqlServiceConfig? service
}

isolated function initContext(http:RequestContext requestContext,
http:Request request) returns Context|http:Response {
http:Request request) returns Context|http:Response {
Context|error context = self.contextInit(requestContext, request);
if context is error {
json payload = {errors: [{message: context.message()}]};
Expand All @@ -352,12 +352,12 @@ isolated function getHttpService(Engine gqlEngine, GraphqlServiceConfig? service
}

isolated function getWebsocketService(Engine gqlEngine, readonly & __Schema schema,
GraphqlServiceConfig? serviceConfig) returns UpgradeService {
GraphqlServiceConfig? serviceConfig) returns UpgradeService {
final ContextInit contextInitFunction = getContextInit(serviceConfig);
UpgradeService websocketUpgradeService = @websocket:ServiceConfig {
subProtocols: [GRAPHQL_TRANSPORT_WS]
} isolated service object {
isolated resource function get .(http:Request request)
isolated resource function get .(http:Request request)
returns websocket:Service|websocket:UpgradeError|http:HeaderNotFoundError {
_ = check request.getHeader(WS_SUB_PROTOCOL);
Context context = check initContext(gqlEngine, contextInitFunction, request);
Expand All @@ -383,9 +383,8 @@ isolated function initContext(Engine engine, ContextInit contextInit, http:Reque
}
}


isolated function getGraphiqlService(GraphqlServiceConfig? serviceConfig, string graphqlUrl,
string? subscriptionUrl = ()) returns HttpService {
string? subscriptionUrl = ()) returns HttpService {
final readonly & ListenerAuthConfig[]? authConfigurations = getListenerAuthConfig(serviceConfig).cloneReadOnly();
final CorsConfig corsConfig = getCorsConfig(serviceConfig);

Expand All @@ -396,8 +395,8 @@ isolated function getGraphiqlService(GraphqlServiceConfig? serviceConfig, string

isolated resource function get .() returns http:Response|http:InternalServerError {
string|error htmlAsString = subscriptionUrl is string
? getHtmlContentFromResources(graphqlUrl, subscriptionUrl)
: getHtmlContentFromResources(graphqlUrl);
? getHtmlContentFromResources(graphqlUrl, subscriptionUrl)
: getHtmlContentFromResources(graphqlUrl);
if htmlAsString is error {
return {
body: htmlAsString.message()
Expand All @@ -411,6 +410,24 @@ isolated function getGraphiqlService(GraphqlServiceConfig? serviceConfig, string
return graphiqlService;
}

isolated function getEndpoints(int|http:Listener listenTo, *ListenerConfiguration configuration)
returns [string, string] {
ListenerSecureSocket? secureSocket = ();
int port;
if listenTo is int {
port = listenTo;
secureSocket = configuration.secureSocket;
} else {
port = listenTo.getPort();
secureSocket = listenTo.getConfig().secureSocket;
}

if secureSocket is () {
return [string `http://${LOCALHOST}:${port}`, string `ws://${LOCALHOST}:${port}`];
}
return [string `https://${LOCALHOST}:${port}`, string `wss://${LOCALHOST}:${port}`];
}

isolated function attachHttpServiceToGraphqlService(Service s, HttpService httpService) = @java:Method {
'class: "io.ballerina.stdlib.graphql.runtime.engine.ListenerUtils"
} external;
Expand Down
35 changes: 33 additions & 2 deletions ballerina/tests/06_configurations_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
// specific language governing permissions and limitations
// under the License.

import ballerina/test;
import graphql.parser;

import ballerina/http;
import ballerina/test;

@test:Config {
groups: ["configs", "validation"],
dataProvider: dataProviderQueryDepthConfigurations
Expand Down Expand Up @@ -78,7 +80,6 @@ function testGraphiQLPath(string path) returns error? {
test:assertEquals(err.message(), "Invalid path provided for GraphiQL client");
}


function dataProviderGraphiQLPath() returns (string[][]) {
return [
["/ballerina graphql"],
Expand All @@ -95,3 +96,33 @@ function testInvalidMaxQueryDepth() returns error? {
Error err = <Error>engine;
test:assertEquals(err.message(), "Max query depth value must be a positive integer");
}

@test:Config {
groups: ["listener", "configs", "graphiql"],
dataProvider: dataProviderGraphiqlPathLog
}
isolated function testGraphiqlPathLog(int|http:Listener listenTo, ListenerConfiguration config, string httpPath,
string websocketPath) returns error? {
[string, string] [expectedHttpPath, expectedWebsocketPath] = getEndpoints(listenTo, config);
test:assertEquals(expectedHttpPath, httpPath);
test:assertEquals(expectedWebsocketPath, websocketPath);
}

ListenerSecureSocket secureSocket = {
key: {
path: "./tests/resources/certs/ballerinaKeystore.p12",
password: "ballerina"
}
};

listener http:Listener testListener1 = new (9090);
listener http:Listener testListener2 = new (9091, {secureSocket});

function dataProviderGraphiqlPathLog() returns map<[(int|http:Listener), ListenerConfiguration, string, string]>|error {
return {
"1": [testListener1, {}, "http://localhost:9090", "ws://localhost:9090"],
"2": [testListener2, {}, "https://localhost:9091", "wss://localhost:9091"],
"3": [9092, {}, "http://localhost:9092", "ws://localhost:9092"],
"4": [9093, {secureSocket}, "https://localhost:9093", "wss://localhost:9093"]
};
}
Binary file not shown.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- [[#4859] Fix Service Crashing when Intersection Types are Used as Input Objects](https://github.com/ballerina-platform/ballerina-library/issues/4859)
- [[#6418] Fix Code Coverage Showing Invalid Entries with GraphQL](https://github.com/ballerina-platform/ballerina-library/issues/6418)
- [[#6310] Fix Incorrect Error Message in Compiler Plugin](https://github.com/ballerina-platform/ballerina-library/issues/6310)
- [[#6459] Fix Incorrect Compilation Error when Port is Passed via a Variable with SSL Settings](https://github.com/ballerina-platform/ballerina-library/issues/6459)

## [1.11.0] - 2023-02-21

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,22 @@ listener graphql:Listener listener4 = new graphql:Listener(9000);
listener graphql:Listener listener5 = check new graphql:Listener(9000);

graphql:ListenerConfiguration configs = {
timeout: 1.0
timeout: 1.0
};

listener graphql:Listener listener6 = new(9000, configs);
listener graphql:Listener listener7 = check new(9000, configs);
listener graphql:Listener listener8 = new graphql:Listener(9000, configs);
listener graphql:Listener listener9 = check new graphql:Listener(9000, configs);

configurable int port = 9090;
listener graphql:Listener listener10 = new(port,
secureSocket = {
certFile: "sample/cert/file",
keyFile: "sample/key/file"
}
);

service graphql:Service on new graphql:Listener(4000) {
resource function get name() returns string {
return "John";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
package io.ballerina.stdlib.graphql.compiler.service.validator;

import io.ballerina.compiler.api.symbols.Symbol;
import io.ballerina.compiler.api.symbols.SymbolKind;
import io.ballerina.compiler.api.symbols.TypeDescKind;
import io.ballerina.compiler.api.symbols.VariableSymbol;
import io.ballerina.compiler.syntax.tree.ExplicitNewExpressionNode;
import io.ballerina.compiler.syntax.tree.FunctionArgumentNode;
import io.ballerina.compiler.syntax.tree.ImplicitNewExpressionNode;
Expand Down Expand Up @@ -100,11 +103,18 @@ private void validateListenerArguments(SyntaxNodeAnalysisContext context,
// two args are valid only if the first arg is numeric (i.e, port and config)
if (arguments.size() > 1) {
PositionalArgumentNode firstArg = (PositionalArgumentNode) arguments.get(0);
FunctionArgumentNode secondArg = arguments.get(1);
SyntaxKind firstArgSyntaxKind = firstArg.expression().kind();
if (firstArgSyntaxKind != SyntaxKind.NUMERIC_LITERAL) {
updateContext(context, CompilationDiagnostic.INVALID_LISTENER_INIT, secondArg.location());
if (context.semanticModel().symbol(firstArg.expression()).isEmpty()) {
return;
}
Symbol firstArgSymbol = context.semanticModel().symbol(firstArg.expression()).get();
if (firstArgSymbol.kind() == SymbolKind.VARIABLE) {
VariableSymbol variableSymbol = (VariableSymbol) firstArgSymbol;
if (variableSymbol.typeDescriptor().typeKind() == TypeDescKind.INT) {
return;
}
}
FunctionArgumentNode secondArg = arguments.get(1);
updateContext(context, CompilationDiagnostic.INVALID_LISTENER_INIT, secondArg.location());
}
}
}

0 comments on commit a3e5a83

Please sign in to comment.