diff --git a/ballerina/http_annotation.bal b/ballerina/http_annotation.bal index e5fabbc3f9..25c6118f72 100644 --- a/ballerina/http_annotation.bal +++ b/ballerina/http_annotation.bal @@ -24,8 +24,9 @@ # + mediaTypeSubtypePrefix - Service specific media-type subtype prefix # + treatNilableAsOptional - Treat Nilable parameters as optional # + openApiDefinition - The generated OpenAPI definition for the HTTP service. This is auto-generated at compile-time if OpenAPI doc auto generation is enabled -# + validation - Enables the inbound payload validation functionalty which provided by the constraint package. Enabled by default -# + serviceType - The service object type which defines the service contract +# + validation - Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default +# + serviceType - The service object type which defines the service contract. This is auto-generated at compile-time +# + basePath - Base path to be used with the service implementation. This is only allowed on service contract types public type HttpServiceConfig record {| string host = "b7a.default"; CompressionConfig compression = {}; @@ -37,6 +38,7 @@ public type HttpServiceConfig record {| byte[] openApiDefinition = []; boolean validation = true; typedesc serviceType?; + string basePath?; |}; # Configurations for CORS support. @@ -154,12 +156,3 @@ public type HttpCacheConfig record {| # Success(2XX) `StatusCodeResponses` return types. Default annotation adds `must-revalidate,public,max-age=3600` as # `cache-control` header in addition to `etag` and `last-modified` headers. public annotation HttpCacheConfig Cache on return; - -# Service contract configuration -# + basePath - Base path for generated service contract -public type ServiceContractConfiguration record {| - string basePath; -|}; - -# Annotation for mapping service contract information to a Ballerina service type. -public const annotation ServiceContractConfiguration ServiceContractConfig on type; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/Constants.java index fc112e554d..b9ac9ba1ea 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/Constants.java @@ -26,7 +26,6 @@ private Constants() {} public static final String BALLERINA = "ballerina"; public static final String HTTP = "http"; - public static final String SERVICE_CONTRACT_CONFIG = "ServiceContractConfiguration"; public static final String SERVICE_CONTRACT_TYPE = "ServiceContract"; public static final String HTTP_SERVICE_TYPE = "Service"; public static final String SERVICE_TYPE = "serviceType"; @@ -81,9 +80,8 @@ private Constants() {} public static final String CALLER_ANNOTATION = "Caller"; public static final String CACHE_ANNOTATION = "Cache"; public static final String SERVICE_CONFIG_ANNOTATION = "ServiceConfig"; - public static final String SERVICE_CONTRACT_CONFIG_ANNOTATION = "ServiceContractConfig"; public static final String MEDIA_TYPE_SUBTYPE_PREFIX = "mediaTypeSubtypePrefix"; - public static final String INTERCEPTABLE_SERVICE = "InterceptableService"; + public static final String BASE_PATH = "basePath"; public static final String RESOURCE_CONFIG_ANNOTATION = "ResourceConfig"; public static final String PAYLOAD_ANNOTATION_TYPE = "HttpPayload"; public static final String CALLER_ANNOTATION_TYPE = "HttpCallerInfo"; diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java index 4944e51e8a..b6f829aa90 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java @@ -23,14 +23,12 @@ import io.ballerina.projects.plugins.CompilerPluginContext; import io.ballerina.projects.plugins.codeaction.CodeAction; import io.ballerina.projects.plugins.completion.CompletionProvider; -import io.ballerina.stdlib.http.compiler.codeaction.AddBasePathCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.AddHeaderParameterCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.AddInterceptorRemoteMethodCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.AddInterceptorResourceMethodCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.AddPayloadParameterCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.AddResponseCacheConfigCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.AddResponseContentTypeCodeAction; -import io.ballerina.stdlib.http.compiler.codeaction.ChangeBasePathCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.ChangeHeaderParamTypeToStringArrayCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.ChangeHeaderParamTypeToStringCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.ChangeReturnTypeWithCallerCodeAction; @@ -64,9 +62,7 @@ private List getCodeActions() { new AddResponseCacheConfigCodeAction(), new AddInterceptorResourceMethodCodeAction(), new AddInterceptorRemoteMethodCodeAction(), - new ImplementServiceContract(), - new AddBasePathCodeAction(), - new ChangeBasePathCodeAction() + new ImplementServiceContract() ); } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnosticCodes.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnosticCodes.java index 54cae8016b..93aa3db518 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnosticCodes.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpDiagnosticCodes.java @@ -109,10 +109,10 @@ public enum HttpDiagnosticCodes { ERROR), HTTP_153("HTTP_153", "'http:ServiceConfig' annotation is not allowed for service declaration implemented via the " + "'http:ServiceContract' type. The HTTP annotations are inferred from the service contract type", ERROR), - HTTP_154("HTTP_154", "base path not found for the service defined with 'http:ServiceContract' type. " + - "Expected base path is '%s'", ERROR), - HTTP_155("HTTP_155", "invalid base path found for the service defined with 'http:ServiceContract' type." + - " Expected base path is '%s', but found '%s'", ERROR), + HTTP_154("HTTP_154", "base path not allowed in the service declaration which is implemented via the " + + "'http:ServiceContract' type. The base path is inferred from the service contract type", ERROR), + HTTP_155("HTTP_155", "configuring base path in the 'http:ServiceConfig' annotation is not allowed for non service" + + " contract types", ERROR), HTTP_156("HTTP_156", "invalid service type descriptor found in 'http:ServiceConfig' annotation. " + "Expected service type: '%s' but found: '%s'", ERROR), HTTP_157("HTTP_157", "'serviceType' is not allowed in the service which is not implemented " + @@ -123,8 +123,6 @@ public enum HttpDiagnosticCodes { "'http:ServiceContract' type. The HTTP annotations are inferred from the service contract type", ERROR), HTTP_160("HTTP_160", "'%s' annotation is not allowed for resource function implemented via the " + "'http:ServiceContract' type. The HTTP annotations are inferred from the service contract type", ERROR), - HTTP_161("HTTP_161", "'http:ServiceContractConfig' annotation is only allowed for service object type " + - "including 'http:ServiceContract' type", ERROR), HTTP_HINT_101("HTTP_HINT_101", "Payload annotation can be added", INTERNAL), HTTP_HINT_102("HTTP_HINT_102", "Header annotation can be added", INTERNAL), diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceValidator.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceValidator.java index eeea0a4bbe..aa70234b98 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceValidator.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpServiceValidator.java @@ -19,7 +19,6 @@ package io.ballerina.stdlib.http.compiler; import io.ballerina.compiler.api.SemanticModel; -import io.ballerina.compiler.api.symbols.AnnotationAttachmentSymbol; import io.ballerina.compiler.api.symbols.ObjectTypeSymbol; import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol; import io.ballerina.compiler.api.symbols.Symbol; @@ -28,9 +27,7 @@ import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol; import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.api.symbols.UnionTypeSymbol; -import io.ballerina.compiler.api.values.ConstantValue; import io.ballerina.compiler.syntax.tree.AnnotationNode; -import io.ballerina.compiler.syntax.tree.BasicLiteralNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; import io.ballerina.compiler.syntax.tree.MappingFieldNode; @@ -60,6 +57,7 @@ import java.util.Set; import static io.ballerina.stdlib.http.compiler.Constants.BALLERINA; +import static io.ballerina.stdlib.http.compiler.Constants.BASE_PATH; import static io.ballerina.stdlib.http.compiler.Constants.COLON; import static io.ballerina.stdlib.http.compiler.Constants.DEFAULT; import static io.ballerina.stdlib.http.compiler.Constants.EMPTY; @@ -69,10 +67,8 @@ import static io.ballerina.stdlib.http.compiler.Constants.PLUS; import static io.ballerina.stdlib.http.compiler.Constants.REMOTE_KEYWORD; import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_CONFIG_ANNOTATION; -import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_CONTRACT_CONFIG_ANNOTATION; import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_CONTRACT_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_TYPE; -import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_CONTRACT_CONFIG; import static io.ballerina.stdlib.http.compiler.Constants.SUFFIX_SEPARATOR_REGEX; import static io.ballerina.stdlib.http.compiler.Constants.UNNECESSARY_CHARS_REGEX; import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.getCtxTypes; @@ -102,10 +98,8 @@ public void perform(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext) { Optional serviceTypeDesc = getServiceContractTypeDesc( syntaxNodeAnalysisContext.semanticModel(), serviceDeclarationNode); - if (serviceTypeDesc.isPresent() && !validateBasePathFromServiceType(syntaxNodeAnalysisContext, - serviceTypeDesc.get(), serviceDeclarationNode)) { - return; - } + serviceTypeDesc.ifPresent(typeDescriptorNode -> + checkBasePathExistence(syntaxNodeAnalysisContext, serviceDeclarationNode)); Optional metadataNodeOptional = serviceDeclarationNode.metadata(); metadataNodeOptional.ifPresent(metadataNode -> validateServiceAnnotation(syntaxNodeAnalysisContext, @@ -182,98 +176,12 @@ private static Set extractMethodsFromServiceType(TypeDescriptorNode serv return serviceObjTypeSymbol.methods().keySet(); } - private static boolean validateBasePathFromServiceType(SyntaxNodeAnalysisContext ctx, - TypeDescriptorNode serviceTypeDesc, - ServiceDeclarationNode serviceDeclarationNode) { - SemanticModel semanticModel = ctx.semanticModel(); - Optional serviceTypeSymbol = semanticModel.symbol(serviceTypeDesc); - if (serviceTypeSymbol.isEmpty() || - !(serviceTypeSymbol.get() instanceof TypeReferenceTypeSymbol serviceTypeRef)) { - return true; - } - - Symbol serviceTypeDef = serviceTypeRef.definition(); - if (Objects.isNull(serviceTypeDef) || !(serviceTypeDef instanceof TypeDefinitionSymbol serviceType)) { - return true; - } - - Optional serviceTypeInfo = serviceType.annotAttachments().stream().filter( - annotationAttachment -> isOpenServiceTypeInfoAnnotation(annotationAttachment, semanticModel) - ).findFirst(); - if (serviceTypeInfo.isEmpty() || !serviceTypeInfo.get().isConstAnnotation()) { - return true; - } - - Optional expectedBasePathOpt = getBasePathFromServiceTypeInfo(serviceTypeInfo.get()); - if (expectedBasePathOpt.isEmpty()) { - return true; - } - - String expectedBasePath = expectedBasePathOpt.get().trim(); - + private static void checkBasePathExistence(SyntaxNodeAnalysisContext ctx, + ServiceDeclarationNode serviceDeclarationNode) { NodeList nodes = serviceDeclarationNode.absoluteResourcePath(); - if (nodes.isEmpty()) { - if (!expectedBasePath.equals("/")) { - reportBasePathNotFound(ctx, expectedBasePath, serviceTypeDesc.location()); - return false; - } - return true; - } - - String actualBasePath = constructBasePathFormNodeList(nodes); - if (!actualBasePath.equals(expectedBasePath)) { - reportInvalidBasePathFound(ctx, expectedBasePath, actualBasePath, nodes); - return false; - } - return true; - } - - private static String constructBasePathFormNodeList(NodeList nodes) { - // Handle string literal values - if (nodes.size() == 1 && nodes.get(0).kind().equals(SyntaxKind.STRING_LITERAL)) { - String basicLiteralText = ((BasicLiteralNode) nodes.get(0)).literalToken().text(); - return basicLiteralText.substring(1, basicLiteralText.length() - 1); - } - - StringBuilder basePath = new StringBuilder(); - for (Node node : nodes) { - if (node.kind().equals(SyntaxKind.SLASH_TOKEN)) { - basePath.append("/"); - } else if (node.kind().equals(SyntaxKind.IDENTIFIER_TOKEN)) { - basePath.append(((Token) node).text().replaceAll("\\\\", "").replaceAll("'", "")); - } + if (!nodes.isEmpty()) { + reportBasePathNotAllowed(ctx, nodes); } - return basePath.toString(); - } - - private static boolean isOpenServiceTypeInfoAnnotation(AnnotationAttachmentSymbol annotationAttachmentSymbol, - SemanticModel semanticModel) { - Optional serviceTypeInfo = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, - SERVICE_CONTRACT_CONFIG); - Optional annotationDescType = annotationAttachmentSymbol.typeDescriptor().typeDescriptor(); - if (annotationDescType.isPresent() && serviceTypeInfo.isPresent() && - serviceTypeInfo.get() instanceof TypeDefinitionSymbol serviceTypeInfoSymbol) { - return annotationDescType.get().subtypeOf(serviceTypeInfoSymbol.typeDescriptor()); - } - return false; - } - - private static Optional getBasePathFromServiceTypeInfo(AnnotationAttachmentSymbol serviceTypeInfo) { - Optional serviceTypeInfoValue = serviceTypeInfo.attachmentValue(); - if (serviceTypeInfoValue.isEmpty()) { - return Optional.empty(); - } - Object serviceTypeInfoMapObject = serviceTypeInfoValue.get().value(); - if (serviceTypeInfoMapObject instanceof Map serviceTypeInfoMap) { - Object basePath = serviceTypeInfoMap.get("basePath"); - if (Objects.nonNull(basePath) && basePath instanceof ConstantValue basePathConstant) { - Object basePathString = basePathConstant.value(); - if (Objects.nonNull(basePathString) && basePathString instanceof String basePathStrValue) { - return Optional.of(basePathStrValue); - } - } - } - return Optional.empty(); } protected static void validateResources(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, @@ -389,15 +297,6 @@ private static boolean isListenerBelongsToHttpModule(TypeSymbol listenerType) { return false; } - public static TypeDescKind getReferencedTypeDescKind(TypeSymbol typeSymbol) { - TypeDescKind kind = typeSymbol.typeKind(); - if (kind == TypeDescKind.TYPE_REFERENCE) { - TypeSymbol typeDescriptor = ((TypeReferenceTypeSymbol) typeSymbol).typeDescriptor(); - kind = getReferencedTypeDescKind(typeDescriptor); - } - return kind; - } - private static void validateResourceLinks(SyntaxNodeAnalysisContext syntaxNodeAnalysisContext, LinksMetaData linksMetaData) { if (!linksMetaData.hasNameReferenceObjects()) { @@ -466,13 +365,8 @@ protected static void validateServiceAnnotation(SyntaxNodeAnalysisContext ctx, M serviceTypeDesc); return; } - if (annotValue.isPresent()) { - validateServiceConfigAnnotation(ctx, annotValue); - } - } - if (SERVICE_CONTRACT_CONFIG_ANNOTATION.equals(annotStrings[annotStrings.length - 1].trim()) - && HTTP.equals(annotStrings[0].trim()) && !isServiceContractType) { - reportServiceContractTypeAnnotationNotAllowedFound(ctx, annotation.location()); + annotValue.ifPresent(mappingConstructorExpressionNode -> + validateServiceConfigAnnotation(ctx, mappingConstructorExpressionNode, isServiceContractType)); } } } @@ -506,8 +400,8 @@ private static void validateAnnotationUsageForServiceContractType(SyntaxNodeAnal } protected static void validateServiceConfigAnnotation(SyntaxNodeAnalysisContext ctx, - Optional maps) { - MappingConstructorExpressionNode mapping = maps.get(); + MappingConstructorExpressionNode mapping, + boolean isServiceContractType) { for (MappingFieldNode field : mapping.fields()) { String fieldName = field.toString(); fieldName = fieldName.trim().replaceAll(UNNECESSARY_CHARS_REGEX, ""); @@ -524,6 +418,8 @@ protected static void validateServiceConfigAnnotation(SyntaxNodeAnalysisContext } } else if (SERVICE_TYPE.equals(strings[0].trim())) { reportServiceTypeNotAllowedFound(ctx, field.location()); + } else if (BASE_PATH.equals(strings[0].trim()) && !isServiceContractType) { + reportBasePathFieldNotAllowed(ctx, field.location()); } } } @@ -572,28 +468,21 @@ private static void reportInvalidServiceContractType(SyntaxNodeAnalysisContext c updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_156, expectedServiceType, actualServiceType); } - private static void reportBasePathNotFound(SyntaxNodeAnalysisContext ctx, String expectedBasePath, - Location location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_154, expectedBasePath); - } - - private static void reportInvalidBasePathFound(SyntaxNodeAnalysisContext ctx, String expectedBasePath, - String actualBasePath, NodeList nodes) { + private static void reportBasePathNotAllowed(SyntaxNodeAnalysisContext ctx, NodeList nodes) { Location startLocation = nodes.get(0).location(); Location endLocation = nodes.get(nodes.size() - 1).location(); BLangDiagnosticLocation location = new BLangDiagnosticLocation(startLocation.lineRange().fileName(), startLocation.lineRange().startLine().line(), startLocation.lineRange().endLine().line(), startLocation.lineRange().startLine().offset(), endLocation.lineRange().endLine().offset(), 0, 0); - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_155, expectedBasePath, actualBasePath); + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_154); } - private static void reportServiceTypeNotAllowedFound(SyntaxNodeAnalysisContext ctx, NodeLocation location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_157); + private static void reportBasePathFieldNotAllowed(SyntaxNodeAnalysisContext ctx, Location location) { + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_155); } - private static void reportServiceContractTypeAnnotationNotAllowedFound(SyntaxNodeAnalysisContext ctx, - NodeLocation location) { - updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_161); + private static void reportServiceTypeNotAllowedFound(SyntaxNodeAnalysisContext ctx, NodeLocation location) { + updateDiagnostic(ctx, location, HttpDiagnosticCodes.HTTP_157); } private static void enableImplementServiceContractCodeAction(SyntaxNodeAnalysisContext ctx, String serviceType, diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddBasePathCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddBasePathCodeAction.java deleted file mode 100644 index 08b23ef231..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/AddBasePathCodeAction.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package io.ballerina.stdlib.http.compiler.codeaction; - -import io.ballerina.compiler.syntax.tree.NonTerminalNode; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.projects.plugins.codeaction.CodeAction; -import io.ballerina.projects.plugins.codeaction.CodeActionArgument; -import io.ballerina.projects.plugins.codeaction.CodeActionContext; -import io.ballerina.projects.plugins.codeaction.CodeActionExecutionContext; -import io.ballerina.projects.plugins.codeaction.CodeActionInfo; -import io.ballerina.projects.plugins.codeaction.DocumentEdit; -import io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes; -import io.ballerina.tools.text.LineRange; -import io.ballerina.tools.text.TextDocument; -import io.ballerina.tools.text.TextDocumentChange; -import io.ballerina.tools.text.TextEdit; -import io.ballerina.tools.text.TextRange; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.EXPECTED_BASE_PATH; -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; - -/** - * Represents a code action to add the expected base path to the service declaration - * from the service contract type. - * - * @since 2.12.0 - */ -public class AddBasePathCodeAction implements CodeAction { - @Override - public List supportedDiagnosticCodes() { - return List.of(HttpDiagnosticCodes.HTTP_154.getCode()); - } - - @Override - public Optional codeActionInfo(CodeActionContext context) { - NonTerminalNode node = CodeActionUtil.findNode(context.currentDocument().syntaxTree(), - context.diagnostic().location().lineRange()); - String diagnosticMsg = context.diagnostic().message(); - Pattern pattern = Pattern.compile("Expected base path is (.*)"); - Matcher matcher = pattern.matcher(diagnosticMsg); - String basePath = ""; - if (matcher.find()) { - basePath = matcher.group(1); - } - CodeActionArgument basePathArg = CodeActionArgument.from(EXPECTED_BASE_PATH, basePath); - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, node.location().lineRange()); - return Optional.of(CodeActionInfo.from("Add base path from the service contract", - List.of(locationArg, basePathArg))); - } - - @Override - public List execute(CodeActionExecutionContext context) { - LineRange lineRange = null; - String basePath = ""; - for (CodeActionArgument argument : context.arguments()) { - if (NODE_LOCATION_KEY.equals(argument.key())) { - lineRange = argument.valueAs(LineRange.class); - } - if (EXPECTED_BASE_PATH.equals(argument.key())) { - basePath = argument.valueAs(String.class); - } - } - - if (lineRange == null || basePath.isEmpty()) { - return Collections.emptyList(); - } - - SyntaxTree syntaxTree = context.currentDocument().syntaxTree(); - TextDocument textDocument = syntaxTree.textDocument(); - int end = textDocument.textPositionFrom(lineRange.endLine()); - - List textEdits = new ArrayList<>(); - textEdits.add(TextEdit.from(TextRange.from(end, 0), " \"" + basePath + "\"")); - TextDocumentChange change = TextDocumentChange.from(textEdits.toArray(new TextEdit[0])); - TextDocument modifiedTextDocument = syntaxTree.textDocument().apply(change); - return Collections.singletonList(new DocumentEdit(context.fileUri(), SyntaxTree.from(modifiedTextDocument))); - } - - @Override - public String name() { - return "ADD_BASE_PATH"; - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeBasePathCodeAction.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeBasePathCodeAction.java deleted file mode 100644 index c994c7cf0b..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/codeaction/ChangeBasePathCodeAction.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package io.ballerina.stdlib.http.compiler.codeaction; - -import io.ballerina.compiler.syntax.tree.NonTerminalNode; -import io.ballerina.compiler.syntax.tree.SyntaxTree; -import io.ballerina.projects.plugins.codeaction.CodeAction; -import io.ballerina.projects.plugins.codeaction.CodeActionArgument; -import io.ballerina.projects.plugins.codeaction.CodeActionContext; -import io.ballerina.projects.plugins.codeaction.CodeActionExecutionContext; -import io.ballerina.projects.plugins.codeaction.CodeActionInfo; -import io.ballerina.projects.plugins.codeaction.DocumentEdit; -import io.ballerina.stdlib.http.compiler.HttpDiagnosticCodes; -import io.ballerina.tools.text.LineRange; -import io.ballerina.tools.text.TextDocument; -import io.ballerina.tools.text.TextDocumentChange; -import io.ballerina.tools.text.TextEdit; -import io.ballerina.tools.text.TextRange; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.EXPECTED_BASE_PATH; -import static io.ballerina.stdlib.http.compiler.codeaction.Constants.NODE_LOCATION_KEY; - -/** - * Represents a code action to replace the existing base path of the service declaration - * with the expected base path from the service contract type. - * - * @since 2.12.0 - */ -public class ChangeBasePathCodeAction implements CodeAction { - @Override - public List supportedDiagnosticCodes() { - return List.of(HttpDiagnosticCodes.HTTP_155.getCode()); - } - - @Override - public Optional codeActionInfo(CodeActionContext context) { - NonTerminalNode node = CodeActionUtil.findNode(context.currentDocument().syntaxTree(), - context.diagnostic().location().lineRange()); - String diagnosticMsg = context.diagnostic().message(); - Pattern pattern = Pattern.compile("Expected base path is (.*),"); - Matcher matcher = pattern.matcher(diagnosticMsg); - String basePath = ""; - if (matcher.find()) { - basePath = matcher.group(1); - } - CodeActionArgument basePathArg = CodeActionArgument.from(EXPECTED_BASE_PATH, basePath); - CodeActionArgument locationArg = CodeActionArgument.from(NODE_LOCATION_KEY, node.location().lineRange()); - return Optional.of(CodeActionInfo.from("Change base path according to the service contract", - List.of(locationArg, basePathArg))); - } - - @Override - public List execute(CodeActionExecutionContext context) { - LineRange lineRange = null; - String basePath = ""; - for (CodeActionArgument argument : context.arguments()) { - if (NODE_LOCATION_KEY.equals(argument.key())) { - lineRange = argument.valueAs(LineRange.class); - } - if (EXPECTED_BASE_PATH.equals(argument.key())) { - basePath = argument.valueAs(String.class); - } - } - - if (lineRange == null || basePath.isEmpty()) { - return Collections.emptyList(); - } - - SyntaxTree syntaxTree = context.currentDocument().syntaxTree(); - TextDocument textDocument = syntaxTree.textDocument(); - int start = textDocument.textPositionFrom(lineRange.startLine()); - int end = textDocument.textPositionFrom(lineRange.endLine()); - - List textEdits = new ArrayList<>(); - textEdits.add(TextEdit.from(TextRange.from(start, end - start), "\"" + basePath + "\"")); - TextDocumentChange change = TextDocumentChange.from(textEdits.toArray(new TextEdit[0])); - TextDocument modifiedTextDocument = syntaxTree.textDocument().apply(change); - return Collections.singletonList(new DocumentEdit(context.fileUri(), SyntaxTree.from(modifiedTextDocument))); - } - - @Override - public String name() { - return "CHANGE_BASE_PATH"; - } -} diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HTTPServicesRegistry.java b/native/src/main/java/io/ballerina/stdlib/http/api/HTTPServicesRegistry.java index cc6c2606b5..419ed95e24 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HTTPServicesRegistry.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HTTPServicesRegistry.java @@ -104,13 +104,15 @@ public List getSortedServiceURIsByHost(String hostName) { * Register a service into the map. * * @param service requested serviceInfo to be registered. - * @param basePath absolute resource path of the service + * @param basePathFromDeclaration absolute resource path of the service */ - public void registerService(BObject service, String basePath) { + public void registerService(BObject service, String basePathFromDeclaration) { Optional serviceContractType = getServiceContractType(service); HttpService httpService = serviceContractType.map(referenceType -> - HttpServiceFromContract.buildHttpService(service, basePath, referenceType)).orElseGet( - () -> HttpService.buildHttpService(service, basePath)); + HttpServiceFromContract.buildHttpService(service, basePathFromDeclaration, + referenceType)).orElseGet( + () -> HttpService.buildHttpService(service, basePathFromDeclaration)); + String basePath = httpService.getBasePath(); service.addNativeData(HttpConstants.ABSOLUTE_RESOURCE_PATH, basePath); String hostName = httpService.getHostName(); if (servicesMapByHost.get(hostName) == null) { diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java index 84af585f01..e7ee1c3092 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpConstants.java @@ -125,6 +125,7 @@ public final class HttpConstants { public static final BString ANN_CONFIG_ATTR_COMPRESSION = StringUtils.fromString("compression"); public static final BString ANN_CONFIG_ATTR_COMPRESSION_ENABLE = StringUtils.fromString("enable"); public static final BString ANN_CONFIG_ATTR_COMPRESSION_CONTENT_TYPES = StringUtils.fromString("contentTypes"); + public static final BString ANN_CONFIG_BASE_PATH = StringUtils.fromString("basePath"); public static final String ANN_CONFIG_ATTR_CACHE_SIZE = "cacheSize"; public static final String ANN_CONFIG_ATTR_CACHE_VALIDITY_PERIOD = "cacheValidityPeriod"; public static final String ANN_CONFIG_ATTR_WEBSOCKET = "webSocket"; diff --git a/native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java b/native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java index 5bc2c3374b..def4b288c7 100644 --- a/native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java +++ b/native/src/main/java/io/ballerina/stdlib/http/api/HttpServiceFromContract.java @@ -28,6 +28,8 @@ import io.ballerina.stdlib.http.api.nativeimpl.ModuleUtils; import io.ballerina.stdlib.http.uri.DispatcherUtil; +import java.util.Objects; + import static io.ballerina.runtime.api.utils.StringUtils.fromString; import static io.ballerina.stdlib.http.api.HttpUtil.checkConfigAnnotationAvailability; @@ -50,6 +52,10 @@ public static HttpService buildHttpService(BObject service, String basePath, HttpService httpService = new HttpServiceFromContract(service, basePath, serviceContractType); BMap serviceConfig = getHttpServiceConfigAnnotation(serviceContractType); if (checkConfigAnnotationAvailability(serviceConfig)) { + Object basePathFromAnnotation = serviceConfig.get(HttpConstants.ANN_CONFIG_BASE_PATH); + if (Objects.nonNull(basePathFromAnnotation)) { + httpService.setBasePath(basePathFromAnnotation.toString()); + } httpService.setCompressionConfig( (BMap) serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_COMPRESSION)); httpService.setChunkingConfig(serviceConfig.get(HttpConstants.ANN_CONFIG_ATTR_CHUNKING).toString());